Skip to content

Gate evidence and release integrity — lint rides the discovered gate, End-state ownership spans deps siblings, version guard gains a monotonic floor (0.14.62) - #1139

Merged
Ljferrer merged 16 commits into
masterfrom
dev/2026-07-24-gate-evidence-and-release-integrity
Jul 27, 2026
Merged

Gate evidence and release integrity — lint rides the discovered gate, End-state ownership spans deps siblings, version guard gains a monotonic floor (0.14.62)#1139
Ljferrer merged 16 commits into
masterfrom
dev/2026-07-24-gate-evidence-and-release-integrity

Conversation

@Ljferrer

Copy link
Copy Markdown
Owner

Plan 5 of 6 — campaign 2026-07-24-standing-record-and-guard-hardening. Plan: docs/plans/2026-07-24-gate-evidence-and-release-integrity.md · Red-team CLEARED (3 rounds) · Closes #1081 #1082 #1083.

Base note: the campaign brief called for stacking this onto dev/2026-07-24-drift-guard-and-floor-diagnostic-hardening. That branch no longer exists on origin — PRs #1097, #1109, #1117 and #1126 were each opened against master and all four merged earlier today, so plans 1–4 are already on master (verified: all four tips are ancestors of origin/master). This targets master accordingly. 16 commits ahead, 6 behind (master carries the merged #1127 memory-tighten).

What this closes

Three verified holes that let a false "all green" survive the exact checkpoint built to catch it.

#1081 — the redaction lint was structurally invisible to gate evidence. It ran only in post-push CI, so any plan End state citing it was a permanent SOFT cannot-confirm at every gate-audit. A thin skills/_shared/war-memory-lint.test.sh wrapper now rides the gate's existing *.test.sh self-discovery — zero engine change: resolveGate and both hand-mirrored copies are byte-untouched, and CI keeps its triggers, steps and permissions (header comment only). The wrapper fails loud on a missing target, so a moved docs/learnings/ can never print lint: clean forever.

#1082 — an in-phase sibling's condition read as provably-UNMET-HARD. The gate-audit's only ownership exemption was "a LATER phase", so a condition owned by a not-yet-landed deps-chained sibling held the land against a task that fully met its own slice. Case (3) now names both owners on both prompt surfaces in one commit (dispatched endStateBlock + standing agents/war-auditor.md), reusing the out-of-scope title token so every downstream route is unchanged.

#1083 — lock-step ≠ monotonic. A coherent all-four-slots revert passed every guard — which is how a Gate-2 commit staged from a stale worktree once silently reverted a landed release. Three layers land: a monotonic-floor test (bounded first-parent window; fail-open only on an unusable git context or a genuinely empty window — a non-empty log parsing to zero versions is RED, never a vacuous pass), a Gate-2 pre-push staged-file-list check, and a publication-worktree dirty-reuse refusal that only ever refuses loudly (never resets, never destroys work).

Phases

phase tip result
1 — evidence wiring + carve-out + release floor c663340 4/4 merged, 0 escalations, 0 fix rounds, unanimous 3-seat panels
2 — release 23f853c 1/1 merged, release 0.14.62

64 agents, 0 errors. 11 findings absorbed across 6 ace/polish commits — durable record is the commit bodies, not issues.

Release 0.14.62 resolved from the live slots at land time (all four read 0.14.61; predecessors ran 0.14.58 → 0.14.61), never from a plan literal. Verified at the landed tip: version-slots.test.mjs 4/4 green, including the new monotonic floor on its first real bump.

Unexecuted backstops

