| name | inventory-reconciliation |
| description | Use when landing a PR that closes a numbered item in `SECURITY_INVENTORY.md` *Recommended policy* or *Missing work*, or when threading a new parameter through public APIs with a deferred default flip. Covers the *Executed past-tense one-liner* phrasing and when to use the half-closed two-step. |
| allowed-tools | Read, Bash, Grep |
Inventory Reconciliation (lean-zip)
Patterns for executing SECURITY_INVENTORY.md Recommended
policy and Missing work bullets as individual PRs.
Anchor-refresh PRs are forbidden (issue #2345)
Do not create — and do not claim — any issue or PR whose only
output is a line-number anchor refresh (e.g. "re-anchor stale
Zip/Foo.lean:N to :M", "tighten range cite to def-line",
"refresh stale source-line cites") in SECURITY_INVENTORY.md,
.claude/skills/**/*.md, or plans/**/*.md.
Markdown files in this project no longer track source-line
numbers. Citations use stable identifiers (function name,
theorem name, fixture filename, section header) which do not
shift with line edits.
Worker action: if you encounter such an issue, post a
comment linking to issue #2345 and run
coordination skip <N> "anchor-refresh PR forbidden by #2345".
Planner action: refuse to create such an issue. Do not
generate work whose entire diff is integer changes in markdown
links.
One bullet per PR (semantic only)
When a PR executes a numbered Recommended policy item or a
Missing work bullet, it lands as its own PR — do not bundle
two unrelated bullets into one PR. This is about preserving the
bullet-to-PR-number 1:1 mapping in PROGRESS.md, not about
guarding link-checker warnings (which no longer exist for
line-anchor drift).
Precedent: Rec. 1/2/3/4/5 landed as five PRs in the
2026-04-22 audit-completion wave (#1617, #1618, #1623, #1630,
#1631).
The Executed — … past-tense one-liner
When a PR closes a numbered bullet, rewrite that bullet (in
the same PR) from future-tense to past-tense:
- Before:
- [ ] Recommend raising native-side default to 1 GiB to match FFI whole-buffer (Rec. 5).
- After:
- [x] **Rec. 5 executed**: native-side default raised to 1 GiB in #1617; see that PR for caller impact.
Keep the - [x] checkbox. Keep the rec. number
(**Rec. 5 executed**) so the planner can cross-reference. One
sentence on caller impact with a PR-number link.
Half-closed two-step
There are two two-step shapes; both keep the inventory honest
during the half-closed window. Pick by what the closing PR is
splitting — a parameter (API surface) or a scaffold (artefact
shell with a deferred body).
Parameter variant (parameter-add + default-flip)
Use a two-step (parameter-add PR + default-flip PR) when:
- The parameter is new to a public API but the existing
callers are numerous.
- The default flip needs independent caller-impact analysis.
- The param-add touches multiple subsystems but the flip is
localised.
Use a one-step when:
- The parameter has no existing callers (no backwards-compat
burden).
- The flip's caller impact is trivially bounded.
Between-step discipline: during the half-closed window,
the bullet is re-phrased as "half-closed by #N (parameter
added, default still 0). Flip tracked as #M." — the
#M is the follow-up issue, filed at the same time as the
param-add PR goes up.
Precedent: #1610 (half-closed, default 0) → #1631
(default-flip) was the 2026-04-22 Rec. 2 execution. #1630 is
the counter-example (new param, no callers, one-step).
Scaffold variant (scaffold + body fill-in)
Use a two-step (scaffold PR + body fill-in PR) when:
- The artefact has a host / toolchain prerequisite that not
every worker host satisfies (e.g. nightly Rust, Linux-only
sanitizer runtime, GPU, network credentials).
- The skeleton can be structurally complete (env-guard +
usage block + Missing-work forward-pointer) on any host,
with the body deferred to a host-gated follow-up.
- A load-bearing env-guard is the right way to signal "host
ineligible" cleanly (exit code distinct from "skeleton not
yet implemented" — see PR #2383's two disjoint exit-2 vs
exit-1 diagnostics).
Use a one-step when:
- The recipe body is self-contained and runnable on the worker
host minting the PR.
- No host-gating is needed.
Between-step discipline: during the half-closed window:
- The Missing work bullet is edited in place with a
forward-pointer to the new artefact (NOT flipped to Recent
wins yet — the body is still TODO). The
- [ ] checkbox
stays unchecked.
- A sibling Current local guardrails bullet records the
half-state with a "scaffolded but not yet executed"
qualifier so a future reader of either bullet can tell that
the work is partially executed.
- The body fill-in is tracked as a separate
agent-plan
feature issue, filed at the same time as the scaffold PR
goes up (or by the paired-review of the scaffold PR,
whichever comes first).
- The follow-up issue carries a host-gating preamble naming
the toolchain prerequisite and the macOS-style
coordination skip pattern from #2366 / #2369, so worker hosts that
cannot satisfy the prerequisite release the claim cleanly
without churning the queue.
Precedent: PR #2383 (scaffold —
scripts/sanitize-rust-ffi.sh skeleton + Sanitizer recipe
inventory paragraph) → issue #2392 (body fill-in) is the
2026-04-29 Track E miniz_oxide TCB execution. The parameter
variant (#1610 → #1631) remains canonical for parameter
splits; this variant is for artefact-shell splits and
lives alongside it.
Single author per wave
If two inventory-reconciliation bullets are claimed
concurrently, the second-to-merge pays rebase cost on
SECURITY_INVENTORY.md. Planner should throttle to ≤ 2
edits-inventory concurrent (per the #1600 meditate's §7
heuristic) and prefer sequential bullets over parallel ones.
Terminal-closure tightening cadence
When a Recent wins bullet covers a per-slot fixture family with a
shared guard (e.g. the post-#1928 UStar interior-NUL family — five
slots name/linkname/prefix/uname/gname, all under the
same containsNul reject), each per-slot landing rewrites the
same bullet's residual-coverage carve-out, shrinking the
residual-statement by exactly one slot — keeping the bullet honest
as the family closes.
Per-step shape: each PR in the wave touches exactly one bullet
in SECURITY_INVENTORY.md Recent wins — the bullet for the
shared guard. The PR's SECURITY_INVENTORY.md diff is one
carve-out paragraph rewrite (no new bullet, no row reorder), with
the carve-out's slot list shrinking by one entry. The new entry
in Recent wins (the per-slot fixture itself) goes elsewhere in
the file (typically the per-format inventory rows), not as a new
bullet — the bullet is the family anchor, not the per-slot
anchor.
Closure-of-sub-arm vs terminal-closure-of-family: the cadence
distinguishes two carve-out endings.
- Closure of a sub-arm leaves the bullet's carve-out paragraph
in partial form, naming the still-open arm. Example phrasing:
"… filesystem-reaching arm fully closed (3/3); defense-in-depth
arm pending (uname/gname)." The bullet still acknowledges
residual work.
- Terminal closure of the whole family rewrites the carve-out
to a terminal-closure form: "fully closed N/N — no more
sibling per-slot fixtures expected" (or equivalent). The
bullet stops promising future tightenings. After terminal
closure, future per-slot work on the same guard would be a
different family (different guard, different bullet), not a
6th tightening of this one.
5-step precedent (post-#1928 UStar interior-NUL wave, terminal):
- #1880 — slot
name; carve-out introduced ("4 of 5 slots remain to land as
sibling per-slot fixtures").
- #1934 — slot
linkname; carve-out shrunk to "3 of 5 remain
(prefix/uname/gname)".
- #1937 — slot
prefix; carve-out shrunk to "filesystem-reaching arm fully
closed (3/3); defense-in-depth arm pending (uname/gname)".
- #1944 — slot
uname; carve-out shrunk to "4/5; gname remaining" (sub-arm
not fully closed yet).
- #1957 — slot
gname; carve-out rewritten to terminal-closure form
("fully closed 5/5 — no more sibling per-slot fixtures
expected").
Step 3 is a closure of a sub-arm (filesystem-reaching arm); step
5 is the terminal closure of the family. The two phrasings are
not interchangeable — using terminal-closure phrasing at step 3
would mis-promise that no defense-in-depth siblings are coming;
using sub-arm phrasing at step 5 would falsely keep the bullet
open.
When the cadence is appropriate:
- The bullet covers a per-slot fixture family with a shared
guard (all slots throw the same error wording family,
differing only in slot-name suffix), and
- The fixture wave proceeds one slot per PR (per the
one-bullet-per-PR cadence above), and
- The slot count is known up front (so terminal closure has a
well-defined endpoint).
When it is not:
- One-shot bookkeeping PRs (placeholder substitutions,
PR-number sweeps) — these touch the inventory but
do not tighten a carve-out.
- Paired-review entries — these record observations on a landed
PR; they do not modify the carve-out.
- Defense-in-depth extensions to a different guard family — a
new family gets its own bullet, not a 6th tightening of the
closed one.
Per-slot fixture-naming asymmetry
Per-slot fixtures in a family with a shared guard come in two
flavours, distinguished by whether the slot smuggles into its
own field or into the path slot. The fixture-name suffix
encodes the distinction.
Filesystem-reaching arm — single-name suffix: the slot's
value transforms into the path before the guard fires (e.g. the
UStar prefix slot is concatenated with name by splitPath
before the path-validation guard runs). The fixture name uses a
single name suffix because the smuggled NUL byte ultimately
lands in the path slot:
ustar-<slot>-nul-in-name.tar
The slot identifier names the writer-side smuggle vector; the
-in-name half names the post-transform smuggle target.
Defense-in-depth arm — doubled-SLOT suffix: the slot's value
is written verbatim to its own header field with no path
transform (e.g. UStar uname and gname are owner-metadata
fields that never reach the filesystem path). The fixture name
uses a doubled slot suffix because the smuggled NUL byte
lands directly in the slot's own field:
ustar-<slot>-nul-in-<slot>.tar
The slot identifier names both the writer-side smuggle vector
and the smuggle target — they are the same field.
5-slot precedent (post-#1928 UStar interior-NUL family):
| Slot | Arm | Fixture filename |
|---|
name | filesystem-reaching | ustar-name-nul-in-name.tar |
linkname | filesystem-reaching | ustar-linkname-nul-in-name.tar |
prefix | filesystem-reaching | ustar-prefix-nul-in-name.tar |
uname | defense-in-depth | ustar-uname-nul-in-uname.tar |
gname | defense-in-depth | ustar-gname-nul-in-gname.tar |
The name slot is the degenerate filesystem-reaching case where
slot and target collide — the fixture is ustar-name-nul-in-name.tar
because both the writer-side vector and the smuggle target are
literally the name field, but the arm is filesystem-reaching
(the field reaches the filesystem path).
Smuggle-target invariant: the suffix encodes the field that
actually receives the smuggled NUL byte in the on-disk
header — the attack surface, not just the slot name.
Cross-reference: malformed-fixture-builder covers the
builder-script side of the same asymmetry — the pathOverride
hook (filesystem-reaching arm) versus the verbatim-write pattern
with no override (defense-in-depth arm).
Origin: paired-review #1963 §E.8.e and §E.8.f flagged both
observations after the post-#1928 wave's terminal closure.
Scope — what this skill does not cover
- Does not cover new top-level inventory sections (that's a
planner-design call, not a reconciliation shape).
- Does not cover the prose of the docstring changes themselves —
those follow the #1573 template, documented in
error-wording-catalogue.