Skip to content

Retain keyed signal receipts and refuse key reuse with different intent - #877

Draft
rmcdaniel wants to merge 7 commits into
mainfrom
claude/216-command-keys
Draft

rmcdaniel wants to merge 7 commits into
mainfrom
claude/216-command-keys

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

What

  • Signals get the same caller-key receipt identity updates already have: request_id (body key on Server, withRequestId() or the request_id control-plane option embedded) is stored as a signal-v1: token under the existing unique index; a retry with the same key returns the retained admission (retained: true) without recording another command, signal, history event or task, including after closure and after continue-as-new.
  • Intent equality: a new workflow_commands.intent_digest column (DDL-only migration) records a canonical digest of command type, name, validated typed arguments and run target. Same key with a different intent is refused with 409 request_id_conflict (outcome rejected_request_id_conflict, recorded for audit, never consumes the key). Legacy receipts without a digest are recomputed from retained arguments when accepted, or returned with intent_verified: false when rejected.
  • CommandRequestIdentity generalizes UpdateRequestIdentity (kept as a facade); responses add request_id, retained, intent_verified and conflict fields.

Status (work in progress)

Done: implementation, CommandIntentTest, CommandRequestIdentityTest, rewritten UpdateRequestIdentityTest (contract change), phpstan clean, ecs clean. Full unit/feature suites not yet run to completion; an adversarial review was started but its findings are not yet applied.

Remaining: run the full suites on PostgreSQL/MySQL, apply review findings, CHANGELOG entry, release as a minor version after the pending 2.7.x/2.9.9 publications, docs in all eight locales.

Part of durable-workflow/.github#216. Contract: durable-workflow/.github#216 (comment)

🤖 Generated with Claude Code

Part of durable-workflow/.github#216 (safe signal/update retries after a lost response). Work in progress; see the draft PR body for the remaining steps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Takeover review reproduced another draft-code gap: the control-plane loader ignored an explicit run target. Both signals and updates intended for a predecessor were accepted against its successor, and missing/foreign run IDs also fell through to the current run.

Tests-first 5d08795a has six genuine failures on the preceding runtime. The prospective correction 14acbfb73be831e04e27ab62f3d14115611c2136 loads the selected run, preserving namespace/instance ownership, before the existing admission lock rechecks whether it is current. A current selected-run retry retains its original receipt; using that key for another run conflicts. Missing and foreign runs return 404 without mutations.

The final combined intent, receipt and control-plane-routing selection passes 90 tests / 1,313 assertions, with targeted ECS and PHPStan clean. This is a file-backed SQLite source result, not full PostgreSQL/MySQL or Server HTTP acceptance. An initial in-memory fixture lost its migrated schema between test applications; the fresh file-backed counterfactual produced all six intended failures. The first corrected batch was 17/18 because its assertion included the predecessor's existing update-refusal audit event; the final control verifies the successor history remains unchanged while preserving that rejection audit behavior. No released-product finding is inferred.

Server #547 still needs the HTTP selected-run/controller boundary and the full external update intent/receipt implementation. Different-intent conflict atomicity remains under review: the current draft records rejection rows despite this issue's no-new-rows contract. No completion or publication is claimed.

@rmcdaniel

Copy link
Copy Markdown
Member Author

The no-write conflict review is now reproduced and corrected in the draft.

Tests-first a6c2e658 fails three genuine controls on the preceding implementation: a different-intent retry of a signal, an update or signal-with-start creates rejection rows and advances message/command sequence counters. The update also appends an UpdateRejected event. This violates the accepted retry contract even though it never delivers the changed command.

Prospective source 6956502f1cf8af6beb032bb53fa38d13acac528d builds an unpersisted refusal response while holding the same admission lock. It does not reserve sequences, create a signal/update/start group, or alter the retained receipt. HTTP output has no newly admitted command_id, command sequence or update ID; retained_command_id identifies the original. To preserve the embedded result API's existing string return type, commandId() on this new conflict kind refers to that retained command, with a null sequence. This does not claim that the retained command itself was rejected.

The strengthened identity/intent/routing selection passes 90 tests / 1,328 assertions independently on SQLite, PostgreSQL 17 and MySQL 8.0.46. Targeted ECS and PHPStan pass. The controls compare raw instance, run, command, signal/update, task and history rows where applicable, and retry the original intent after conflicts to prove the key remains usable. Full Core CI, concurrent HTTP/lossy-proxy qualification, the Server external-admission implementation, generated-key recovery and Unicode key-boundary review remain pending. No draft component is treated as a published fix.

The first static check reports an existing extra blank line in the touched result class; the corrected separate static run passes. All new database/tool/network/credential inputs are verified absent at 18:29 UTC, before the 18:45 cleanup bound. The active inherited source/dependencies remain for #216. Other owners' resources and all frozen/native fixtures are unchanged.

@sonarqubecloud

Copy link
Copy Markdown

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.

2 participants