None. All three plan-declared backstops were executed at Phase-1 land:

  • Captured-gate-banner confirmation — PASS. == gate(bash): ./skills/_shared/war-memory-lint.test.sh == + lint: clean present in all five captured .war/gate-*.log artifacts — End state 1 confirmed from a real refiner artifact, not the in-task proxy.
  • Integrated-tip §4.4 sweep re-check — PASS. All four sweeps clean at the landed tip, every named control fired. memory-audit.yml's retired claim is gone; the lesson's hits are past-tense under its MITIGATED (#1081) prefix; ADR 0024 = 0 hits; README = 0 lock-step hits.
  • MEMORY.md projection budget re-check — FAILED at the time, now resolvable. render-index REFUSED (projection 24,893 B vs the 24,400 B hard cap) because the memory-tighten archives were still unmerged, leaving the on-disk repo root at 104 hot lessons while this phase promoted 2 more. docs(learnings): tighten — archive 13 cold lessons #1127 has since merged to master, which clears the cause. Lessons were never at risk — they stayed on disk and queryable; only the projection index was stale.

Learnings

5 project-typed lessons promoted through Gate 2 (lint clean, pushed 73334aa): the three origin lessons updated with MITIGATED/RESOLVED status, plus two new — the *.test.sh vehicle's interaction with the merge-path test floor, and the leader-strip → whitespace-normalize → grep doc-sweep ordering. The Gate-2 pre-push duty that landed in this very PR was applied to its own promotion commit.

Follow-ups filed

Not touched, deliberately

resolveGate and both mirrors, war-config.mjs, every status enum, HARD_ESCALATION_REASONS, KNOWN_LAND_DECISIONS, every floor exit code, and CI behavior.

🤖 Generated with Claude Code

WAR Test and others added 16 commits July 25, 2026 21:54
…ds, escalating)

Terminal BLOCKED. The Lead stopped patching after round 2 and is escalating five
residual open questions to the operator.

Round 1 found 4 blockers over 3 roots; two were resolved and verified:

- ${plan.file} in endStateBlock was a phase-level crash. `plan` is destructured
  with no default and never entry-validated (workflow-template.js:231, which is
  why :484 reads `if (plan) plan.gate = ...`), endStateBlock is built top-level
  and unconditionally (:1497), and pt throws on undefined by contract (:191). A
  plan-less zero-task phase claiming End-state conditions would have gone from a
  clean route to held:workflow-error phase-wide. Now the guarded form at all
  three mandate sites.
- The monotonic mechanic silently depended on git's IMPLIED
  --diff-merges=first-parent: all 50 slot-touching first-parent commits are
  merges, and --diff-merges=off yields zero versions. version-slots.test.mjs
  runs in every refiner gate, so a zero-parse would have bricked the gate
  campaign-wide. The flag is now pinned explicitly.

Round 2 returned 5 blockers, mostly defects the round-1 patch introduced: a
mis-recorded measurement (a -n 6 sample written as a full-window figure; the
real number is 49 from 50), a false causal claim (the Lead wrote that the path
filter makes rev-list return non-merges; three probes measured otherwise and the
Lead confirmed --first-parent is the discriminator), a floor contradiction, and
an overstated test citation.

Two wrong Lead explanations in a row on one paragraph is what the
two-rounds-per-blocker rule exists for. The wrong text is WITHDRAWN rather than
replaced with a third theory, and that paragraph now carries only measured facts
plus a do-not-implement marker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Plan 5 (gate-evidence-and-release-integrity) was BLOCKED after 2 red-team
rounds with 5 residual questions escalated to the operator. All five ruled:

1. rev-list + git show alternative DROPPED — the proven
   `git log --first-parent --diff-merges=first-parent` form is the sole mechanic
2. §4.4 sweeps re-anchored: leader-strip -> whitespace-normalize -> grep -io,
   full clauses kept, each paired with a named must-fire control
3. orphaned §4.4 disposition 5 (ADR 0024) assigned to Task 1.1; Notes bullet
   now maps all six dispositions to owners
4. two-surface drift guard stays per-surface bounded ordered pins; the D3
   registry row rejected as strictly weaker than End state 3
5. End state 4 enumerates the plan-less claims-bearing regression case; the
   :6692 citation re-anchored by construct name

Round 3 (wf_89c5f40a-e37, 10 probes, 15 agents, 10/10 on-target) returned 3
Major blockers. One was real — whitespace normalization alone does NOT survive
a comment-leader reflow (`# ` survives `tr`, so the clause still misses). Fixed
by prepending a comment-leader strip; proven at the real base: 0 hits raw,
0 tr-only, 1 with the strip, control firing on the same run, all four sweeps
green. The other two blockers were refuted as stale-base self-confounds — the
probes measured this Lead worktree (v0.14.57) instead of the campaign base
(master, v0.14.61), where the plan's README claim is true.

One Lead statement introduced this session was likewise stale-base and is
withdrawn in the report: the cited plan-less test sits at :6692 exactly as
originally cited; only the coverage half (no endState key) stands.

