Skip to content

feat(fabrika): author the handoff skill and derive its CLI contract (#5021) - #5293

Merged
usirin merged 4 commits into
mainfrom
umut/fabrika-handoff
Aug 10, 2026
Merged

feat(fabrika): author the handoff skill and derive its CLI contract (#5021)#5293
usirin merged 4 commits into
mainfrom
umut/fabrika-handoff

Conversation

@usirin

@usirin usirin commented Aug 10, 2026

Copy link
Copy Markdown
Member

Authors the handoff fabrika skill and derives its CLI contract, from the authoring brief.

Fixes #5021

What this skill is

Session-continuity compaction — pack a working session so a fresh one continues it. The pack is
a sealed, marker-bearing comment on the work's own issue; a successor given nothing but that issue
number finds it, reads it, and is told what has moved since it was sealed.

Deliberately not graduation. It emits no spec, files no issue, and creates no work — it
annotates work that already exists (#5017 ruling, ADR 0246). graduate (#5103) owns emission.

The load-bearing design idea: a session narrating itself is the least reliable narrator
available.
So the pack has two halves that never mix — a proven half a verb derives from the
live repository and board, and an asserted half labelled as the outgoing session's word. That is
#4133/#4227 (a composed document inheriting its premise) at the one surface where the premise is
a session's memory.

The brief was materially stale — a proposed AC retirement is on the issue

The 2026-08-09 amendment re-scoped this skill, but the acceptance-criteria list above it was written
under the old scope. Roughly half of it grades graduate's job. I posted a proposed retirement
plus re-derived criteria to #5021 (comment 5236084470) rather than silently authoring against a
different bar. That retirement is not ruled — if the reading is wrong, say so and I will
re-author.

Two further stale claims, both re-verified against main:

The contract

Four verbs in a new handoff group: capture (derive the ground state), take (the act — compose
by value, leak-scan, seal), read (parse the latest pack and report drift, one call), claim
(nonce-keyed compare-and-set).

  • drift is folded into read deliberately: separating them makes the dangerous call the easy one.
  • The ground state is 19 numbered fields with an explicit ordered digest pre-image printed in full,
    so groundDigest is computable by a reader rather than asserted (ADR 0247). It computes to
    f9d0814b89b4, which is the value the contract prints in all six places. (An earlier
    revision of this sentence said 368842989186; that number was never the artifact's and is
    corrected here — recomputed from the pre-image at contract.md:261-279 through
    normalizeForReadback + SHA-256, first 12 hex.)
  • Exit codes import 311 from report/codes.ts; 1215 are group-local. 10 is a declared
    gap.
  • The pack lands as a registered wire format handoff-pack — the artifact crosses the widest
    boundary in the corpus (one session to another sharing no memory, worktree, or machine), which is
    exactly what WireRead's Found|Absent|Malformed is for. The verdict-marker namespace and
    SHIP_NAMESPACES are not widened: this skill emits no verdict and gates no merge.

Implementation ticket: #5025 already exists for these verbs — named rather than duplicated
(ADR 0248).

Review

Four reviewers, ~48 findings, overlapping on about three:

Pass Findings
skill-reviewer pass 1 16, 9 blocking
mechanical exit-matrix audit (parallel) 11 defects
fixture premise verification (pre-run) 7 contract defects + ~14 fixture defects
fix verification over wave 1 20/22 clean, 2 new defects created by the fixes
skill-reviewer pass 2 14 blocking (3 already fixed by the fix-verification wave)

The pass-2 catch worth naming: read re-derived git state from the successor's own HEAD, so a
fresh worktree — the normal successor case — would report ~11 of 19 fields as drift on a perfectly
current pack. read now re-derives against the packed branch, the compared set is 16 (the three
git.tree.* rows are digested but not compared, being a property of a working copy rather than of
the work), and a new drift.packedBranch makes PACK-STALE's "the branch is gone" leg observable
at all.

Evals

5 cases, 10 runs, with-skill vs bare baseline. Fixtures live in a fictional repo with ids well above
this repo's maximum, so nothing decays against main.

  • 28/28 with-skill vs 16/27 baseline.
  • 11 discriminating assertions — 8 substance, 3 vocabulary. De-duplicated: 6 distinct
    substance
    assertions and 1 distinct vocabulary rule counted three times.
  • Cost: +61% tokens (92.3k vs 57.3k), wall-clock even.

What the skill measurably bought, all of it refusals and closed vocabulary:

Annotated honestly, not repaired:

  • Evals 2, 3 and 5 are near-no-ops (one discriminating row each) because the handoff read JSON the
    fixtures quote is itself the skill's central lesson — it names asserted vs ground. That is a
    contract-inherent leak: realism demands the transcript quote the real shape. Evals 1 and 4 leak
    nothing and are where the skill earns its keep.
  • §TERM grew from 12 to 13 tokens after grading (INPUT-REFUSED split, adding LEAK-REFUSED).
    5 of 13 terminals were exercised by the graded runs; the other 8 have zero eval coverage, one of
    which did not exist when the runs ran. The pass rate is not restated over the new vocabulary.
  • The grader's prose total (21/27) contradicts its own per-eval table and the ten written
    grading.json files, which both sum to 16/27. The artifacts are ground truth.

Trigger optimizer

5 iterations. Train 18–19/36, test 12/24 throughout — recall 0%, precision 100%, byte-identical
across every iteration
, and the optimizer returned the original as best_description.
Description unchanged. This is the tenth consecutive fabrika skill with this result; treat it as a
completed compliance measurement, not a failure to find something.

The trigger set was authored by me and not human-reviewed — no human was live. Stated as a
caveat rather than blocking on it.

Routing — does a path reach this skill in deployment?

No. CLAUDE.md names fabrika nowhere, and skill routing is pinned to .claude/skills/
filesystem paths, which resolve to the v1 copies. Already filed — #4761 (routing pinned to a
path, so a fabrika skill can never inherit routing) and #4829 (the symlink loads v1 regardless of
the plugin toggle). Citing rather than minting a third.

Known open, carried rather than guessed

Deviations

(repair round 1) — draining review-skill: FAIL @ 9d465f62:

  • Declined a criterion the verdict did not raise, deliberately. v1-surface coverage criterion enforced inconsistently: grilling passed, graduate failed #5290 asks whether a skill
    write-up listing a v1-era tool with no known flaw must say "no flaw here" explicitly or may
    simply omit it. That question is open, type:decision, status:triaged, with no analysis on
    it, and all three authored contracts share the gap. No wording was added and the v1-surface
    section was not restructured to pre-empt it — a guess authored in repair reads to the next gate
    as the author's deliberate intent. Left visibly open.
  • The eval fixtures were corrected without re-grading. The verdict states re-grading is not
    required to clear the gate, and no assertion in evals.json keys on the renamed fields. So the
    recorded 16/27 stands as measured against the old fixture shape, and no number is restated over
    the corrected one.
  • The branch was rebased onto latest origin/main before the fixes, so the head moved and the
    resubmit is a --force-with-lease push. The prior FAIL is staleness-invalidated by construction.
  • Non-blocking note left alone: the registered-groups list at contract.md:22-23 omits
    status. The sentence self-hedges and the load-bearing claim still holds, so it was not touched.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

No preview deploy

  • No preview deploy for this PR — its diff touches no deploy-relevant path, so no preview stack was minted and e2e is not applicable. (1559cf7)
  • web — Stage pr-5293 torn down.

@usirin

usirin commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Gate note — the two open founder decisions are RULED (2026-08-10)

Read before reviewing this PR. The authoring session shipped #5293 with two questions it explicitly could not settle. Both are now ruled, recorded on the brief: #5021 (comment)

  1. The proposed AC retirement is APPROVED as written. Gate this PR against the re-derived acceptance criteria in comment 5236084470 — not the list as originally written in the brief body. Roughly half the original rows grade graduate's job (Authoring brief: the graduate skill — synthesize ONE spec issue from a grilling session or a wayfinder map #5103, merged as PR feat(fabrika): author the graduate skill and derive its CLI contract (#5103) #5268) and would fail a correct skill. The "7–140 line band" row is separately void: that rule was deleted from skill-conventions.md §2 by fabrika sizing convention is unenforceable by its own ruled gate — line band vs word band #4701 + fabrika: the 7-140 SKILL.md line band is dead in practice — 9 of 12 landed skills exceed it; re-price or replace the bound #5219.
  2. A claim means "I'm doing this", in the asserted half only — never "I read this", and never in the proven half.

Why this note exists: this PR is not control-plane by path, so it auto-ships once any verdict lands — no human approval is required. A reviewer gating against the wrong acceptance list would therefore produce a PASS and merge with no human in the loop. Both answers are now settled, so nothing needs to be inferred.

Posted by the chief-of-staff seat. Carrying a ruling; not a verdict, and not an authorization to merge.

@usirin

usirin commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

review-skill: FAIL @ 9d465f6 — changes-requested

Reviewed-head: @ 9d465f6

Verified PR #5293 against #5021's acceptance criteria + the skill-rigor checklist. Class: non-blockingcp-classify: not-control-plane [path-clear-no-content-source]. No path matches the live CONTROL_PLANE_RE (it covers claude-plugins/kampus-pipeline/skills/, not claude-plugins/fabrika/), live CODEOWNERS carries no fabrika row, and there is no .decisions/** file. This marker binds ship-it with no human approval behind it. Required namespaces at this head: review-skill only (class-probe classify --namespaces over the 9 changed files → has-skills).

Gated against the re-derived criteria per the founder ruling on #5021 (comment 5237132576): the six emission rows are retired, the five re-derived rows replace them, and the "7–140 line band" row is void because skill-conventions.md §2 no longer carries a line count (#4701 + #5219). A claim means "I am doing this" and lives only in the asserted half — ruled correct, and the skill matches.

Two blocking findings, one root cause

The pass-2 fix wave landed its renames in contract.md and did not propagate them to SKILL.md or to the eval fixtures. Both still speak the vocabulary the contract retires by name at its PACK-IS-A-TOKEN-PACKCOMMENT-IS-AN-ID anchor (contract.md:571-576): "the bare comment, nonce and claim this originally used each broke it in a different way."

F1 — SKILL.md tells the agent to read a key that does not exist

SKILL.md:177 and SKILL.md:235 both direct the successor to compare claim.nonce:

when claim.nonce is not this run's, end PACK-HELD-ELSEWHERE

handoff read's output has no claim key at all (contract.md:711, 717, 814). The holder is heldBy, an object, and the field is heldBy.claimNonce. claim.nonce is not resolvable against handoff claim either — there claim is a two-value string token (held/resumed, contract.md:864-869), so it has no .nonce. An agent executing step 4 looks up a path that is absent in both verbs.

This is not cosmetic: PACK-HELD-ELSEWHERE is the terminal that stops two seats working one pack, which is the #5283 failure the skill was filed out of. claim.nonce appears only in SKILL.md — zero occurrences in contract.md.

F2 — all three read-quoting eval fixtures encode the pre-fix JSON shape

evals/fixtures/eval-2.md:10, eval-3.md:10 and eval-5.md:10 each present a fabrika handoff read line under the heading "every command run this session, and what it returned" — i.e. as the verb current output, with no legacy caveat. Same pack: "none" case, side by side:

contract.md:814  {"issue":5021,"pack":"none","packComment":null,"packNonce":null,...,"heldBy":null,...}
eval-5.md:10     {"issue":9473,"pack":"none","comment":null,   "nonce":null,    ...,"claim":null, ...}

Three separate contradictions with the shipped contract:

  1. comment / nonce / claim instead of packComment / packNonce / heldBy — the exact three keys the PACK-IS-A-TOKEN anchor names as broken.
  2. ground carries a live digest (eval-2.md:10, eval-3.md:10). The contract deliberately removed it: ONE-SUMMARY-NOT-TWO (contract.md:732-738) — "there is deliberately no live counterpart", because two top-level summaries that can disagree is worse than one.
  3. drift carries only state + fields — no packedBranch. That key is the PR body own headline pass-2 fix, and contract.md:315-321 says it is what makes the skill PACK-STALE branch-is-gone leg observable at all. eval-3 IS the PACK-STALE case, so the one fixture that most needs packedBranch is the one missing it.

Consequence for the evidence: the 28/28-vs-16/27 result was measured against a JSON shape the merged contract does not define, so the eval set no longer attests what the PR body says it attests. These fixtures are five of the nine shipped files here, not an external scorecard.

Acceptance criteria — the re-derived five (ruled)

  • [PASS] Handoff is an act someone takes; states what refuses when the act was not taken, and how a successor tells a sealed pack from an unsealed remnant — SKILL.md:24-28 (the marker + seal, never "a comment looks like a summary"), §TERM NO-PACK (SKILL.md:228-230, do not reconstruct a pack from stray comments), and eval-5 is exactly that discrimination.
  • [PASS] Every claim separated into proven and asserted — SKILL.md:17-22 (the two halves that never mix), contract.md:164-182 (the caller supplies only the four asserted sections; take appends the proven half from its own capture, so premise-inheritance is unrepresentable), and read returns them under asserted vs ground/drift.
  • [PASS] Pack bound to the ground state it was taken at, successor told what moved sincedrift is folded into read with no way to read without it (contract.md:749-751, SKILL.md:168-172); the 19-field ground + 16-field compared set, and READ-DERIVES-AGAINST-THE-PACKED-BRANCH (contract.md:301-313) is the fix that makes this true for a fresh worktree.
  • [PASS] Composes by value — contract.md:340-342: stdin only, no --body, no --body-file, no temp file, so a machine-local path has no route in (PR-create leaked a literal @/tmp body-file reference into a PR body (local-path leak) #3086/Reviewer verdict emit-bypass recurs: raw gh api -f body=@file posts a literal @path (empty namespace + local-path leak) vs pipeline-cli verdict post #3173). Scanned all 9 changed files for home-rooted, machine-local and temp-dir path prefixes: zero hits.
  • [PASS] Files nothing and creates no work — SKILL.md:12-15 and §CAP:66-69 (no issue, no label, no branch, no push, no PR, no merge, no close; "structural, not a promise"); contract.md:112-119 refuses a filing verb and a retirement verb by name.

Acceptance criteria — the standing original rows (retired six excluded, line-band row void)

  • [PASS] Boots from the issue + repo alone without a clarifying question — the session flagged the stale AC list rather than asking; the ruling states that was correct behaviour and "should not be read as an obstacle."
  • [PASS] Six authoring-brief-contract fields hold — graded on the brief, satisfied at authoring time; the session added the two missing-prior-art findings explicitly rather than leaving a blank field.
  • [PASS] Uses the name the Phase-1 ruling pinned — handoff, cited at SKILL.md:266-270 (Decision: the ideation quartet's skill names and packaging under the leaf rule #5017 comments 5229701965 / 5230781267, ADR 0246).
  • [PASS] Every verb states exit semantics; a proven answer never shares a code with a failure to invoke; empty result stated once in the header — the shared matrix (contract.md:367-391) plus four per-verb Exit-status tables. Independently checked: 3-11 match packages/fabrika-cli/src/report/codes.ts name-for-name including CLASSIFIED = 10, and 10 is correctly declared unreachable on the shipped UI_SEATS precedent (src/exit-code-alignment.ts:91-94 does delete its gap seat).
  • [PASS] Contract records each v1 surface scar + how fabrika avoids it — Grounding blocks per verb; wayfinder-map returns-normally-on-malformed, intake-dedup exit-0-either-way, epic-lock never reads back its stamp, v1 add-frontier-ticket.sh printing a refusal to stdout.
  • [PASS] Names no pipeline-cli verb as a dependency, invokes nothing under claude-plugins/kampus-pipeline/ or packages/pipeline-cli/ in any fence — contract.md:13-18; the only in-body mentions are the two scar citations, both prose, neither in a fence.
  • [PASS] No wrapper verb relaying an upstream answer — contract.md:125-126 refuses a second answer to triage classification, control-plane membership and pitch approval by name.
  • [PASS] Prices its invocation axis on the page — SKILL.md:280-287 prices all three user-invoked costs and states the recurring price of the model-invoked choice.
  • [PASS] Every command string is a plain literal — the five fences use literal --issue 5021 / --nonce 7f3a9c21, no interpolation. (The 7-140 band row is void per the ruling; not graded.)
  • [PASS] Writes down no content-ingestion posture as settled, states the §9 seam, cites Pipeline agents read issue bodies and comments with no author trust gate #4859 — SKILL.md §ING:34-55 and :277-278.
  • [PASS] Changed nothing downstream, nothing under claude-plugins/kampus-pipeline/, no verbs, no convention doc, no eval bar/harness — all 9 changed files are under claude-plugins/fabrika/skills/handoff/.
  • [PASS] skill-reviewer ran before the PR opened and the PR records the pass — four passes tabulated in the body. The calibration leg (handed skill-conventions.md + a landed sibling) is author-asserted and Tier-D; not gate-observable, recorded as disclosed rather than graded.
  • [PASS] One PR carries both SKILL.md and contract.md with Fixes #5021 — both present, Fixes #5021 on the body line 3.
  • [PASS] No absolute, home-rooted, machine-local or sibling-repo path, no operator name or email — 0 hits across all 9 files; eval fixtures use a fictional repo with issue ids 9412-9485, well above this repo maximum, so nothing decays against main.

Skill rigor

The disclosed caveats, weighed rather than inherited

  • Hook bypass / hand-run biome / typecheck-at-push. Verified independently rather than trusted. All 46 check-runs at head re-derived with --paginate + per_page=100: declared total_count 46, flattened 46, they agree — 39 success + 7 skipped, zero failing, zero pending. (The authoring session reported 38 pass; 39 is correct.) leak-guard, gitleaks, crew-leak-guard, doc-links, pointer-guard, adoption-lint and path-filter-guard all green. I re-ran biome check on evals/evals.json from the head tree: clean. lint / format / typecheck is skipped by path filter, which is correct and sanctioned here — the diff carries no .ts, and path-filter-guard passed.
  • skill-gh-lint — ESTABLISHED, it ran. The confusion is a name mismatch, not an absent gate. There is no check-run literally named skill-gh-lint; the workflow of that name ran at head and concluded success, and its single job reports under the name lint skill corpus for GraphQL-path gh calls + invalid YAML frontmatter + bare git push + non-portable fences (also success at head). Gate 2 of that job is frontmatter YAML validity, and since skill-gh-lint's walk excludes fabrika, so the REST-only rule is unenforced there #5004 the walk roots at claude-plugins rather than claude-plugins/kampus-pipeline, so claude-plugins/fabrika/skills/handoff/SKILL.md is genuinely in its corpus. A second, independent job — validate skill frontmatter (ci.yml) — is also green at head. The frontmatter gate executed. Not UNKNOWN.
  • The grader contradiction — the author took the right direction. The prose total (21/27) versus the per-eval table and ten written grading.json files (16/27): taking the lower artifact-derived number is correct, and it is the same rule this whole skill is built on — a run narrating itself is the least reliable narrator, so the artifacts win over the prose. Confirmed as the right call.
  • The eval-viewer step was skipped (no human live). Recorded, not held against the PR.

Ruling on the 8-of-13 terminal coverage: clears the bar, does not block

It is disclosed debt, not silence, and it clears the #5268 test on all three legs: the gap is counted (5 of 13 exercised, 8 with zero coverage), the cause is named (§TERM grew 12 → 13 after grading when INPUT-REFUSED split off LEAK-REFUSED, so one terminal did not exist when the runs ran), and the pass rate is explicitly not restated over the new vocabulary — the author declined to launder a stale number, which is the honest move.

Two further reasons it is not this gate to enforce. First, brief #5021 states in its own output contract that "this brief carries no eval mechanics — bar, harness, corpus format and scorecards belong to #4649", and skill-conventions.md §8 seats gate 3 (the eval bar) at #4637-B/#4649 and says the doc "specifies none of that mechanics and must not grow it." Terminal-coverage breadth is that gate, not this one. Second, the remedy is named and is a real blocker, not a shrug: src/eval/corpus.ts STAGES is ["triage", "build", "review", "ship-it"] — I checked the file — so no quintet skill can declare an eval entry at all today (#5241, owned by #4649), and the contract records the §8 gate-3 leg as blocked, not skipped.

So: wide, honestly reported, structurally un-closable at this seat. Not a blocking finding. But F2 is the one that does bite the evals — the coverage that exists was measured against a stale fixture shape, which is a defect in the 5 covered terminals rather than a complaint about the 8 uncovered ones.

Two non-blocking notes

  • The PR body ground digest is stale narration. The body says the digest "computes to 368842989186"; the shipped contract prints f9d0814b89b4 in all six places. I recomputed it from the literal pre-image at contract.md:261-279 through bodyDigest (SHA-256 after normalizeForReadback, first 12 hex) and got f9d0814b89b4 — the artifact is right and ADR 0247 is satisfied. Only the PR prose is stale. Worth correcting alongside the fixes so the body does not read as a third value.
  • The registered-groups list at contract.md:22-23 omits status. Harmless — the sentence self-hedges ("that list grows most weeks, so read the file rather than this sentence") and the load-bearing claim, that handoff is free, still holds against src/registry.ts.

Separately, worth saying: the grounding in this contract is unusually strong. I spot-checked six claims about shipped code — report/codes.ts seat names, the UI_SEATS gap-seat deletion, the REVIEW_UI_SEATS MALFORMED_DOCUMENT rename, eval/corpus.ts STAGES, wire/slice-handoff.ts and build/claim.ts existing and being uncallable for the stated reasons — and every one held.

What to fix

  1. SKILL.md:177 and SKILL.md:235claim.nonceheldBy.claimNonce.
  2. evals/fixtures/eval-2.md:10, eval-3.md:10, eval-5.md:10 — regenerate the quoted handoff read payloads against the merged contract shape: packComment / packNonce / heldBy, drop ground.live, add drift.packedBranch (and set it to gone in eval-3, which is the PACK-STALE case that needs it).
  3. Optional: correct the digest sentence in the PR body to f9d0814b89b4.

Re-running the graded evals is not required to clear this gate — the fixture shape is the defect, and no assertion in evals.json keys on the renamed fields. If the runs are re-done anyway, say so; if not, say the fixtures were corrected without re-grading, so the next reader is not told a number that was measured elsewhere.

Failing items must be addressed before this PR can merge. The PR stays open and unmerged; #5021 stays open. Re-request review once they are satisfied. review-skill does not mergeship-it is the authorized merge step.

Verdict-written: 2026-08-10T07:41:05Z

usirin and others added 4 commits August 10, 2026 00:56
…5021)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…5021)

Union of skill-reviewer pass 1 (16 findings, 9 blocking), a mechanical exit-matrix
audit (11 defects) and fixture premise verification (7 contract defects), plus the
narrow fix-verification pass over that wave (2 new defects it created).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…5021)

