Skip to content

HW-01: stable effect identity and durable receipt contract#139

Merged
skylarbpayne merged 11 commits into
mainfrom
remediation/hw-01
Jul 12, 2026
Merged

HW-01: stable effect identity and durable receipt contract#139
skylarbpayne merged 11 commits into
mainfrom
remediation/hw-01

Conversation

@skylarbpayne

@skylarbpayne skylarbpayne commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • adds attempt-independent operation identity over canonical workflow/effect/adapter/input coordinates
  • adds durable SQLite effect intents, CAS claims, token fencing, terminal receipts/errors, and sensitive receipt descriptors
  • resolves adapters only through the FND-RT generic process-local service lookup (effects.adapters, contract v1)
  • refuses legacy/unclassified execution; unsafe intents require explicit authorization and cannot be automatically reclaimed

Delivery semantics

This contract is at least once, not exactly once. Supported idempotent adapters must deduplicate by the stable operation ID and provide receipt lookup. The durable receipt proves what the adapter reported; it does not claim universal exactly-once delivery.

Base and scope

docs/contracts/effects.md
src/hermes_workflows/effects.py
tests/contracts/test_effect_adapter_contract.py
tests/fixtures/effect_contract_v1.json
tests/probes/kill_after_effect.py
tests/test_effect_idempotency.py

Stable identity receipt

Contract fixture vector:

  • operation ID: op_779cfb267aaf4485e131af3c56d0366053f28814563e018beefab5b9627eaf95
  • input hash: 0443d565a2434c98e2d2034c8bdc5621cc77e23864d75088f7b6e29e3d8ed928
  • fixture SHA-256: 4808144715e0dfd168768fbe8ee17dac1a57f91bfe673af6065ac2a43cd91bf9

The first TDD command failed with ModuleNotFoundError: hermes_workflows.effects, then passed after implementation.

Crash transcripts

Probe operation ID for all four windows: op_d00b6c1fa97f79cb17494d6de67bea401b866b896bbcdb324fa62837d8c41c1c

Probe input hash: 2c4c3d67557b89e6376283cf6017041fb69327f0e6a006715ec56471485a90e7

Fencing rows below are [state, claim_attempts, SHA-256(claim_token)]; raw opaque tokens are not exposed.

window attempts external effects adapter receipts completed receipts fencing row
before adapter call 2 1 1 1 [completed, 2, 3a614ac684eaf54235de04917066bddcceff57cf980876e5c5c919ba7e9e19ee]
during adapter call 2 1 1 1 [completed, 2, ce3b765b4b770d7d9a70276fb1252bd6d5e53ef3b6641b95dec24489d5014654]
after external effect before adapter receipt 2 1 1 1 [completed, 2, 274fe711d387d86e31e75ef37637a3924e33bd92002647515196f632ba54ace7]
after durable receipt before linked command completion 2 1 1 1 [completed, 1, de1567ec2c7575e47d22887c51e519d069915408266a5c3fb1a45fdd79573edd]

Contract evidence

  • real file-backed adapter subprocess invoked twice: one external effect and the same queryable adapter receipt
  • pending → claimed → completed/failed transitions use real temporary SQLite
  • concurrent claim race yields one winner
  • expired and stale tokens cannot complete/fail; expired unsafe claims cannot be automatically reclaimed
  • legacy/unclassified durable rows are not claimable
  • sensitive receipt descriptor excludes the raw payload and access_token
  • Python 3.9 direct smoke: completed

Verification

  • uv run pytest -q tests/test_effect_idempotency.py tests/contracts/test_effect_adapter_contract.py tests/test_runtime_services_contract.py54 passed
  • python tests/probes/kill_after_effect.py → all four windows converged to one external effect and one completed receipt
  • uv run pytest -q488 passed, 2 skipped
  • git diff --check → clean
  • fixture SHA-256 and exact allowlist verification → passed

Non-goals

No central runtime activation, generic retry, lease cancellation, callbacks, live service/profile/registry/DB mutation, exactly-once claim, inferred purity, or UI redesign.

Do not merge until independent HW-01 specification and quality reviews pass.

Repair 1 evidence (782786b)