Verdict: CLEARED. /war may execute plan 5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rapper (#1130)

Closes the #1081 gap: `war-memory.mjs lint docs/learnings/` ran only in
post-push CI, so a plan End state citing it was a structurally SOFT
cannot-confirm at every gate-audit — no captured artifact could carry the
evidence.

- NEW skills/_shared/war-memory-lint.test.sh — a gate-DISCOVERED `*.test.sh`
  beside the CLI. The gate's own `-name '*.test.sh'` sweep picks it up with
  ZERO engine change: no `resolveGate`/composer edit (both hand-mirrored
  copies byte-untouched), no floor-script vehicle. bash-3.2-safe,
  cwd-independent (repo root resolved two dirs up from $0). Target is `$1`
  when given (fixture override for the meta-tests), else the repo's
  docs/learnings/ — explicit, because a bare `lint` falls back to the LOCAL
  memory root. Existence guard (anti-vacuity, conscious deviation from spec
  §4.1): cmdLint's `catch { continue }` prints "lint: clean" exit 0 on an
  absent target, so the wrapper fails LOUD with exit 2 naming the path
  instead. On an existing target the CLI exit code propagates untouched.
- war-memory.test.mjs: three meta-tests spawn the wrapper against a violating
  fixture (exit 1 + offending file + `home-path` on stdout), a clean fixture
  (exit 0 + "lint: clean"), and a nonexistent target (guard exit 2 naming the
  path). Fixtures live under mkdtemp, never docs/learnings/; the home-path
  shape is assembled at runtime, never committed. Temp-break proven: deleting
  the `exec node … lint` line reds the first two, deleting the `-d` guard reds
  the third — and only the third.
- memory-audit.yml: header comment only (name/on/permissions/steps
  byte-identical) — CI is now the POST-PUSH backstop, the discovered suite is
  the PRE-MERGE enforcement.
- gate-artifact-never-includes-war-memory-lint lesson: `MITIGATED (#1081):`
  description prefix (compressed 221B -> 188B), a lead MITIGATED note naming
  the wrapper, tense-shifted historical record, and forward advice that no
  longer sends a gate-auditor straight to SOFT-note on a post-wrapper tip.

Gate green (935 JS tests, all discovered shell suites); the captured gate log
carries `== gate(bash): ./skills/_shared/war-memory-lint.test.sh ==` followed
by `lint: clean`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… fix, default-target pin (#1130)

Advisory absorb-disposition fixes, one commit, no behavior change beyond the
widened guard and one additive test:

1. [Nit] Existence guard covers absent-but-not-unreadable, while its own header
   comment claims both (war-memory-lint.test.sh:34). cmdLint wraps statSync AND
   readdirSync in one catch{continue}, so an existing-but-unreadable target
   (EACCES) still yielded a vacuous "lint: clean" exit 0 — the code was narrower
   than the comment it ships with. Fix: `[ ! -d ] || [ ! -r ]`, message updated
   to match ("does not exist or is not readable").

2. [Nit] memory-audit.yml: 'this is not plugin code' dangled after two inserted
   sentences about war-memory-lint.test.sh — a file that IS plugin code (skills/
   ships as the plugin), so 'this' could bind to the wrapper and read as the
   opposite of the truth. Fix names the referent: "This WORKFLOW is
   WAR-repo-only — it is not plugin code." Comment-only; name/on/permissions/
   steps byte-identical.

3. [Nit] No meta-test exercised the wrapper's DEFAULT target — all three cases
   override $1, so a default silently repointed at an existing wrong dir (e.g.
   docs/, which exists and holds no top-level .md) would pass every meta-test
   AND print "lint: clean" exit 0 in the gate forever. Fix: fourth case spawns
   the wrapper no-arg from tmpdir(), pinning the default target AND the
   cwd-independence the plan mandates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…prompt surfaces (#1131)

Sub-issue #1131 (#1082). A condition owned by a not-yet-landed deps-chained
sibling task read as provably-UNMET-HARD against an upstream task that fully met
its own slice — the recorded incident dodged the false hold only through auditor
judgment.

Extends the EXISTING case (3) ownership exemption to name both owners (never a
case (4)): the dispatched `endStateBlock` in workflow-template.js and the standing
`execution-evidence` checklist in agents/war-auditor.md, in one commit. Reuses the
`out-of-scope` finding-title token, so the handoff `endState` status derivation and
every downstream route are unchanged.

Case (3) now also interpolates the plan path so the seat can map conditions to
owning slices without guessing — GUARDED as `${(plan && plan.file) ?? '<unset>'}`:
`plan` is never entry-validated and `endStateBlock` renders at top-level scope, so
a bare read would throw phase-wide on a plan-less claims-bearing phase.

Tests (workflow-template.test.mjs):
- criterion-11 case-3 pin rewritten as ONE ordered CASE-BOUNDED regex (anchored on
  the literal `(3)` marker, lazy spans, terminating on case 3's own `NEVER a hold`)
- same-shape bounded pin for the new war-auditor.md bullet
- behavioral: a sibling-owned out-of-scope Nit → status out-of-scope, landed
- NEGATIVE: a Critical finding merely mentioning out-of-scope → unmet + held
  (pins the derivation's arm order)
- regression: plan-less + tasks:[] + non-empty phase.endState still dispatches,
  never held:workflow-error (sole coverage of the guarded interpolation)

Temp-break proofs: dropping the sibling clause from either surface alone reds
exactly that surface's pin; the bare `${plan.file}` reds the regression case to
held:workflow-error while the adjacent plan-less/zero-task test stays green;
swapping the derivation arms reds only the new negative case.

Records/comments swept: both workflow-template.js header comments and the
schemas.md `intent` parenthetical gain the sibling-task clause; the origin lesson
carries a RESOLVED (#1082) description prefix + a body note naming both surfaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… incident is above (#1131)

[Nit] Learnings RESOLVED note says "The incident below" but the incident
narrative is above it: the appended RESOLVED paragraph sits AFTER the
What-happened / The-rule / Why-it-matters narrative and immediately before
## Related, so its closing sentence 'The incident below is what the
carve-out encodes' pointed at nothing but the Related section. One-word
fix: below -> above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ckwards (#1132)

Lock-step is not monotonic: a coherent all-four-slots DOWNGRADE moves every
slot together, so the lock-step test passes it. That is exactly how a Gate-2
`docs(learnings)` commit staged from a stale publication worktree silently
reverted a landed release (#1083).

New fourth test in `version-slots.test.mjs` (the existing three are byte-
untouched — the file diff is pure insertion, 83 add / 0 delete): ONE bounded
git spawn from the suite's existing `repoRoot`

  git log --first-parent --diff-merges=first-parent -n 50 -p -- \
      .claude-plugin/plugin.json

parsed for `+`-side `"version"` values; `readSlots()`'s canonical
`plugin.json#version` must be >= the window max under a local three-component
NUMERIC semver compare. No unbounded history walk, no per-commit spawn, no
dependency.

Measured at this implementation base (never trusted from plan text):
  - window yields 50 version values, max 0.14.61; working tree 0.14.61 -> green
  - all 50 slot-touching first-parent commits are MERGES, so the entire signal
    comes from merge diffs. `--diff-merges=first-parent` is therefore passed
    EXPLICITLY: suppressing the implication (`--diff-merges=off`) yields a
    NON-EMPTY 17,408-byte log with ZERO version values. Since this suite runs
    in every refiner-dispatched gate, that is a campaign-wide brick — the
    non-vacuity assertion makes it loud instead of silent.

PROOFS (uncommitted scratch-fixture probe evidence; a resulting gate-audit
cannot-confirm is SOFT by recorded doctrine):

  RED proof (End state 5) — scratch git fixture, commit 0.99.9 then a coherent
  all-four-slots downgrade to 0.99.8:
    pass 3 / fail 1 — lock-step, extraction and README-prose tests all PASS;
    the monotonic test FAILS naming BOTH versions:
      "plugin.json#version (0.99.8) is BELOW the highest version in the last
       50 slot-touching first-parent commits (0.99.9) — the release tip moved
       BACKWARDS."

  Fail-open boundary, proven all three arms:
    (a) OPEN — non-git directory copy of the tree: 4/4 pass, note
        "fail-open — unusable git context, `git log` exited 128: fatal: not a
         git repository"
    (b) OPEN — git repo with zero slot-touching commits in the window (0-byte
        log): 4/4 pass, note "fail-open — empty log, no slot-touching commits"
    (c) CLOSED — merge-only fixture (the merge is the ONLY slot-touching
        first-parent commit, the live campaign shape) with the pin suppressed:
        non-empty 152-byte log, zero parses -> RED, "the PARSE is broken …,
        not an empty window; a broken parse never passes vacuously".
        The same fixture with the pin PRESENT is 4/4 green, so the
        `--diff-merges=first-parent` pin is proven load-bearing.

The assertion message names the incident class, the remedy, and the remedy
OWNER: the Lead/operator restores all four slots in ONE dedicated commit on
the affected branch tip — never a parallel task-branch edit, slot files are
release-class shared files — and the test stays red until that restore lands.

CLAUDE.md: the Releasing paragraph's `version-slots.test.mjs` sentence now
also names the monotonic floor and its fail-open boundary, keeping the
standing doc truthful.

Sweep (plan §4.4, this task's share; leader-strip -> whitespace-normalize ->
case-insensitive grep for "lock-step"):
  - CLAUDE.md — 2 hits, both in the amended Releasing paragraph (CONTROL fired)
  - skills/war/assets/version-slots.test.mjs — 9 hits (CONTROL fired); the
    pre-existing "Lock-step: each slot is asserted …" comment describes that
    test's own mechanism and stays true without the floor
  - README.md — ZERO hits: disposition MOOT at this base, exactly as the plan's
    red-team round 1 measured (the `## Status` blurb rotated to plan 4's
    0.14.61 release prose, which says nothing about version slots)
  - docs/learnings/gate2-…-revert-a-release-bump.md — 8 hits, Task 1.4's file:
    REPORTED, not edited. Its description ("the test checks lock-step, not
    monotonic increase") and body lines "locks the four release slots in
    lock-step … Lock-step ≠ monotonic" are now stale as of this commit — the
    `MITIGATED (#1083):` prefix and body note are Task 1.4's End state 8.

Gate green at this base: node --test 'skills/**/*.test.mjs' 933/933, shell
suite exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…touching commits, not raw first-parent commits (#1132)

Absorb three converging Nit findings on the same string (the empty-log
fail-open diagnostic in the monotonic-floor test):

1. [Nit] Empty-log fail-open diagnostic mis-describes the window bound
   (-n bounds OUTPUT commits, not traversal) and contradicts the assertion
   message — with the plugin.json pathspec, -n 50 limits commits OUTPUT
   after history simplification, so an empty log means ZERO slot-touching
   commits in the entire reachable first-parent history, not "the last 50
   commits happened not to touch the slot"; the assertion message already
   used the correct phrasing, so the two messages contradicted each other.

2. [Nit] Empty-log fail-open diagnostic misdescribes the window: -n 50
   counts slot-touching commits, not raw first-parent commits — verified
   empirically (the 50 returned commits span far more than 50 raw
   first-parent commits); only this one diagnostic drifted from the header
   comment and the RED assertion, sending a reader to the wrong place.

3. [Nit] Fail-open empty-log diagnostic mis-describes the window bound
   ("the last 50 first-parent commits") — git walks as far back as needed
   to collect 50 MATCHING commits; a single lagging string, not a mechanic
   defect.

Fix: reword the diagnostic to "git found no slot-touching first-parent
commits at all (window: the last ${WINDOW} slot-touching commits; shallow
clone, fresh history, or a moved plugin.json path)". Message-only; no
behavior change, no assertion weakened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…reuse refusal (#1133)

Closes the procedural and provisioning layers of #1083 — a Gate-2
docs(learnings) commit staged from a stale publication worktree silently
reverted a landed release while the lock-step version guard stayed green.

Three edits, one hazard:

- skills/war/SKILL.md — the Gate-2 promotion flow now lists the docs commit's
  staged file set (`git show --name-only --format= HEAD`) between the
  `docs(learnings): phase N` commit step and the `ensure-origin` push, and
  refuses to push when any path falls outside the resolved promotion
  destination or `CLAUDE.md`. This is the ROOT-CAUSE probe, one level above the
  version slots: the incident mechanism is a bulk stage of stale tracked files,
  so it catches a stale skill or hook the same way it catches a stale slot.
- skill-doc-contracts.test.mjs — new D22 row (next free number resolved from the
  file at implementation time) locking that duty with ONE ordered match:
  commit step -> `--name-only` probe -> do-not-push clause -> `ensure-origin`.
  One regex pins the pairing AND the position. Never two presence checks.
  RED proof: removing the probe token alone, and removing the do-not-push
  clause alone, each fail D22 and only D22 (11 pass / 1 fail, both ways).
- provision-worktrees.sh — `cmd_ensure_publication_worktree` behavior (b)
  (registered + present + HEAD already on the working branch) now runs the same
  `status --porcelain -uno` probe behaviors (c)/(d) use and refuses a dirty
  reuse via the EXISTING catalogued `$EX_WRONG_BRANCH`, naming the
  stale-staging hazard and the remove-then-re-provision remedy. Only ever
  refuses — never reset, never cleaned, never switched away. No new exit
  constant, no bare numeric literal, so the E-family census is untouched.

Deliberate deviations from spec §4.3, both plan-sanctioned:
- the probe is the staged FILE LIST, not the spec's version-slot re-read, which
  detects only the slot instance of the class;
- behavior (b) exits `$EX_WRONG_BRANCH` rather than the spec's default generic
  exit — the catalogue's own text classifies "a dirty tree we refuse to
  switch/remove" there, and (d) plus `remove-publication-worktree` already exit
  6 for the same class.

Header comments amended in the same edit: the byte-for-byte mirror sentence and
the "staleness is the CAS retry's job" claim now record the divergence — REF
staleness stays the CAS's job, WORKING-TREE staleness is this subcommand's
refusal, publication verb only (the refinery counterpart is a recorded
non-goal). The behavior-list (b) line and the test file's block header follow.

New test case P.8 pins the SPECIFIC exit code 6, never merely non-zero (a
crashed script also exits non-zero), plus the stderr hazard + remedy content and
the survival of the tracked modification. Delete-and-trace: removing the guard
drops P.8 to exit 0 and reds three assertions. P.2 keeps its UNTRACKED sentinel
at exit 0, so `-uno` stays proven.

The origin lesson gains a `MITIGATED (#1083):` description prefix (net shorter
than before — budget-safe) and a body section naming all three layers, including
the monotonic floor produced by Task 1.3 in this same phase. Two claims that
went stale with that floor ("does not assert the version increased",
"version-slots.test.mjs will not warn") are corrected in place.

Gate green: 933/933 JS tests, all discovered shell suites, redaction lint clean.
§4.4 sweep re-run post-change with its control firing — all three doctrine
tokens read accurately; SKILL.md still carries none, as the plan predicted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fore remove (#1133)

[Minor] Behavior-(b) refusal names a remedy its sibling verb deterministically
refuses (remove-publication-worktree is dirty-guarded on the identical probe).
The die at cmd_ensure_publication_worktree behavior (b) fires exactly when
`git -C <wt> status --porcelain -uno` is non-empty, yet instructed "Run
remove-publication-worktree '<path>', then re-provision" — and
cmd_remove_publication_worktree guards on the byte-identical probe, so step 1
of the named remedy is guaranteed to exit 6 in the very state that produced
the message. SKILL.md's Gate-2 entry heal scan and ADR 0022 (D) both reserve
remove-publication-worktree for the clean arm. Fix: insert the intermediate
commit-or-discard-by-hand step into the remedy sentence, keeping both P.8
grep tokens (remove-publication-worktree, re-provision) and the
stale-staging hazard naming intact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d-state exemption (#1131, #1133)

Phase-close coherence sweep, phase 1 "Evidence wiring + ownership carve-out +
release floor". Two queued findings, both doc-only; no runtime surface, no
version slot, no test pin touched.

[Minor] Evict flow now hard-reds every gate in this repo — migration.md's
"fails open once the dir is absent" is only true of CI (task 1.1,
skills/_shared/war-memory-lint.test.sh:34)
  The wrapper's default target IS docs/learnings/, so after an eviction PR
  merges a fresh clone has no such dir, the existence guard exits 2, and the
  gate's `bash "$f" || exit 1` aborts every refiner-dispatched gate. Recorded
  in the wrapper's own header comment block (the finding's primary remedy):
  an eviction must delete this wrapper in the same PR, and migration.md
  §Evict step 6's CI fail-open note is true of CI only. Chose the wrapper
  header over amending migration.md because that playbook ships to plugin
  consumers, who never discover this file (the gate's `find .` runs in the
  target repo tree) — a repo-specific "delete this wrapper" instruction there
  would be wrong for every consumer.

[Minor] design.md §18 still enumerates the single-owner End-state exemption
("later-phase → out-of-scope") — doc-cascade straggler outside the swept scope
(task 1.2, skills/war/references/design.md:133)
  The spec of record enumerated the gate-audit End-state cases with one owner
  where the dispatched `endStateBlock`, agents/war-auditor.md, and schemas.md
  now name two. Extended the enumeration in place with the deps-chained-sibling
  clause, matching schemas.md's landed wording. Straggler outside the plan's
  file-anchored §4.4 sweep set, so a survey-derived correction, not a
  regression.

Gate green: `node --test 'skills/**/*.test.mjs'` 942/942 pass; the discovered
shell loop runs 27 suites (including ./skills/_shared/war-memory-lint.test.sh,
which prints `lint: clean`), full gate exit 0.
Five project-typed lessons captured by the servitor after plan 5 phase 1 landed
(tip c663340): the three origin lessons updated with MITIGATED/RESOLVED status,
plus two new ones — the *.test.sh vehicle's interaction with the merge-path test
floor, and the leader-strip-before-normalize-before-grep doc-sweep ordering.

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

Bump the four release slots in lock-step to 0.14.62, the next free patch
above the live integration base: all four read 0.14.61 at this task's base
(phase-1 tip 73334aa), and the monotonic floor's 50-commit first-parent
window over .claude-plugin/plugin.json maxes at 0.14.61 as well. Resolved
from the slots as they stand, never from a plan literal (stacked campaign
releases lag cumulatively; predecessors landed .58/.59/.60/.61).

Slots: plugin.json version; marketplace.json metadata.version AND
plugins[0].version; README ## Status (replace-in-place, no badge).

The README Status blurb describes phase 1 precisely: the redaction lint now
rides the gate's existing *.test.sh self-discovery as a thin wrapper
(pre-merge, captured in the gate log, exit 2 on an absent target);
the gate-audit End-state ownership exemption names deps-chained sibling
tasks on both prompt surfaces via the existing out-of-scope token; and the
version guard gains a monotonic floor plus a Gate-2 pre-push
staged-file-list check and a publication-worktree dirty-reuse refusal
through the existing $EX_WRONG_BRANCH. It makes no claim that resolveGate,
any enum, any floor exit code, or CI behavior changed.

Gate green: 942/942 JS tests, full shell loop exit 0 — including the
monotonic floor, which this bump is the first to clear (End state 11).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… claimed a vacuous GREEN (task 2.1, README.md:340)

Queued phase-close finding [Minor]: "README blurb's unpinned-`--diff-merges`
counterfactual claims a vacuous GREEN — the landed non-vacuity assert makes it
a loud RED, and the blurb contradicts itself two clauses later (task 2.1,
README.md:340)".

Both halves were wrong against the landed tree, and both are re-measured here:

(a) Unpinned does NOT yield zero versions today. `git log --first-parent -n 50
    -p -- .claude-plugin/plugin.json` with no `--diff-merges` yields 50 version
    hits at this base — exactly what the source comment in
    `skills/war/assets/version-slots.test.mjs` says ("which modern git shows
    only because `--first-parent` IMPLIES that flag"). The zero-version result
    needs an explicit `--diff-merges=off`, and it is base-scoped: at the
    implementation base 73334aa the window is 50/50 merges and
    `--diff-merges=off` yields ZERO, but at this polish base the window is
    49 merges + this phase's own direct release commit, so the same command
    yields 1. An unqualified present-tense "yields ZERO" would ship a fresh
    false claim, so the blurb now mirrors the source comment's own attributed
    measurement ("50 of 50 at the implementation base") instead of asserting a
    counterfactual number.

(b) "green the suite vacuously" was contradicted by the landed non-vacuity
    assert AND by the blurb's own next sentence. With `--diff-merges=off` the
    log still emits commit headers, so `log.trim() !== ''` and
    `assert.ok(seen.length > 0, ...)` fires RED.

The clause after "**pinned explicitly** — " now states the load-bearing reason
(the whole signal comes from merge diffs, which git shows today only because
`--first-parent` implies that flag; the explicit pin keeps the window from
collapsing if that display default ever shifts) and leaves the RED claim to the
existing next sentence, which already carries it. Prose after the em dash only:
the `**0.14.62**` slot literal is byte-identical, no other file is touched.

Gate green: node --test 'skills/**/*.test.mjs' 942/942; the discovered shell
loop exits 0 over 27 suites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

Redaction lint is CI-only - wire war-memory lint as a gate-checkable floor step

1 participant