Skip to content

fix(api): reject stale ticket updates and completion atomically - #53

Merged
ifuri-validator-agent[bot] merged 1 commit into
mainfrom
fix/guarded-ticket-update
Sep 5, 2026
Merged

fix(api): reject stale ticket updates and completion atomically#53
ifuri-validator-agent[bot] merged 1 commit into
mainfrom
fix/guarded-ticket-update

Conversation

@tom-sapletta-com

Copy link
Copy Markdown
Contributor

A project observer can finish reading a ticket before a worker records a failure, then overwrite the worker's execution state or receipts with its stale result. Add POST /tickets/{id}/update-if-current and complete-if-current, both requiring the observed updated_at and enforcing it inside the existing store mutation lock.

Both endpoints reuse the current validation and event paths, including the governed completion receipt gate. Missing revisions are rejected; conflicting writes return 409 without a change event. Dedicated endpoints prevent older servers from silently ignoring a precondition. Existing routes remain compatible.

Validation: 104 API and lifecycle tests passed, including stale-write interleavings in YAML and sharded YAML storage; Ruff passed for the new regression suite. Dependency for Subactor Core ticket-231; deploy this additive API before the Core conditional writer.

@ifuri-validator-agent ifuri-validator-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deterministic Validator approval for exact head b6c6a959a631fbcfbeb968852778ad0d3aa81b82.

Ticket: ticket-231
Correlation ID: planfile-pr-53-ticket-231
Model: zai/glm-5.3
Reviewed diff chunks: 1
Advisory LLM verdict: APPROVE
Advisory summary: Reviewed all 1 diff chunk(s). Adds conditional update/complete endpoints with required expected_updated_at, capability advertisement, and thorough regression tests covering validation, happy path, stale-write races (including interleaved mutation), and the completion receipt gate across both storage backends. Race safety is enforced inside the existing store mutation lock via update_ticket's expected_updated_at, which the interleaving test demonstrates.
Advisory findings: none
The LLM output above is advisory and was not used as the approval trust root.

Actual PR impact radar

Exact range: 8f9b7d0b6a05fbb86170f8bd6da6ba153272716a...b6c6a959a631fbcfbeb968852778ad0d3aa81b82
Change digest: e19eb430f6961949c9eafce456b1a708ea0d7328691c43d898dce1c4ef92c313
Score: 48/100 (M), estimated 52 min, split recommended: true
Affected services/components: repository-wide/unclassified