Closed the two fresh operation-identity integrity blockers at exact head 782786b2f2b8120bdf40fdde5be02caed0a59079:

  • TDD red: focused regressions failed because duplicate-key custom Mapping input was silently collapsed and forged hash-shaped operation_id was persisted.
  • TDD green: duplicate mapping items now fail closed; SQLiteEffectStore.ensure_intent recomputes complete identity from workflow/effect/adapter/canonical input before insertion and rejects mismatch.
  • Targeted effect/adapter suite: 14 passed.
  • Python 3.9 floor: 16 passed across compatibility + effect/adapter tests.
  • Full suite: 491 passed, 1 skipped.
  • Independent adversarial probe: 32 concurrent claimers → 1 winner, durable attempts=1; duplicate mapping rejected; forged ID rejected before insert.
  • Four crash windows: each converged to completed with exactly 1 external effect, 1 adapter receipt, and 1 local receipt; durable claim attempts 2/2/2/1.
  • git diff --check: clean. Exact six-path allowlist and base ancestry from 583459d3ee36dae63f89dbd87d175f91b53ec39d: verified.
  • Semantics remain at least once, not exactly once. No live Palmer/profile/registry/database/service/install/deploy action occurred.

Fresh independent SR, then fresh independent QR, remain mandatory before merge.

Repair 2 evidence (fc00967)

Closed the fresh receipt operation-identity blocker at exact head fc0096772f95dd57fdd2bd75e81302288d5924a5:

  • TDD red: four lookup/perform regressions failed because conflicting receipt operation_id values were accepted and durably completed under the requested operation.
  • TDD green: coordinator and adapter-contract regressions now reject conflicting lookup and perform receipts before durable completion; omitted or exactly matching receipt IDs remain valid.
  • Targeted effect/adapter suite: 18 passed.
  • Full suite: 495 passed, 1 skipped.
  • Four crash windows: each retained exactly 1 external effect, 1 adapter receipt, and 1 completed local receipt; durable claim attempts 2/2/2/1.
  • git diff --check: clean. Exact six-path allowlist and base ancestry from 583459d3ee36dae63f89dbd87d175f91b53ec39d: verified.
  • Semantics remain at least once, not exactly once. No live Palmer/profile/registry/database/service/install/deploy action occurred.

Fresh independent SR, then fresh independent QR, remain mandatory before merge.

Repair 5 evidence (79d0e56)

Closed the forged/stale/expired claim pre-adapter blocker at exact head 79d0e5677b09859be70632c6003c96fcddbb671f:

  • TDD red: 3 focused regressions failed because each rejected claim still called adapter lookup and perform before the terminal CAS refusal (adapter.calls had 2 entries instead of 0).
  • TDD green: forged-token, superseded-token, and expired-current-token cases all fail before adapter lookup/perform; the coordinator now proves durable operation/token ownership and unexpired claim state, then binds adapter/input checks to the durable identity.
  • Targeted effect/adapter suite: 24 passed.
  • Full suite: 501 passed, 1 skipped.
  • Four crash windows: each retained exactly 1 external effect, 1 adapter receipt, and 1 completed local receipt; durable claim attempts 2/2/2/1.
  • git diff --check: clean. Exact six-path allowlist verified; only src/hermes_workflows/effects.py and tests/test_effect_idempotency.py changed.
  • Semantics remain at least once, not exactly once. No live profile/registry/database/service/install/deploy mutation occurred.

Fresh independent SR, then fresh independent QR, remain mandatory before merge.

Repair 7 evidence (a62fbd8)

Closed SR8's unsafe-policy bypass at exact head a62fbd8c80a4d3bfc09933a3fcaa7eecb57292e7:

  • TDD red: the direct ensure_intent(UNSAFE) + claim + execute_claimed regression performed and durably completed the external effect without authorization; the authorized-path regression also proved no durable authorization column existed.
  • TDD green: unsafe authorization is now immutable durable intent evidence, prepare(..., allow_unsafe=True) persists it, and execute_claimed rechecks the durable evidence before adapter lookup or perform. Unauthorized direct-store intents remain inventoriable/once-claimable but cannot call the adapter or write a receipt.
  • Targeted effect/adapter suite: 27 passed.
  • Full suite: 504 passed, 1 skipped.
  • Four crash windows: each retained exactly 1 external effect, 1 adapter receipt, and 1 completed local receipt; durable claim attempts 2/2/2/1.
  • git diff --check: clean. Exact six-path allowlist and ancestry from repair base 1f91be7c3742a539cd16aac810018e7783c7161c: verified; five allowed paths changed.
  • Fixture SHA-256: 9f3276542ccd570bfb90d4ecce2d27936df4da6caee45a05a387f68b7d59e52e.
  • Semantics remain at least once, not exactly once. No live Palmer/profile/registry/database/service/install/deploy action occurred.

Fresh independent SR, then fresh independent QR, remain mandatory before merge.

Repair 9 evidence (78d9255)