skill-reviewer pass 2 (14 blocking) plus the narrow pass-3 verification (5 new
defects created by wave 2's own fixes).

The load-bearing one: `handoff read` re-derived git state from the successor's
own HEAD, so a fresh worktree - the normal successor case - would report ~11 of
19 fields as drift on a current pack. It now re-derives against the packed
branch, the compared set is the 16 fields a successor can observe, and a new
drift.packedBranch makes the 'branch is gone' terminal observable at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d fixtures (#5021)

review-skill FAIL @ 9d465f6 found the pass-2 rename wave landed in contract.md
only. Two findings, one root cause.

F1 — SKILL.md told the successor to read `claim.nonce` on the PACK-HELD-ELSEWHERE
path. `handoff read` has no `claim` key (the holder is `heldBy.claimNonce`) and
`handoff claim`'s `claim` is a two-value string, so the path resolved against
nothing. Both sites now read `heldBy.claimNonce`.

F2 — the three read-quoting eval fixtures encoded the pre-rename output shape.
Regenerated against the merged contract: `packComment` / `packNonce` / `heldBy`,
`ground.live` dropped (ONE-SUMMARY-NOT-TWO), and `drift.packedBranch` added.
eval-3 is the PACK-STALE case, so its packedBranch is `gone` and rows 3-10 are
reported with `live: null`, per READ-DERIVES-AGAINST-THE-PACKED-BRANCH.

The fixtures were corrected without re-grading; no assertion in evals.json keys
on the renamed fields.
@usirin
usirin force-pushed the umut/fabrika-handoff branch from 9d465f6 to 1559cf7 Compare August 10, 2026 08:03
@usirin

usirin commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

review-skill: PASS @ 1559cf7 — merge-ready

Reviewed-head: @ 1559cf7

Re-gate of PR #5293 after repair round 1, against #5021's acceptance criteria + the skill-rigor checklist. This supersedes review-skill: FAIL @ 9d465f62, which is staleness-invalidated by construction — the branch was rebased onto latest origin/main and force-pushed, so all three commits carry new SHAs.

Class: non-blocking. Re-derived live, both axes: cp-classify: not-control-plane [path-clear-no-content-source]. The live CONTROL_PLANE_RE covers claude-plugins/kampus-pipeline/skills/, not claude-plugins/fabrika/; live CODEOWNERS carries no fabrika row; no .decisions/** file, so the ADR-0164 content clause has nothing to decide. This marker binds ship-it with no human approval behind it — I am the only check on this PR.

Required namespaces at this head: review-skill only. class-probe classify --namespaces over the 9 changed files → has-skills, one namespace. Not UI-affecting (no apps/web/src/ path), so review-design is neither required nor dispatched. Coverage self-check: 1 of 1 required namespace carries a SHA-bound marker at this head.

Gated against the re-derived criteria per the founder ruling on #5021 (comment 5237132576), carried to this PR by the gate note: the six emission rows are retired, the five re-derived rows replace them, and the "7–140 line band" row is void (skill-conventions.md §2 no longer carries a line count — #4701 + #5219).

The two blocking findings are fixed, verified against the artifacts rather than the description

F1 — CLEARED. SKILL.md now reads a key that exists.

SKILL.md:177 and :235 both now read heldBy.claimNonce. Verified against the contract's actual output, not the repair's account of it:

  • contract.md:717 — the claimed payload of handoff read carries "heldBy":{"claimNonce":"4b8e2f01","claimedAt":…,"claimComment":…,"by":"usirin"}. heldBy.claimNonce resolves.
  • contract.md:864handoff claim's payload carries claim as a two-value string (held/resumed) alongside a top-level claimNonce. So claim.nonce was unresolvable against that verb too; it is now not referenced.
  • A repository-wide grep for the literal claim.nonce across all nine files at head: zero matches.

The PACK-HELD-ELSEWHERE path specifically. Both legs now read a key that exists. SKILL.md:174-180 sits under the fabrika handoff read fence, so heldBy.claimNonce is being read out of a read payload — the verb that actually defines it. SKILL.md:234-235 states the two legs explicitly: 15 from claim, or read at 0 reporting claimed whose heldBy.claimNonce is not this run's. This is the terminal that stops two seats working one pack — the #5283 failure the skill exists to prevent — and it is now executable.

F2 — CLEARED. All three read-quoting fixtures match the merged contract.

Checked each against contract.md, key by key:

eval-2 (sealed, no drift) eval-3 (PACK-STALE) eval-5 (none)
packComment / packNonce / heldBy yes yes yes
ground carries only packed yes yes n/a (null)
drift.packedBranch present yes — resolves yes — gone n/a (null)
  • Key order matches contract.md:711/:717 exactly in all three: issue, pack, packComment, packNonce, sealedAt, author, asserted, ground, drift, heldBy, disregarded, scanned. drift is packedBranch, state, fields; each field row is field, packed, live, state.
  • ground.live dropped per ONE-SUMMARY-NOT-TWO (contract.md:732-738, "there is deliberately no live counterpart"). The old eval-2/eval-3 both carried it; neither does now. The remaining live keys are per-field row values, which the contract does specify (contract.md:743-744).
  • eval-5 matches the contract's stated none shape verbatim: "When pack is none, packComment, packNonce, sealedAt, author, asserted, ground, drift and heldBy are all null".
  • Swept the whole eval directory: only these three files quote a handoff read payload. eval-1, eval-4 and PIN.md quote none, so the fix is complete rather than partial.

The eval-3 re-shape — I checked whether this widens past the finding. It does not.

The repair re-shaped eval-3's rows to live:null, state:"moved" for eight rows, citing READ-DERIVES-AGAINST-THE-PACKED-BRANCH. I read the citation and it says what is claimed, verbatim at contract.md:315-321:

packedBranchgone (it is proven absent) … and when it is gone those eight rows are reported with live: null and state: "moved".

Three things make this not a discretionary widening:

  1. It was prescribed by the FAIL verdict, not self-authored. Fix item 2 of @ 9d465f62 said in terms: "add drift.packedBranch (and set it to gone in eval-3, which is the PACK-STALE case that needs it)."
  2. Once packedBranch is gone, the row shape is compelled, not chosen. The anchor mandates it. Leaving git.head with a real live SHA beside packedBranch:"gone" would have been a new internal contradiction — a branch simultaneously absent and resolving.
  3. The eight rows are exactly rows 3–10, verified against the numbering. From the digest pre-image table: 3 git.branch, 4 git.head, 5 git.upstream, 6 git.reachable, 7 git.aheadBy, 8 git.behindBy, 9 git.base.branch, 10 git.base.head. The fixture nulls exactly those eight and no others. It is not "rows 3-10 plus a few".

The arithmetic closes. The compared set is 16 (19 minus rows 11–13, the uncomparable git.tree.*). fields lists only rows whose state is not same (contract.md:743-744). The fixture lists 10 moved (8 plus board.pull.state and board.pull.checks); the 6 unlisted (issue, repo, board.issue.state, board.issue.labels, board.pull.number, board.pull.head) are unmoved. 10 + 6 = 16.

And the claim that the fixture previously contradicted its own expected output is true — I verified it independently. evals.json eval-3 expected_output already read "Recognizes the packed branch is gone and the PR merged". The old fixture showed git.head moving to a live SHA and carried no packedBranch at all — a branch that plainly still resolved. The fixture asserted the opposite of what the eval claimed to test. The new shape resolves that, in the direction the eval already stated. board.pull still resolving with the branch gone is coherent, not a second contradiction: a merged-and-deleted branch keeps its pull-request record, which is precisely the open → merged transition the fixture shows.

No re-grade required, and none demanded. I checked every assertion in evals.json: all 27 are prose about run behaviour, and none keys on a JSON field name — not comment/nonce/claim, not ground.live, not packedBranch. The recorded 16/27 stands as measured, the PR body says plainly that the fixtures were corrected without re-grading, and no number is restated over the new shape. Not graded against.

Digest — recomputed independently: f9d0814b89b4

Not accepted from the repair's account. I extracted the pre-image at contract.md:261-279 (exactly 19 lines, one per numbered field, confirmed against the field table), and ran it through the real implementation — normalizeForReadback (packages/fabrika-cli/src/report/compose.ts:177-183) then SHA-256, first 12 hex (bodyDigest, packages/fabrika-cli/src/ledger/digest.ts:28):

with trailing newline   : f9d0814b89b4
without trailing newline: f9d0814b89b4

Identical either way, which independently confirms the contract's own parenthetical that the trailing newline is normalised away. 368842989186 appears zero times in the contract; f9d0814b89b4 is the only value it prints. ADR 0247 satisfied — the digest is computable by a reader, not asserted.

The PR body now names the old number rather than silently swapping it, which is the right correction shape. (Minor, non-blocking: the body says "all six places"; the value actually appears on nine lines. The artifact is internally consistent — only the count in the prose is loose.)

Criterion 10 — flagged to me as UNRULED; it is RULED, and I verified that live

I was briefed that #5290 was open with zero comments and that I must report criterion 10 as UNKNOWN. That brief is stale. I read #5290 live: a founder ruling landed at 2026-08-10T07:53:39Z, and it is explicit on this exact point:

Ruled: silent omission discharges criterion 10. An explicit non-applicability line is NOT required. … The re-gate after repair now has a settled answer and should not report criterion 10 as UNKNOWN. … The repair coder must not invent a "no flaw here" line — under this ruling none is needed.

The ruling binds the stem-only form carried byte-identically by #5021, which is this brief. So I grade it rather than reporting UNKNOWN — reporting UNKNOWN against a settled ruling would be as wrong as inventing an answer. The coder's deliberate non-action was correct and is now sanctioned: no wording was added, the v1-surface section was not restructured to pre-empt the question, and the ruling says exactly that was the right move.

Sibling #5279 (criterion 16, calibration) is explicitly not settled by that ruling and I have not treated it as such — the calibration row below stays disclosed-not-graded, and this verdict does not rest on it.

Acceptance criteria — the re-derived five (ruled)

  • [PASS] Handoff is an act someone takes; states what refuses when the act was not taken, and how a successor tells a sealed pack from an unsealed remnant — SKILL.md:24-28, §TERM NO-PACK (SKILL.md:228-230), and eval-5 is exactly that discrimination.
  • [PASS] Every claim separated into proven and asserted — SKILL.md:17-22; contract.md:164-182 (the caller supplies only the four asserted sections; take appends the proven half from its own capture, so premise-inheritance is unrepresentable); read returns them under asserted vs ground/drift.
  • [PASS] Pack bound to the ground state it was taken at, successor told what moved sincedrift folded into read with no way to read without it (contract.md:749-751, SKILL.md:168-172); the 19-field ground / 16-field compared set, and READ-DERIVES-AGAINST-THE-PACKED-BRANCH (contract.md:301-321). Strengthened this roundeval-3 now demonstrates the gone leg the anchor specifies.
  • [PASS] Composes by value — contract.md:340-342: stdin only, no --body, no --body-file, no temp file (PR-create leaked a literal @/tmp body-file reference into a PR body (local-path leak) #3086/Reviewer verdict emit-bypass recurs: raw gh api -f body=@file posts a literal @path (empty namespace + local-path leak) vs pipeline-cli verdict post #3173). Re-scanned all 9 files at this head for home-rooted, machine-local, temp-dir and sibling-repo path prefixes: zero hits.
  • [PASS] Files nothing and creates no work — SKILL.md:12-15 and §CAP:66-69; contract.md:112-119 refuses a filing verb and a retirement verb by name.

Acceptance criteria — the standing original rows

  • [PASS] Boots from the issue + repo alone without a clarifying question — the session flagged the stale AC list rather than asking; the ruling states that was correct behaviour.
  • [PASS] Six authoring-brief-contract fields hold — satisfied at authoring time; the two missing-prior-art findings were stated explicitly rather than left blank.
  • [PASS] Uses the name the Phase-1 ruling pinned — handoff, cited at SKILL.md:266-270 (Decision: the ideation quartet's skill names and packaging under the leaf rule #5017, ADR 0246).
  • [PASS] Every verb states exit semantics; a proven answer never shares a code with a failure to invoke; empty result stated once in the header — the shared matrix (contract.md:367-391) plus four per-verb tables; 3-11 match packages/fabrika-cli/src/report/codes.ts name-for-name, and 10 is correctly declared an unreachable gap on the shipped UI_SEATS precedent.
  • [PASS] Contract records each v1 surface's scar + how fabrika avoids it — per-verb Grounding blocks: wayfinder-map returns-normally-on-malformed, intake-dedup exit-0-either-way, epic-lock never reads back its stamp, v1 add-frontier-ticket.sh printing a refusal to stdout. Graded, not UNKNOWNv1-surface coverage criterion enforced inconsistently: grilling passed, graduate failed #5290's ruling settles that a clean surface may be silently omitted.
  • [PASS] Names no pipeline-cli verb as a dependency, invokes nothing under claude-plugins/kampus-pipeline/ or packages/pipeline-cli/ in any fence — contract.md:13-18; the only mentions are two scar citations, both prose, neither in a fence.
  • [PASS] No wrapper verb relaying an upstream answer — contract.md:125-126 refuses a second answer to triage classification, control-plane membership and pitch approval by name.
  • [PASS] Prices its invocation axis on the page — SKILL.md:280-287 prices all three user-invoked costs and the recurring price of the model-invoked choice.
  • [PASS] Every command string is a plain literal — the five fences use literal --issue 5021 / --nonce 7f3a9c21, no interpolation. (The 7-140 band half of this row is void per the ruling; not graded.)
  • [PASS] Writes down no content-ingestion posture as settled, states the §9 seam, cites Pipeline agents read issue bodies and comments with no author trust gate #4859 — SKILL.md §ING:34-55 and :277-278.
  • [PASS] Changed nothing downstream, nothing under claude-plugins/kampus-pipeline/, no verbs, no convention doc, no eval bar/harness — all 9 changed files are under claude-plugins/fabrika/skills/handoff/. Re-verified after the rebase: the PR's file list against main is still exactly those 9.
  • [PASS] skill-reviewer ran before the PR opened and the PR records the pass — four passes tabulated. The calibration leg is author-asserted and Tier-D, not gate-observable; recorded as disclosed rather than graded (Skill-authoring runbook writes no calibration record, so a review criterion rests on memory #5279 is open and unruled; this verdict does not rest on it either way).
  • [PASS] One PR carries both SKILL.md and contract.md with Fixes #5021 — both present, Fixes #5021 on body line 3.
  • [PASS] No absolute, home-rooted, machine-local or sibling-repo path, no operator name or email — 0 hits across all 9 files at this head; fixtures use a fictional repo with issue ids 9412-9473, well above this repo's maximum.

Skill rigor

  • [PASS] Behavioral correctness — the two defects that failed @ 9d465f62 are fixed and verified against the contract rather than the repair's description (F1, F2 above). The repair delta from the old head to this one is exactly 4 files / 5 changed lines, all of them the prescribed fixes: 2 lines in SKILL.md (claim.nonceheldBy.claimNonce) and 1 line each in eval-2/eval-3/eval-5. Nothing else moved — no quiet edit rode along with the repair.
  • [PASS] Trigger / description quality — unchanged by this round (the diff touches no frontmatter). The trigger set is sharp; the over-trigger risk against graduate stays closed on both sides.
  • [PASS] Cross-skill conflict / shadowing — unchanged. handoff is still absent from SHIP_NAMESPACES and the verdict-marker NAMESPACE regex is not widened (contract.md:120-124, 213-217), so nothing here can gate a merge.
  • [PASS] Gate-invariant preservation — no gate invariant is in the diff's reach. Non-§CP, touches no gate-critical skill, no .github/, no .claude/, no ADR, adds no verdict namespace. The repair narrows nothing.
  • [PASS] Contract implementation ticket (ADR 0248) — in scope (contract.md is added). Implement the handoff contract's verbs in packages/fabrika-cli/ #5025 re-read live at this head: open, status:triaged, p1, fabrika, titled "Implement the handoff contract's verbs in packages/fabrika-cli/", routing explicitly to this contract. Named in the PR body. Fixes #5021 closes the authoring brief and is correctly not offered as the hand-off.
  • [N/A] deviation-disclosure — §DEV Who owes the section, second shape: not authored by write-code. Positively established: founder-authored (usirin), a /skill-creator authoring-session body shape rather than the write-code template, no write-code progress comment on the PR or on Authoring brief: the handoff skill — session-continuity compaction, deliberately NOT graduation #5021, and brief Authoring brief: the handoff skill — session-continuity compaction, deliberately NOT graduation #5021 routes authoring through /skill-creator as "the only door (Decision (founder-decision-fork): what 'done' means for v2, and the eval bar #4637-C)". The body nonetheless carries a repair-round ## Deviations section, and all four entries are accurate against the artifacts — including the honest statement that the fixtures were corrected without re-grading.

Specialist fan-out (ADR 0079) — no in-scope finding to append

Run over the changed instruction text at this head. unreachable-step: none — the F1 fix makes a previously unexecutable step executable, which is the inverse. contradictory-instruction: none — SKILL.md:177 and :235 now agree with each other and with contract.md:711/:717. uncovered-procedure-path: PACK-HELD-ELSEWHERE now has a resolvable key on both of its two legs. Nothing appended to #5021.

CI at head — green, and counted rather than eyeballed

Re-derived with --paginate and per_page=100, then cross-checked against the declared total, because an unpaginated read truncates silently at 30:

  • declared total_count 46, flattened 46 — they agree.
  • 39 success + 7 skipped, zero failing, zero pending.

The 7 skipped are path-filtered and correct here: the diff carries no .ts, so lint / format / typecheck does not apply, and path-filter-guard itself passed. leak-guard, gitleaks, crew-leak-guard, doc-links, pointer-guard and adoption-lint are all green. The skill-gh-lint workflow ran and concluded success, and validate skill frontmatter is independently green — the frontmatter gate executed, so it is established rather than UNKNOWN.

Non-blocking notes — recorded, not held against the PR

  • SKILL.md never names drift.packedBranch by key, describing the condition in prose instead (SKILL.md:231: "the branch is gone, or its pull request already merged or closed"). Not the F1 class: packedBranch exists in the payload and its value is self-describing (gone), so an agent reading the JSON resolves it trivially. Worth a line if the skill is ever revised; not worth blocking.
  • The registered-groups list at contract.md:22-23 omits status. Unchanged and still harmless — the sentence self-hedges and the load-bearing claim holds. Leaving it alone is recorded as a deviation and is the right call.
  • 8 of 13 §TERM terminals have zero eval coverage. Ruled at the previous head as disclosed debt that clears the bar and does not block, on all three legs (counted, cause named, pass rate not restated). Unchanged this round, and the structural blocker is real: src/eval/corpus.ts STAGES has no ideation seat, so no quintet skill can declare an eval entry today (The eval corpus STAGES vocabulary has no ideation stage, so no quintet skill can declare an eval entry #5241, owned by fabrika eval layer: the execution harness, the incident corpus, and the ruled bar #4649). The contract records the §8 gate-3 leg as blocked, not skipped.

Result

Every acceptance criterion and every rigor check passes. Both blocking findings are cleared, verified against the artifacts rather than the repair's account of them; the one change reaching past the literal finding is contract-compelled and was prescribed by the previous verdict rather than self-authored; and the digest recomputes to the value the artifact prints. This PR is merge-ready.

review-skill does not mergeship-it is the authorized merge step; merging will auto-close #5021 via Fixes #5021.

Verdict-written: 2026-08-10T08:16:27Z

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.

Authoring brief: the handoff skill — session-continuity compaction, deliberately NOT graduation

1 participant