Machine-readable radar JSONL and SVG
{"actual_change":{"additions":135,"base_sha":"8f9b7d0b6a05fbb86170f8bd6da6ba153272716a","binary_files":0,"categories":{"code":2,"tests":1},"change_digest":"e19eb430f6961949c9eafce456b1a708ea0d7328691c43d898dce1c4ef92c313","comparison":"8f9b7d0b6a05fbb86170f8bd6da6ba153272716a...b6c6a959a631fbcfbeb968852778ad0d3aa81b82","deletions":2,"file_count":3,"files":["planfile/__init__.py","planfile/api/server.py","tests/test_ticket_update_precondition.py"],"head_sha":"b6c6a959a631fbcfbeb968852778ad0d3aa81b82","service_count":0,"services":[]},"assessment_mode":"observed-pr","axes":{"coupling":3,"delivery":3,"scope":1,"uncertainty":3,"validation":2},"complexity":"M","confidence":0.9,"diagnostics":["RADAR-ACCEPTANCE-MISSING","RADAR-BUDGET-EXCEEDED"],"estimate":{"budget_minutes":30,"minutes":52,"within_budget":false},"impact":{"components":["planfile","tests"],"files":["planfile/__init__.py","planfile/api/server.py","tests/test_ticket_update_precondition.py"],"public_interfaces":["planfile/api/server.py"],"runtime_dependencies":1},"schema":"subactor.ticket-radar/v1","score":48,"split":{"parts":[{"estimated_minutes":20,"name":"Define contract and acceptance boundary","scope":["planfile/api/server.py"]},{"estimated_minutes":11,"name":"Implement planfile","scope":["planfile"]},{"estimated_minutes":11,"name":"Implement tests","scope":["tests"]},{"estimated_minutes":15,"name":"Validate and project to trackers","scope":["tests","planfile","github/gitlab/jira projections"]}],"reason":"estimated_minutes_exceed_budget","recommended":true},"standards":[{"id":"wellmanifest/dsl","revision":"6c60fc4e0dd1f1bb74f46a7745e28019908d1203","version":"0.1.0-dev"},{"id":"wellmanifest/ticket-lifecycle","revision":"5bf581907a87b46a13a73e6c033d3abe4d9a306f","version":"0.1.0-dev"},{"id":"wellmanifest/git-lifecycle","revision":"7d77d4b7af57e69bc75c3a0290b3a4805c5c4438","version":"0.2.0-dev"},{"id":"wellmanifest/logs","revision":"48c284ef7a069055c0bcb6b900147ce5e65f8b43","version":"0.3.0"}],"ticket_ref":"ticket-231"}
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img"><title>ticket-231: fix(api): reject stale ticket updates and completion atomically</title><rect width="128" height="128" rx="12" fill="#f8fafc"/><g stroke-width="1"><polygon points="64,55 72,61 69,71 59,71 56,61" fill="none" stroke="#d7dde5"/><polygon points="64,47 80,59 74,78 54,78 48,59" fill="none" stroke="#d7dde5"/><polygon points="64,38 89,56 79,85 49,85 39,56" fill="none" stroke="#d7dde5"/><polygon points="64,30 97,53 84,92 44,92 31,53" fill="none" stroke="#d7dde5"/><polygon points="64,21 105,51 89,99 39,99 23,51" fill="none" stroke="#d7dde5"/><line x1="64" y1="64" x2="64" y2="21" stroke="#aab4c0"/><line x1="64" y1="64" x2="105" y2="51" stroke="#aab4c0"/><line x1="64" y1="64" x2="89" y2="99" stroke="#aab4c0"/><line x1="64" y1="64" x2="39" y2="99" stroke="#aab4c0"/><line x1="64" y1="64" x2="23" y2="51" stroke="#aab4c0"/></g><polygon points="64,55 89,56 79,85 54,78 39,56" fill="#fb923c" fill-opacity="0.45" stroke="#c2410c" stroke-width="2"/><circle cx="64" cy="64" r="3" fill="#c2410c"/><g font-family="sans-serif" font-size="7" fill="#334155"><text x="64" y="11" text-anchor="middle">SCO</text><text x="114" y="48" text-anchor="middle">COU</text><text x="95" y="107" text-anchor="middle">UNC</text><text x="33" y="107" text-anchor="middle">VAL</text><text x="14" y="48" text-anchor="middle">DEL</text></g><text x="64" y="124" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#0f172a">M · 52m</text></svg>
Merge will be attempted after this approval when explicitly authorized. ## Decision record (recomputable)
DECISION D-231-9297
TICKET ticket-231
HEAD_SHA b6c6a959a631fbcfbeb968852778ad0d3aa81b82
CORRELATION_ID planfile-pr-53-ticket-231
ACTOR agent:ifuri-validator-agent[bot]
APPLIED_RULE P-CORE-015
INPUT author_login = "tom-sapletta-com"
INPUT observed_checks = ["notify=PASS","ci-loop=PASS"]
INPUT required_checks = ["ci-loop","notify"]
INPUT required_checks_source = "protected registry (env/request)"
INPUT reviewer_login = "ifuri-validator-agent[bot]"
INPUT superseded_checks = []
INPUT ticket_radar_receipt = {"schema":"subactor.ticket-radar/v1","base_sha":"8f9b7d0b6a05fbb86170f8bd6da6ba153272716a","head_sha":"b6c6a959a631fbcfbeb968852778ad0d3aa81b82","change_digest":"e19eb430f6961949c9eafce456b1a708ea0d7328691c43d898dce1c4ef92c313","score":48,"complexity":"M","estimated_minutes":52,"split_recommended":true,"services":[],"authority":"ADVISORY","promotion":"FORBIDDEN"}
VERDICT APPROVE AUTHORITY DETERMINISTIC
REJECTED REQUEST_CHANGES BECAUSE NO_UNSAFE_CHANGE_REASON_FOUND
ADVISORY llm_verdict = "APPROVE" MODEL "zai/glm-5.3"
ASSERT VERDICT_AUTHORITY != "ADVISORY"

@ifuri-validator-agent
ifuri-validator-agent Bot merged commit 13f1d7f into main Sep 5, 2026
2 checks passed
@ifuri-validator-agent
ifuri-validator-agent Bot deleted the fix/guarded-ticket-update branch September 5, 2026 14:13
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