Closed QR12s stale-claim generation bypass at exact head 78d925502120b2cf628a544596e4f4f5f598877d:

  • TDD red at 145803a: a real temporary-SQLite expiry/reclaim regression forced the token generator to repeat the prior ownership token; the reclaimed attempt reused it, so stale and current claims were indistinguishable.
  • TDD green: SQLiteEffectStore.claim() no longer accepts caller-chosen tokens. The store generates ownership internally and refuses to reuse the active generations token, retrying generation before any durable claim transition.
  • Targeted effect/adapter suite: 29 passed.
  • Full suite: 506 passed, 1 skipped.
  • Four crash windows: each retained exactly 1 external effect, 1 adapter receipt, and 1 completed local receipt; durable claim attempts 2/2/2/1.
  • git diff --check: clean. Cumulative diff is exactly within the six-path allowlist; base 583459d3ee36dae63f89dbd87d175f91b53ec39d remains an ancestor.
  • Semantics remain at least once, not exactly once. No live Palmer/profile/registry/database/service/install/deploy action occurred.

Fresh independent SR, then fresh independent QR, remain mandatory before merge.

@skylarbpayne

skylarbpayne commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Repair3 pushed at e7f25e2. SQLiteEffectStore.complete() and fail() now canonicalize the original Mapping instead of collapsing it through dict(...). Added adversarial receipt/error regressions proving duplicate item streams are rejected while durable state remains claimed and no receipt/error persists. Local evidence: focused 20 passed; adversarial 3 passed; full 497 passed, 1 skipped; all four crash windows converged with one external effect and one durable receipt; exact allowlist/ancestry and git diff --check passed. Fresh independent SR then QR remain mandatory; do not merge.

@skylarbpayne

Copy link
Copy Markdown
Owner Author

Repair4 at 4e65e72 binds EffectClaim.operation_id to the durable record before adapter lookup/performance. TDD evidence: the forged replace(valid_claim, operation_id=other_id) regression failed at e7f25e2 (DID NOT RAISE), then passed after the fix. Focused HW-01 suite: 21 passed. Four crash windows: each converged completed with one external effect and one durable receipt. Full suite: 498 passed, 1 skipped. git diff --check passed; exact repair diff is limited to src/hermes_workflows/effects.py and tests/test_effect_idempotency.py; e7f25e2 is an ancestor. Awaiting fresh independent SR/QR; do not merge.

@skylarbpayne

Copy link
Copy Markdown
Owner Author

HW-01 repair5 pushed at 79d0e5677b09859be70632c6003c96fcddbb671f (previous head 4e65e72bbc9d4e3efb748d3647cc6add74b6a31a).

Blocker repaired: EffectCoordinator.execute_claimed() now verifies the claim's operation ID, token, and unexpired ownership against the durable intent before either adapter.lookup_receipt() or adapter.perform(). It also validates the supplied record identity against the durable identity.

TDD evidence:

  • RED: uv run pytest -q tests/test_effect_idempotency.py -k noncurrent_claim_before_adapter_call → 3 failed; forged, stale, and expired claims each made adapter lookup + perform calls before CAS refusal.
  • GREEN regression: forged/stale/expired claims now raise before adapter work; 4 targeted ownership tests passed.
  • Focused contract suite: 24 passed in 0.30s.
  • Crash probe: all 4 windows completed with external_effect_count == 1, one adapter receipt, and one durable completed receipt.
  • Full suite: 501 passed, 1 skipped in 32.46s.
  • git diff --check: clean.
  • Exact repair allowlist: only src/hermes_workflows/effects.py and tests/test_effect_idempotency.py; no forbidden paths.

Do not merge; fresh independent specification and quality/security review remain required.

@skylarbpayne

Copy link
Copy Markdown
Owner Author

Repair6 pushed at 1f91be755925b59f93e068a81f5e8c2d53dd83a2 (from 79d0e56).

  • Red: adversarial receipt lookup replaced the durable claim; regression failed because perform was still called.
  • Fix: preserve the pre-lookup fence, then revalidate the current unexpired claim token and durable operation identity immediately before perform.
  • Focused: 25 passed.
  • Full: 502 passed, 1 skipped.
  • Crash probe: all four windows completed with one external effect and one durable receipt.
  • git diff --check: clean.
  • Exact six-path allowlist: clean; this repair changes only effects.py and test_effect_idempotency.py.

No merge performed.

@skylarbpayne

Copy link
Copy Markdown
Owner Author

Repair8 pushed at 145803a: SQLiteEffectStore.complete() now rechecks durable unsafe authorization inside the completion transaction while preserving active-claim/token/expiry fencing. Added a real temporary-SQLite regression proving unauthorized public completion leaves the intent claimed and writes no receipt. Verification: focused 28 passed; all four crash windows passed; full suite 505 passed, 1 skipped; git diff --check clean; exact cumulative six-path allowlist and required ancestry verified.

@skylarbpayne
skylarbpayne merged commit e5cd713 into main Jul 12, 2026
4 checks passed
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