Skip to content

feat(authz): CR-YK-002 atomic capability claiming - #117

Merged
coreytshaffer merged 6 commits into
mainfrom
cr-yk-002-atomic-capability-claiming
Jul 28, 2026
Merged

coreytshaffer merged 6 commits into
mainfrom
cr-yk-002-atomic-capability-claiming

Conversation

@coreytshaffer

@coreytshaffer coreytshaffer commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

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.

Six commits, split to preserve provenance and to keep each correction separately reviewable:

Commit What
27fd869 Foundation — the claim registry, the authz.py boundary, and their tests
e6fda4a Documentation status, correcting a previously inaccurate "implemented on branch" claim
e8f9249 Merge main (resolves one docs conflict; no implementation file conflicted)
eaf432d Schema hardening to v2 — makes SQLite enforce the lifecycle it documents
63a053b Honest terminal store-failure reasons
371838d Documentation for the two corrections above

Source provenance

triage_core/capability_claims.py and tests/test_capability_claims.py were written earlier, left untracked, and lost — never committed to any ref and present 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 by file and the two are not equivalent:

File Evidence Strength
capability_claims.py Compiles to bytecode byte-identical to the surviving __pycache__ entry built from the original Direct
test_capability_claims.py Source byte length recorded in the pyc header matched (25735); its only caches are pytest assertion-rewritten, so no bytecode comparison was possible Indirect

Both 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

Python boundary:  ledger lookup, issuance validation, evidence ordering
SQLite:           atomic ownership, legal lifecycle, row-shape consistency,
                  immutable claim ownership, immutable terminal state

The v1 schema did not back the SQLite half of that split. Verified against it, direct SQL was permitted to skip issued → completed and issued → failed past claimed; insert a claimed row with no owner, or a terminal row never claimed; and rewrite claimant_id, execution_attempt_id, and claimed_at on an already-claimed row — all while PRAGMA integrity_check returned ok. The public API never performed those writes, so behavior was correct in practice, but enforcement lived in the caller.

eaf432d closes this at schema version triagecore.capability_claims.v2: a legal-transition whitelist, a table-level CHECK binding 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 EXISTS cannot retrofit a table-level CHECK, 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 into capability_not_found, making three different states indistinguishable: genuinely absent, store busy, store unavailable or corrupt. Only the first is a lifecycle fact. 63a053b adds capability_store_busy and capability_store_unavailable to the closed terminal vocabulary and reserves capability_not_found for a successful read that finds no row.

Verification

  • Focused (tests/test_capability_claims.py, tests/test_authz.py): 120 passed, 1 platform skip.
  • Full suite: 1353 passed, 5 environmental skips, 0 failures, 0 errors, 0 xfail, 0 xpass.
  • CI: green on Python 3.10, 3.11, and 3.12.
  • 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. Cross-process contention is not exercised.
  • git diff --check passes for tracked and untracked content.
  • triage_core/authz.py is 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:

  • Human implementation approval is operator-attested; there is no cryptographic approval artifact.
  • The persistent privacy-invariant audit passes over 698 records, but that ledger contains zero CR-YK-002 events. New payload privacy is covered by focused persistence tests and direct assert_persistent_privacy_safe calls, not by the persistent audit.

Limitations, stated deliberately

  • PRAGMA integrity_check is a logical-consistency check, not a checksum. A splice into unused page space is not detected. This is not a tamper-evidence claim.
  • Local filesystems only. No claim about locking semantics over NFS, SMB, cloud-synced folders, or unusual FUSE mounts.
  • A crash, or a ledger-write failure after the SQLite commit, burns the authorization rather than risking duplicate execution. This intentionally creates an unusable capability and may leave an evidence gap.
  • consume_capability still collapses artifact, scope, and binding mismatches into one legacy reason; claim_capability retains the precise value.

Authority boundary

No execution or integration authority. No tc authz CLI surface, tc run integration, --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

coreytshaffer and others added 2 commits July 27, 2026 22:26
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>
@netlify

netlify Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for poetic-quokka-0fd859 ready!

Name Link
🔨 Latest commit 371838d
🔍 Latest deploy log https://app.netlify.com/projects/poetic-quokka-0fd859/deploys/6a68761fb62d5500088bffd6
😎 Deploy Preview https://deploy-preview-117--poetic-quokka-0fd859.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

coreytshaffer and others added 4 commits July 28, 2026 01:53
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>
@coreytshaffer
coreytshaffer merged commit 5155bbb into main Jul 28, 2026
7 checks passed
coreytshaffer added a commit that referenced this pull request Jul 28, 2026
docs(change): close out CR-YK-002 as merged

Records the outcome of PR #117 (5155bbb). Documentation only.

CR-DD-012B's atomic-claiming prerequisite is satisfied; CR-DD-012B itself
remains blocked pending its own explicit approval.
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