Skip to content

feat(compiler): emit identifier_crop — arm identity-on-pixels for remote-display/pixel workflows#158

Merged
abrichr merged 1 commit into
mainfrom
feat/compiler-identifier-crop
Jul 19, 2026
Merged

feat(compiler): emit identifier_crop — arm identity-on-pixels for remote-display/pixel workflows#158
abrichr merged 1 commit into
mainfrom
feat/compiler-identifier-crop

Conversation

@abrichr

@abrichr abrichr commented Jul 19, 2026

Copy link
Copy Markdown
Member

What

The runtime's pixel-compare identity tier (runtime.identity.verify_pixel_identity, the "right record on a no-DOM surface" check) has been fully wired since the identity-ladder work — but the COMPILER never emitted anchor.identifier_crop, so every Citrix/RDP/remote-display bundle ran with identity-on-pixels unarmed (flagged July 14, re-verified in the July 19 multisubstrate design review). This PR closes that gap end to end:

Compiler emission (extends the #120 machinery, superseded-not-merged via #135)

  • Ports context_region_from_lines + the shared _kept_context_lines band filter from the feat/macos-backend-pixel-path reference branch (feat: macOS pixel backend selector + compile-time identifier crop (arm pixel identity tier) #120) into runtime/identity.py — same filter as context_from_lines, single definition.
  • compiler.compile now emits anchor.identifier_crop + identifier_region for every identity-armed click that captured no structured identity (the pixel-recording case), cropping the tight bounding box of the surviving OCR identity-band lines.
  • Crops land under templates/identifiers/<step>.png — deliberately under templates/ so they ride the exact seal-template-crops (feat: seal template screenshot crops in the AEAD bundle (close at-rest image-PHI gap) #113) lineage: hashed into the integrity manifest, sealed to .enc under TEMPLATE_AAD on save(encrypt=True) with plaintext removed, decrypted in memory on load, and covered by run-gate 5's cleartext-asset check. No new PHI surface, no report rail touches them.

Record-time marking (mirrors the --secret field-marking pattern)

  • record --identifier FIELD (web): the in-page listener captures the marked field's rect at each click (identifier_region on the event). An explicit marking wins over the automatic band box and forces a crop even alongside structured identity (for structural recordings later replayed over Citrix/RDP).
  • record --backend windows|macos|linux|rdp --identifier X,Y,W,H: a pixel capture has no field identity, so the region is marked once per recording; stamped additively into meta.json post-convert (converter contract unchanged). Field-name syntax on a desktop backend is refused loudly (mirrors the --secret refusal).

Explicit degrade, never silent
New Step.identifier_crop_missing_reason (mirrors identity_unarmed_reason) records WHY an identity-applicable step compiled crop-less. Taxonomy:

  1. IDCROP_REASON_STRUCTURED — structured identity owns the step; no identifier pixels at rest (mark with --identifier to force).
  2. IDCROP_REASON_UNARMED — identity not armed at all (see identity_unarmed_reason).
  3. IDCROP_REASON_NO_BAND_REGION — band kept no lines to bound a crop from.
  4. IDCROP_REASON_MARKED_INVALID — marked region degenerate/outside the frame (prefixed onto whichever fallback reason applies; a valid band still yields a crop, with a compile-time warning).

Lint / certification coverage (mirrors the effect-coverage pattern)
lint_workflow now reports identifier_crop_armed_steps / identity_armed_steps ("pixel identity coverage: N/M …") and emits missing_identifier_crop per armed step without a crop — warn when identity rests only on the OCR band (the under-armed Citrix case), info when structured identity covers the step; the finding carries the compiler's degrade reason.

Runtime fix
Replayer._identifier_crops now threads workflow into _asset_bytes, so a SEALED crop in an encrypted bundle decrypts from the in-memory store and still arms the tier (previously the sealed path would silently abstain).

Safety posture (unchanged guarantees)

  • PIXEL_VERIFY_ENABLED stays False: the pixel tier remains MISMATCH-or-ABSTAIN. Emitting a crop can only add a safe HALT on a wrong identifier — it can never authorize a match, so zero-false-accept is preserved (pinned by test).
  • Structured recordings still write no identity pixels at rest unless explicitly marked.

Tests (all new paths covered on synthetic fixtures)

  • tests/test_compile_identifier_crop.py (10): crop emitted + region correct + manifest-hashed on a pixel recording; structured recording writes no crop and records why; event/meta markings win; invalid marking degrades to band with warning; malformed marking fails loud; unarmed reason; encrypted save seals the crop (no cleartext, .enc present, decrypts on load); wrong-MRN drives a pixel-tier MISMATCH through the real Replayer._verify_identity ladder on both plaintext and sealed bundles; same-value never pixel-VERIFIES.
  • tests/test_identifier_crop_lint.py (7): coverage counts/fraction, warn-vs-info severity, degrade reason in the finding, render lines.
  • tests/test_interactive_recorder.py: --identifier FIELD records the rect in-page and the compiled bundle carries the crop at that region.
  • tests/test_desktop_record.py: CLI region parse + refusal, meta.json stamp.

Claims: new identifier-crop-compile-emission entry (tier supported, honest caveats: fixture-proven mechanism, no substrate re-qualification, tier stays veto-only). docs/VERIFICATION.md regenerated. Docs: docs/desktop/RECORDING.md gains the --identifier section.

Full non-E2E suite + ruff (check/format) + mypy (base + strict-safety scope) green locally.

Local notes: the only 2 local failures (tests/test_pixel_identity_probe.py skimage import) are a pre-existing local-environment issue — skimage is absent from the project venv and resolves to a binary-incompatible anaconda install; those files are untouched by this PR.

🤖 Generated with Claude Code

…ote-display workflows

The pixel-compare identity tier (runtime.identity.verify_pixel_identity) has
been wired since the identity-ladder work, but the COMPILER never emitted
anchor.identifier_crop, so Citrix/RDP/remote-display bundles replayed with
identity-on-pixels unarmed. Close the gap end to end, extending the #120
reference machinery (superseded unmerged by #135):

- compile: emit anchor.identifier_crop + identifier_region for identity-armed
  clicks without structured identity (tight box of the surviving OCR identity
  band lines, via context_region_from_lines ported from #120), or at an
  explicitly marked region. Crops land under templates/identifiers/ so they
  ride the exact seal-template-crops (#113) handling: manifest-hashed, sealed
  to .enc under TEMPLATE_AAD on encrypted save, decrypted in memory on load,
  covered by run-gate 5 and the governed-run asset snapshot. No report rail
  touches them.
- explicit marking (mirrors --secret): `record --identifier FIELD` (web,
  in-page rect per click; wins over the band box and forces a crop even
  alongside structured identity) and `record --backend windows|macos|linux|rdp
  --identifier X,Y,W,H` (region stamped additively into meta.json; field-name
  syntax refused loudly on a pixel capture).
- explicit degrade, never silent: new Step.identifier_crop_missing_reason
  (mirrors identity_unarmed_reason) records WHY a step compiled crop-less
  (structured-owns-identity / identity-unarmed / no-band-lines /
  marked-region-invalid).
- lint (mirrors effect coverage): per-bundle pixel-identity coverage
  (identifier_crop_armed_steps / identity_armed_steps) + a
  missing_identifier_crop finding per armed crop-less step (warn on band-only
  identity, info under structured identity), carrying the degrade reason.
- runtime: thread workflow into Replayer._identifier_crops so a SEALED crop
  decrypts from the in-memory store and still arms the tier.

Safety unchanged: PIXEL_VERIFY_ENABLED stays False — the tier remains
MISMATCH-or-ABSTAIN, so a crop can only add a safe HALT on a wrong
identifier, never a pixel false-accept (pinned by test). Structured
recordings still write no identity pixels at rest unless marked.

Tests: compiler emission/marking/degrade/sealing (10), lint coverage (7),
web-recorder marking e2e, desktop CLI parse + meta stamp; wrong-MRN drives a
pixel-tier MISMATCH through the real Replayer ladder on plaintext AND sealed
bundles. Claims: identifier-crop-compile-emission (supported, fixture-scoped
caveats); VERIFICATION.md regenerated; RECORDING.md documents --identifier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@abrichr
abrichr force-pushed the feat/compiler-identifier-crop branch from 485c142 to e234f79 Compare July 19, 2026 09:46
@abrichr
abrichr merged commit b3e54ac into main Jul 19, 2026
14 checks passed
@abrichr
abrichr deleted the feat/compiler-identifier-crop branch July 19, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant