Skip to content

fix(protocol): decide a moot claim from the record, not the label - #167

Merged
rafael-adcp merged 3 commits into
mainfrom
fix/moot-claim-reads-record
Aug 1, 2026
Merged

fix(protocol): decide a moot claim from the record, not the label#167
rafael-adcp merged 3 commits into
mainfrom
fix/moot-claim-reads-record

Conversation

@rafael-adcp

Copy link
Copy Markdown
Owner

Three commits on fix/moot-claim-reads-record. The first changes behavior, the
second changes none, and the third is unrelated to either — see the note at the
end.

decb72f — the moot predicate reads the record (§3.1)

claim_is_moot and issue_is_finished readat

decb72f — the moot predicate reads the record (§3.1)

claim_is_moot and issue_is_finished readat
disagreed with §6's rule 1, which has asked the record since protocol/9:

a delivered task the operator requeued by eld**
by its stale badge and as free by its record.

The worker called it resolved and dropped a task the queue had just handed back,
leaving nobody holding it. Two answers to "did the task leave my claim" is one
too many.

Both predicates now go through holding_state, which is §3.1 itself — where a
record exists it decides, and only a task wis badge.
Reusing the predicate that already backs `Tas cheap:
a legacy no-record queue behaves exactly as it did before.

Cost

open_claim_of needed a record it did not have, and that path is the one with no
GraphQL call in it. Rather than add records mit read
(which would put a commit read on the one path that had none), lease_view now
also returns the state shas the matching-refs read already carried — a pure
parse of a payload in hand, costing no requethe
single record it turns out to need via States.at. A worker holding nothing pays
nothing, and §5.3's "no GraphQL on a named claim" bound still holds.

Fail direction

TaskState.requeued reads an unreadable coming the
direction _comment_total already floors a lost GraphQL field to 0. Offering a
task whose worker then finds nothing to do escalates (§6); the other direction
buries a delivery the operator asked to reop

Module split

Terminal transitions (escalate / deliver / release) moved out of claim.py into
terminal.py. claim.py is about taking a task and holding it, terminal.py about
putting it down. Neither imports the other —
comments.py, which both already depend on. claim.py 761 → 603 lines.

16f8c53 — table-drive the CLI, cut pros

build_parser was 165 lines of repeated add_parser/add_argument sequences —
a table written as procedure, and the largese 16
subcommands are now a COMMANDS tuple and the function is a 16-line walk over
it. Positionals are a signature string ("repo issue worker result_file pr_url?",
a trailing ? marking one that may be omittying
argparse keywords of their own (reap worker, contract field) ride opts.

**No behavior change, and not by inspection.gparse
surface — option strings, dest, nargs, defauaction
class and help — was compared against the pr

IDENTICAL — 16 subcommands, full argparse su

The prose cuts are deliberately narrow. Most package
describe ref shapes the code still accepts and stay, because they are the only
explanation of why those branches exist. What went was autobiography already
written down in HISTORY.md — state.py carrearly
verbatim, and PROTOCOL_VERSION carried a paragraph restating the spec it points
at. Roughly 27 lines across seven modules.

The package stays about 45% prose. That is not slack: the invariants it records
(a projection is never read by a decision; which absence fails open) are ones
neither the type system nor a unit test can

630396b — drop the pre-push semantic-review hook

Unrelated to the two commits above. The udeCLI to review every push touchingskills/**orREADME.md, and ran the agent behavior suite as a second stage — real model runs that spend tokens, on a path where a contributor expects a mechanical cheagent suite is wired into no hook and no CI, and make test-agent` runs it by hand.

Happy to split this into its own PR if it mu

Verification

make check on the branch HEAD:

  • 269 unit tests OK
  • 130 conformance tests OK
  • skill-lint OK

New coverage: ClaimIsMootTests (6 cases) and 5 cases in ResumeVerdictTests,
pinning that a requeued delivery is not moot, that a stale badge the record
contradicts is not moot, that a task with nots
badge, and that in-progress is never moot. Verified these actually pin the
change by restoring the old label-reading predicate: 3 fail, and the 2
backward-compatibility cases pass on both si

tests/COVERAGE.md §3 was corrected — it claimed the write-only clause was
pinned when it was pinned only for the startable filter and the console, not for
claim_is_moot/issue_is_finished.

🤖 Generated with Claude Code

rafael-adcp and others added 3 commits August 1, 2026 19:47
`claim_is_moot` and `issue_is_finished` read the held labels outright. That
disagreed with §6's rule 1, which has asked the record since protocol/9: a
delivered task the operator requeued by commenting reads as still-held by its
stale badge and as free by its record. The worker called it resolved and
dropped a task the queue had just handed back, with nobody left holding it.

Both now go through `holding_state`, which is §3.1 itself — where a record
exists it decides, and only a task with no record falls back to its badge.
Reusing the predicate that already backs `Task.holding` is what makes this
free: a legacy no-record queue behaves exactly as it did before.

`open_claim_of` needed one record it did not have. `lease_view` now also
returns the state shas the matching-refs read already carried — a pure parse
of a payload in hand, no request — and the caller resolves the single record
it turns out to need via `States.at`. A worker holding nothing still pays
nothing, and §5.3's "no GraphQL on a named claim" bound stands.

`TaskState.requeued` reads an unreadable comment total as requeued, the same
fail-open direction `_comment_total` already floors a lost GraphQL field to 0:
offering a task whose worker then finds nothing to do escalates (§6), while
the other direction buries a delivery the operator asked to reopen.

Also splits the terminal transitions (escalate / deliver / release) out of
`claim.py` into `terminal.py`. claim.py is about taking a task and holding it,
terminal.py about putting it down; neither imports the other, since
`read_body_file` moved to `comments.py`, which both already depend on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`build_parser` was 165 lines of repeated `add_parser`/`add_argument` sequences
— a table written as procedure, and not by accident the largest function in the
package. The 16 subcommands are now a `COMMANDS` tuple of `Command` records and
the function is a 16-line walk over it. Positionals are a signature string
(`"repo issue worker result_file pr_url?"`, a trailing `?` marking one that may
be omitted); the two positionals carrying argparse keywords of their own
(`reap worker`, `contract field`) ride `opts` alongside the flags.

No behavior change, and not by inspection: every subcommand's full argparse
surface — option strings, dest, nargs, default, required, choices, type, action
class and help — was compared against the previous parser and matches exactly.

The prose cuts are deliberately narrow. Most `protocol/N` mentions in the
package describe ref shapes the code still accepts and stay, because they are
the only explanation of why those branches exist. What went was autobiography
already written down in HISTORY.md: `state.py` carried 26 lines restating §3.1
nearly verbatim, and `PROTOCOL_VERSION` carried a paragraph restating the spec
it points at. Roughly 27 lines across seven modules.

The package stays about 45% prose. That is not slack — the invariants it
records (a projection is never read by a decision; which absence fails open)
are ones neither the type system nor a unit test can hold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hook asked the logged-in `claude` CLI to review every push touching
`skills/**` or `README.md`, and ran the agent behavior suite as a second stage.
Both are real model runs that spend tokens, on a path where a contributor
expects a mechanical check.

Nothing replaces it. The agent suite is now wired into no hook and no CI —
`make test-agent` runs it by hand when `skills/` or `tests/agent/` change. The
docs that described the hook and the `.githooks/*` LF rule in `.gitattributes`
go with it.
@rafael-adcp
rafael-adcp merged commit 497d681 into main Aug 1, 2026
2 checks passed
@rafael-adcp
rafael-adcp deleted the fix/moot-claim-reads-record branch August 1, 2026 23:20
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