feat(authz): CR-YK-002 atomic capability claiming - #117
Merged
Merged
Conversation
Implements the CR-YK-002 standalone atomic-claiming foundation. Claim ownership and lifecycle state move into a local SQLite registry that takes BEGIN IMMEDIATE before any decision is read; the task ledger remains the durable evidence history and is never the concurrency lock. Source provenance (recorded because it is unusual): triage_core/capability_claims.py and tests/test_capability_claims.py were written earlier, left untracked, and lost -- they were never committed to any ref and appear in no stash. Both were reconstructed by replaying the Write and Edit tool calls from the authoring session transcript, in order; all eight operations matched their anchors exactly once, with no fuzzy matching. Fidelity evidence differs per file and is not equivalent: - capability_claims.py: the reconstruction compiles to bytecode byte-identical to the surviving __pycache__/capability_claims.cpython-314.pyc produced from the original. This is direct evidence of fidelity to the lost file. - test_capability_claims.py: only the source byte length recorded in the pyc header matched (25735 = 25735). Its only caches are pytest assertion-rewritten, so no direct bytecode comparison was possible. Fidelity here rests on clean transcript replay plus the length match, which is weaker. Separately, both files carry exact SHA-256 continuity from reconstruction to this tree, which establishes faithful transfer -- not fidelity to the original: capability_claims.py 148ca8d129763c3f40c67a554a7dbbbd502c90045a1048ac55e3be20501a060c test_capability_claims.py 24fc23aef9ebc183bb807a1c7ac417b757286ad9f4697b9aa64b42b3db500347 Those digests are of the LF working-tree content; the committed blobs are 9793fca and 3bb7aac respectively. Verification at this tree: - focused: tests/test_capability_claims.py + tests/test_authz.py 102 passed, 1 platform skip (POSIX permission bits). - full suite: 1291 passed, 5 environmental skips, 0 failures, 0 errors, 0 xfail, 0 xpass, 0 warnings. - concurrency: the former CR-YK-001 strict xfail is replaced by passing atomicity tests. Barrier-synchronised real threads, each with its own TaskLedger handle and an independent SQLite connection per operation, produce exactly one committed claim at 2 and at 8 concurrent claimers; the losers all report capability_already_consumed. Cross-process contention is not exercised. - SQLite integrity: PRAGMA integrity_check ok, foreign_key_check clean, recorded schema_version matches the module constant. Header corruption and truncation are detected and fail closed. Note that integrity_check is a logical-consistency check, not a checksum: a splice into unused page space is not detected, so this is not a tamper-evidence claim. - git diff --check passes for tracked and untracked content. - the change is an exact seven-path match to the CR-YK-002 allowlist. Authority boundary: this grants no execution or integration authority. triage_core/authz.py is the only non-test module that imports the claim store. No tc authz CLI surface, tc run integration, --confirmed-plan, backend or model invocation, routing, worker, FIDO2, or CR-DD-012B change is included. CR-DD-012B remains unauthorized and blocked. Pre-CR-YK-002 capability events are permanently unclaimable and are never migrated into authority. A crash, or a ledger-write failure after the SQLite commit, burns the authorization rather than risking duplicate execution. Human implementation approval for this slice is operator-attested; there is no cryptographic approval artifact backing it. CR-YK-002 remains unmerged until its PR completes review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updates the CR-YK-002 change request, backlog entry, and change log to describe the atomic-claiming foundation accurately now that it is committed as 27fd869. Corrects an inaccurate claim. Before this pass the CR's Status block asserted that the foundation "is implemented on branch cr-yk-002-atomic-capability- claiming". At the time that was false in a way that mattered: the branch held zero commits ahead of main, and the two central files -- capability_claims.py and test_capability_claims.py -- existed only as untracked working files that had since been lost. The Implementation Record listed them as delivered. The revised wording separates four states that the earlier text conflated: recovered into the working tree, verified by focused and full-suite tests, committed to the branch, and merged. Only the first three are true. The status now cites 27fd869 explicitly and records that review through the implementation PR is still required. The Status block also records that fidelity evidence for the two recovered files is not equivalent -- capability_claims.py is backed by a byte-identical bytecode comparison against the original's surviving cache, while test_capability_claims.py rests on clean transcript replay plus a source-length match. Exact SHA-256 continuity exists for both, but that establishes faithful transfer into this tree, not fidelity to the lost originals. No technical substance of the CR was rewritten: the requirements, storage, claim, terminal, privacy, compatibility, test, and acceptance contracts are unchanged, as are the Implementation Record's technical bullets. Only status language was touched. No code or test change is included in this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
✅ Deploy Preview for poetic-quokka-0fd859 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Brings the branch up to date with main (98df9c1) ahead of PR #117 review. A merge commit is used deliberately rather than a rebase or squash: the docs in e6fda4a cite implementation commit 27fd869 by SHA, and rewriting history would leave those provenance references dangling. One conflict, in docs/current_backlog.md. Both sides edited the same "For the daily-driver lane" paragraph: main appended a CR-DD-013 sentence while keeping the stale "CR-YK-002 requirements are approved, but its implementation remains unauthorized" clause, and this branch replaced that clause without knowledge of CR-DD-013. Resolved by taking main's paragraph and swapping only the CR-YK-002 clause, so both facts survive. The clause now cites 27fd869, matching the two other CR-YK-002 references in this file that e6fda4a had already updated. No other file conflicted. No implementation file conflicted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The module documents SQLite as authoritative for lifecycle state, claimant and execution-attempt binding, and terminal-transition enforcement. The v1 schema did not back that claim. Verified against the previous schema, direct SQL was permitted to: - transition issued -> completed and issued -> failed, skipping 'claimed' entirely, because the transition trigger rejected only updates out of a terminal state and the single case claimed -> issued; - insert a 'claimed' row with no claimant, no execution attempt, and no claimed_at, and insert a terminal row that was never claimed, because nothing tied row shape to state; - rewrite claimant_id, execution_attempt_id, and claimed_at on an already claimed row, because the immutable-binding trigger covered only the nine capability bindings and not claim ownership. The last of these is the sharpest: a claimed row could have its owner replaced wholesale while PRAGMA integrity_check still reported 'ok'. The public Python API never performed any of these writes, so behavior was correct in practice -- but the enforcement lived in the caller, not in the schema, which is precisely what the authority split says is not the case. Changes: - Legal-transition whitelist. capability_state_transition_legal replaces capability_terminal_is_absorbing and permits only issued -> claimed and claimed -> completed|failed. All other state changes abort. - Table-level CHECK binding row shape to state: 'issued' rows carry no lifecycle metadata; 'claimed' rows carry claimed_at, claimant_id, and execution_attempt_id but no terminal fields; terminal rows carry all claim metadata plus terminal_at and terminal_outcome = state. - capability_claim_ownership_immutable rejects changes to claimed_at, claimant_id, or execution_attempt_id once the row leaves 'issued'. - capability_terminal_metadata_immutable rejects changes to terminal_at or terminal_outcome once the row is terminal. SCHEMA_VERSION moves to v2. This is required, not cosmetic: CREATE TABLE IF NOT EXISTS cannot retrofit a table-level CHECK onto an existing table, so a v1 file would otherwise be opened and reported as healthy while silently lacking every guarantee above. A v1 database now fails closed as an unsupported version. No production database exists -- .triagecore/authz/ is absent and no runtime module consumes the store -- so nothing is stranded by the bump. Adds 12 direct-SQL regression tests that bypass the Python API entirely, since that is the only way to prove enforcement lives in the schema. Each was confirmed to fail against the v1 schema and pass against v2: forward skips to both terminal states, claimed rows without an owner, terminal rows never claimed, issued rows carrying claim metadata, mutation of all three claim ownership columns, mutation of both terminal metadata columns, v1 fail-closed, and an end-to-end check that the ordinary claim/finalize path still succeeds for both completed and failed outcomes. Focused: 114 passed, 1 platform skip. Full suite: 1347 passed, 5 environmental skips, no failures, no errors, no xfail, no xpass. No public API signature, reason-code vocabulary, evidence ordering, or authority boundary changed. This grants no execution or integration authority. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
finalize() collapsed every exception into TERMINAL_NOT_FOUND, so a locked, corrupt, or unsupported-schema store reported that the capability did not exist. Three materially different states were indistinguishable: capability genuinely absent claim store temporarily busy claim store unavailable or corrupt Only the first is a lifecycle fact. In the other two the lifecycle state was never observed at all, and reporting absence invites a caller to act on an absence it never saw -- for a foundation that later gates execution, that is the wrong default. The claim path already drew this distinction; finalization now meets the same standard. Adds two closed terminal reasons mirroring the claim-path values: TERMINAL_STORE_BUSY = "capability_store_busy" TERMINAL_STORE_UNAVAILABLE = "capability_store_unavailable" finalize() now maps CapabilitySchemaError and generic sqlite3/OS errors to TERMINAL_STORE_UNAVAILABLE, and sqlite3.OperationalError to TERMINAL_STORE_BUSY or TERMINAL_STORE_UNAVAILABLE via the existing _is_busy predicate. TERMINAL_NOT_FOUND is now reserved for a successful read that found no row. Six focused tests: - unknown capability in a healthy store -> capability_not_found; - held write lock -> capability_store_busy, with the claimed row asserted unchanged because nothing was observed; - corrupt database -> capability_store_unavailable; - unsupported schema version -> capability_store_unavailable; - finalize_capability() preserves each code exactly across the ledger boundary; - no failure path appends an execution_capability_terminal event. Four were confirmed to fail against the previous handler and pass now; the two genuine-absence cases pass either way and serve as controls. No API signature, schema, evidence ordering, or authority boundary changed. The vocabulary widens by two values, which is a closed-set addition, not a free-text reason. This grants no execution or integration authority. Focused: 120 passed, 1 platform skip. Full suite: 1353 passed, 5 environmental skips, no failures, no errors, no xfail, no xpass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Synchronizes the durable record with the two corrective commits, eaf432d and 63a053b. Documentation only; no code or test change. CR-YK-002 change request: - Storage Contract gains the enforcement the v2 schema actually provides: row shape bound to state by a table-level CHECK, a legal-transition whitelist, and immutability of claim ownership and terminal metadata at their proper lifecycle points. - The terminal vocabulary gains capability_store_busy and capability_store_unavailable, with the reason recorded: capability_not_found asserts a lifecycle fact, while a busy or unusable store never observed lifecycle state at all. - A new subsection records the v1 -> v2 migration posture, including what the v1 schema failed to enforce, why the bump is required rather than cosmetic, that v1 databases fail closed and are never migrated or repaired, and that no production claim database existed at the time of the bump. - Status and Implementation Record now cite all three implementation commits and the current verification counts: focused 120 passed / 1 platform skip, full suite 1353 passed / 5 environmental skips, CI green on 3.10, 3.11, and 3.12. Change log: the CR-YK-002 entry describes the v2 schema, the migration posture, and the terminal reason distinction. Backlog: status lines cite all three commits and PR #117, and the purpose entry notes the v2 enforcement and honest terminal reporting. The claim that CR-YK-002 is unmerged is unchanged and remains true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the CR-YK-002 standalone atomic-claiming foundation. Claim ownership and lifecycle state move into a local SQLite registry that takes
BEGIN IMMEDIATEbefore any decision is read; the task ledger remains the durable evidence history and is never the concurrency lock.Six commits, split to preserve provenance and to keep each correction separately reviewable:
27fd869authz.pyboundary, and their testse6fda4ae8f9249main(resolves one docs conflict; no implementation file conflicted)eaf432d63a053b371838dSource provenance
triage_core/capability_claims.pyandtests/test_capability_claims.pywere written earlier, left untracked, and lost — never committed to any ref and present in no stash. Both were reconstructed by replaying theWriteandEdittool calls from the authoring session transcript in order; all eight operations matched their anchors exactly once, with no fuzzy matching.Fidelity evidence differs by file and the two are not equivalent:
capability_claims.py__pycache__entry built from the originaltest_capability_claims.pyBoth files also carry exact SHA-256 continuity from reconstruction into the tree. That establishes faithful transfer, not fidelity to the lost originals — a separate claim.
Authority split
The v1 schema did not back the SQLite half of that split. Verified against it, direct SQL was permitted to skip
issued → completedandissued → failedpastclaimed; insert aclaimedrow with no owner, or a terminal row never claimed; and rewriteclaimant_id,execution_attempt_id, andclaimed_aton an already-claimed row — all whilePRAGMA integrity_checkreturnedok. The public API never performed those writes, so behavior was correct in practice, but enforcement lived in the caller.eaf432dcloses this at schema versiontriagecore.capability_claims.v2: a legal-transition whitelist, a table-levelCHECKbinding row shape to state, and triggers freezing claim ownership and terminal metadata at their proper lifecycle points.v1 databases fail closed as unsupported and are never migrated. The bump is required, not cosmetic —
CREATE TABLE IF NOT EXISTScannot retrofit a table-levelCHECK, so a v1 file would otherwise open, report healthy, and silently lack every guarantee. No production claim database existed at the time of the bump.Honest terminal reasons
finalize()previously collapsed every exception intocapability_not_found, making three different states indistinguishable: genuinely absent, store busy, store unavailable or corrupt. Only the first is a lifecycle fact.63a053baddscapability_store_busyandcapability_store_unavailableto the closed terminal vocabulary and reservescapability_not_foundfor a successful read that finds no row.Verification
tests/test_capability_claims.py,tests/test_authz.py): 120 passed, 1 platform skip.xfail, 0xpass.xfailis replaced by passing atomicity tests. Barrier-synchronised real threads, each with its ownTaskLedgerhandle and an independent SQLite connection per operation, produce exactly one committed claim at 2 and at 8 concurrent claimers. Cross-process contention is not exercised.git diff --checkpasses for tracked and untracked content.triage_core/authz.pyis the only non-test module importing the claim store.Regression evidence, not just green tests. The 11 schema-constraint tests were confirmed to fail against v1 and pass against v2. Four of the six terminal-reason tests were confirmed to fail against the previous handler; the two genuine-absence cases pass either way and serve as controls.
Acceptance-contract status
Nine of eleven items verified. Two partially verified, both evidentiary rather than behavioral:
assert_persistent_privacy_safecalls, not by the persistent audit.Limitations, stated deliberately
PRAGMA integrity_checkis a logical-consistency check, not a checksum. A splice into unused page space is not detected. This is not a tamper-evidence claim.consume_capabilitystill collapses artifact, scope, and binding mismatches into one legacy reason;claim_capabilityretains the precise value.Authority boundary
No execution or integration authority. No
tc authzCLI surface,tc runintegration,--confirmed-plan, backend or model invocation, routing, worker, or FIDO2 change. Pre-CR-YK-002 capability events are permanently unclaimable and are never migrated into authority. CR-DD-012B remains unauthorized and blocked.Note for reviewers
The docs cite implementation commits by SHA, so a rebase or squash would leave those provenance references dangling. Prefer a merge commit.
🤖 Generated with Claude Code