Skip to content

feat(verify-run): judge any executor's write from the system of record, aligned with RFC-0002 - #1198

Merged
abrichr merged 6 commits into
mainfrom
feat/verify-run-rfc0002
Oct 17, 2026
Merged

abrichr merged 6 commits into
mainfrom
feat/verify-run-rfc0002

Conversation

@abrichr

@abrichr abrichr commented Oct 17, 2026

Copy link
Copy Markdown
Member

Ports the Chief Architect's feat/verify-any-executor prototype (8f363086, 0139e1b8) into packages/flow and aligns it with RFC-0002 (Open Effect Receipt).

openadapt-flow verify-run reads the system of record through an existing effect verifier and rules on a declared effect contract. The executor can be an agent, a script, a person or a Flow replay. The executor's run record is hashed into the receipt and never read as evidence. It writes an unsigned openadapt.effect-receipt/v1; it never signs, uploads or calls a model.

RFC-0002 alignment:

  • Signature domain: an issuer signs openadapt.effect-seal-signature/v1 (issuer openadapt-cloud:unattested-verifier) over its own domain. A pre-RFC Execute Seal verifier refuses an effect receipt, and --check refuses an Execute-domain signature on one.
  • Per-run salt (openadapt.run-salt/v1): the run record, the authorization reference, the baseline and every bound parameter in an effect's contract hash are HMAC-SHA256 under a 256-bit salt drawn for the run. The evidence keeps only salt_commitment. The salt stays in the 0600 baseline, or in effect-run.private.json when there is no baseline.
  • Baseline binding: the baseline starts the run (execution_id), holds exactly six keys and binds five, works once, and counts as absent outside max_execution_window_s. Each REST read carries X-OpenAdapt-Execution-Id and traceparent.
  • Tier rule: independent_readback only when every effect was read from the system of record without the UI (Flow tier 1). A separate signed-in UI session is screen_readback. Only independent_readback can yield verified.
  • Self-approval: if the approver is the executor, a landed write is rejected_policy.
  • New optional flags: --remediates <receipt_id> and --correlation-id <id>, both opaque ids. They appear in the receipt body only when given, and are then inside receipt_sha256 and so inside the signature.
  • Offline --check: reports the signature (verified) and the outcome (verified_outcome) separately. It exits 0 only when both hold, or when the outcome is one named with --accept-outcome. An unsigned file is never a pass.
  • One verify.yaml with qualify (feat(qualify): declarative qualify from verify.yaml (carry-over of openadapt-flow#532) #1186): verify-run ignores the qualify-only sections and verifier.session, expands record and ${NAME} with qualify's own code, and computes workflow_digest with qualify's verify_config_sha256.

Tests: tests/test_verify_run.py (35 tests: salting, one-use and windowed baselines, the window and unsalted checks in --check, the link fields, self-approval, accept-outcome, the domain separation, and the qualify file). Local: ruff, format and mypy are clean; the verify-run and qualify tests pass.

Follow-ups: forbidden_effects (RFC "proposed") isn't implemented yet. The approver shape is a placeholder until the approval contracts from #1187 are wired in.

…of record (prototype)

openadapt-flow verify-run reads the store through an existing EffectVerifier (built from the deployment.yaml effects: shape) and rules on an effect_contract.yaml list. The executor's run record is bound by digest and never read as evidence; contract params come from --params, not from the executor. VERIFIED needs every effect CONFIRMED by a system-of-record read; screen read-backs are refused and separate-session reads stop at reconciliation_required.

The output keeps every ExecuteEvidenceReceiptV1 field, adds executor_kind, executor_ref_sha256, and effect_verdict under schema openadapt.effect-receipt/v1, and binds an evidence manifest through evidence_digest. It is unsigned; a seal-signature/v1 over it verifies with openadapt_types.seal and openadapt-agent seal verify unchanged.

Tests: MockMed fault server as the system of record, with test code as the external actor (correct, wrong note, wrong patient, duplicate, phantom write, no baseline, unreachable store).

Ported from openadapt-flow feat/verify-any-executor (8f363086).

Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
verification_tier (independent_readback | screen_readback | unchecked) goes on the receipt and the evidence manifest; only independent_readback can yield verified, and check refuses a verified receipt without it. authorization.approver is null until the openadapt-types approver identity exists (RFC-0002).

Ported from openadapt-flow feat/verify-any-executor (0139e1b8).

Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
- Signatures are openadapt.effect-seal-signature/v1 over their own
  domain (issuer openadapt-cloud:unattested-verifier); an Execute Seal
  verifier refuses an effect receipt, and an Execute signature on one
  is refused here.
- Per-run 256-bit salt (openadapt.run-salt/v1): the run record,
  authorization reference, baseline and every bound parameter are
  HMAC-SHA256 digests; evidence carries only the salt commitment. The
  salt stays in the 0600 baseline or effect-run.private.json.
- Baselines start the run (execution_id), hold exactly six keys, bind
  five, work once, and count as absent outside max_execution_window_s.
  REST reads carry X-OpenAdapt-Execution-Id and traceparent.
- verification_tier follows the RFC tier rule: independent_readback
  only for system-of-record reads (Flow tier 1); a separate UI session
  is screen_readback.
- A self-approved write is rejected_policy.
- New optional --remediates <receipt_id> and --correlation-id, signed
  into the receipt body when given.
- --check reports the signature and the outcome separately; exit 0
  needs both, or an outcome named with --accept-outcome.

Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
RFC-0002 makes openadapt.verify/v1 one file for both commands. verify-run
now ignores the qualify-only sections (app, save_step, inputs, campaign,
verifier.session), expands record with qualify's own record_effects,
expands ${NAME} references with qualify's expander, and computes
workflow_digest with qualify's verify_config_sha256, so a receipt and a
qualification result over one file name the same contract.
@abrichr
abrichr merged commit 893f40d into main Oct 17, 2026
25 checks passed
@abrichr
abrichr deleted the feat/verify-run-rfc0002 branch October 17, 2026 23:39
abrichr added a commit that referenced this pull request Oct 17, 2026
Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
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