Skip to content

feat: audit fixes Phase 1+2+3 (R2/R11 drift, manifest SSOT, depcruise integration) - #1

Merged
artyhoo merged 19 commits into
mainfrom
feat/audit-fixes-2026-05
May 7, 2026
Merged

feat: audit fixes Phase 1+2+3 (R2/R11 drift, manifest SSOT, depcruise integration)#1
artyhoo merged 19 commits into
mainfrom
feat/audit-fixes-2026-05

Conversation

@artyhoo

@artyhoo artyhoo commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

Closes Findings 1, 2, 3, 5 from docs/audits/2026-05-06.md. Three phases of fixes:

Phase 1 — Quick wins

  • R2 wording drift killed across factory/RULES.md, agents/best-practices-sidecar.md, ESLint rule no-unsafe-zod-parse (Batches A, B).
  • R11 made executable: new .github/workflows/workflow-integrity.yml runs actionlint + zizmor + branch-protection assertion (Batch D + 2 fixups for SHA pinning).
  • Three-tier permissions block, expected-failures table, explicit Step 0 in INSTALL-FOR-AI.md and INSTALL.md (Batch C).
  • factory/ARCHITECTURE.template.mdfactory/ARCHITECTURE.ts-server.md (Batch C).
  • Stale audit-results-2026-05-06.md archived to docs/audits/2026-05-06.md with status block; new docs/audits/README.md documents the genre (Batch G).
  • Storybook 10 scaffold for templates/react-next/.storybook/ (Batch H).
  • BAD / GOOD snippets for every rule R1–R11, R12–R20, IR1–IR6 in RULES.md (Batch A).

Phase 2 — Manifest as SSOT

  • New factory/rules-manifest.json + JSON Schema declares all rules as data.
  • scripts/render-rules.ts regenerates the RULES.md summary table from the manifest (idempotent, --check mode).
  • manifest-render-check job in audit-self.yml blocks PRs that drift.
  • scripts/detect-applicable-rules.ts reads the consumer's package.json and emits INSTALL-DECISIONS.md per project.

Phase 3 — depcruise delegation

  • setup.sh runs npx depcruise --init, then layers R3 rules on top via rules-as-tests:layered block.
  • setup.sh runs npx storybook@latest init for the react-next stack and merges Batch H scaffold.
  • install.sh stops shipping .dependency-cruiser.cjs baseline (deferred to setup.sh).
  • install.sh gains --dry-run with aggregated skip warnings.

Not in this PR

  • Phase 4 (npm publishing) — separate umbrella with design doc first; requires semver commitment + TS port of setup.sh to bin/installer.mjs.
  • Phase 5 (spec-driven install) — depends on Phase 4.

Test plan

  • bash tests/audit/audit-ai-docs.test.sh — 5/0 pass
  • cd scripts && npm test — vitest 4/4 pass
  • npx tsx scripts/render-rules.ts --check — exits 0 (manifest ↔ RULES.md aligned)
  • bash install.sh ts-server --dry-run from tmp dir prints plan, writes nothing
  • On first PR after merge — workflow-integrity.yml self-runs against itself; all 3 jobs green
  • Manual sanity: bash setup.sh ts-server in a fresh tmp project hits depcruise --init interactive flow

Caveats

  • docs/meta-factory/ is local untracked WIP, intentionally not included.
  • templates/shared/eslint-rules/package.json still uses vitest run --reporter=basic; will need swap to --reporter=default when bumped to vitest 4.x (already swapped in scripts/package.json).
  • 16 commits including 3 orchestrator fixups; kept verbose history for traceability rather than squashing.

Commit map

Batch Commit(s) Files
A 86a19c1 factory/RULES.md, factory/RULES.react-next.md
B 8ff6d61 agents/best-practices-sidecar.md
C bb8c316, fa8c6f7 README, INSTALL*, factory/ARCHITECTURE rename
D 15dd378 + fixups 30ab6ee, e475a1d .github/workflows/workflow-integrity.yml
G 98b7904 docs/audits/*
H 8e8a490 templates/react-next/.storybook/*
I 5538e59, abafc77, 49942d7 factory/rules-manifest., scripts/
K 733f7f2, 3ea8524, d070a4a + fixup 937279a install.sh, setup.sh

artyhoo added 16 commits May 7, 2026 11:30
…mples for R1-R20

- R2: replace body with canonical path-scoped formulation (Policy/Path-scoped
  enforcement/Outside/Other boundaries/Escape hatch/Check structure)
- R11: replace manual-review Check with three-layer executable check referencing
  workflow-integrity.yml (actionlint, zizmor, gh api branch-protection assertion)
- R1-R11 (RULES.md): add ### Examples subsection after each Check line
- R12-R20 (RULES.react-next.md): add ### Examples subsection after each Check line
…pper)

rhysd/actionlint is the actionlint binary repo, not a GitHub Action
(no action.yml). The canonical action wrapper is reviewdog/action-actionlint.
Pin to v1.72.0 (commit SHA 6fb7acc) with fail_level=error.

Caught by IDE diagnostic + manual verification via gh api.
Self-application of R11 (workflow-integrity must itself be pinned).
- actions/setup-python@v5     → @8d9ed9ac (v5.5.0)
- github/codeql-action@v3     → @717e2306 (v3.30.0)

zizmor unpinned-uses would have flagged these on first run.
…-init

Phase 3 design intent: 'delegate cold-start config to depcruise --init,
then layer R3 rules on top'. install.sh was still copying the template
.cjs first, making setup.sh's '[ ! -f .dependency-cruiser.cjs ]' guard
a no-op (always false). Removed both copy_safe calls (ts-server and
react-next branches); setup.sh's fallback still copies the template if
'npx depcruise --init' fails, so the safety net stays.

Caught by junior in Batch K ATTN.
@socket-security

socket-security Bot commented May 7, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedvitest@​4.1.5961007999100
Addedtsx@​4.21.01001008184100
Addedajv@​8.20.09910010090100

View full report

artyhoo added 3 commits May 7, 2026 12:47
Three fixes after PR #1's first CI run revealed expected meta-failures:

1. audit-self.yml: pin actions/checkout and actions/setup-node to SHAs.
   zizmor's unpinned-uses audit (in workflow-integrity) flagged 6 hits;
   pinning eliminates the failure.

2. workflow-integrity.yml zizmor job: add 'security-events: write'
   permission so codeql-action/upload-sarif can post to the PR.
   Was silently failing with 'Resource not accessible by integration'.

3. workflow-integrity.yml branch-protection assertion: switch to
   tri-state semantics. Previously failed if no branch protection
   existed at all, blocking every PR in repos that haven't yet
   adopted R11. Now: pass-with-warning when no protection exists,
   pass when ci-success is required, fail only when protection
   exists but ci-success is missing (genuine R11 violation).
Upload SARIF step failed with 'Resource not accessible by integration' on
the workflow-runs API. The codeql-action/upload-sarif step requires either
GHAS (GitHub Advanced Security) enabled or specific 'actions: read'
permissions that vary by repo configuration.

For a framework that ships to arbitrary consumer repos (free public,
private without GHAS, enterprise), the SARIF integration is a brittle
dependency. Plain console output is sufficient: zizmor exits non-zero
on findings, that's what gates the job. PR readers see findings in
the workflow log.

zizmor itself passes — no findings remain after pinning audit-self.yml
SHAs (commit 4ddef39).
zizmor on .github/workflows/ now reports 0 findings locally.

Two fix categories:
1. artipacked (auto-fixed by 'zizmor --fix=all'): added 'persist-credentials: false'
   to all 6 actions/checkout invocations across audit-self.yml and
   workflow-integrity.yml. Prevents credential persistence in artifacts.
2. excessive-permissions (manual): added 'permissions: contents: read' to all
   5 jobs that lacked an explicit permissions block (mechanical, rule-to-probe,
   probe-tests, manifest-render-check in audit-self.yml; actionlint in
   workflow-integrity.yml). Default GITHUB_TOKEN permissions are too broad.

Also adds docs/audits/2026-05-07-self-application-gap.md documenting the
philosophical gap exposed by this PR: the framework declares 'every rule is
an executable test' but its own repo lacks pre-commit/pre-push enforcement.
Three fix-cycle iterations on this single PR (4ddef39, fd399e5, this commit)
were necessary because no local zizmor/actionlint hook gates the push.
Tracked as separate follow-up umbrella 'chore/self-application'.
@artyhoo
artyhoo merged commit 35ab3f9 into main May 7, 2026
9 checks passed
artyhoo added a commit that referenced this pull request May 8, 2026
Phase 8.8.1 addendum push-based health check on 12 non-cascade §13.x
triggers (excluding §13.2 narrative, §13.3/§13.6/§13.7/§13.9 closed,
§13.11 cascade, §13.10 #5 cascade, §13.15 unused). For each entry:
decompose trigger into observable signals → run verification probe →
classify FIRED / STILL ARMED / CASCADE-DEPENDENT.

Result: 12/12 STILL ARMED or PENDING T7. Zero FIRED. No per-fire
trigger-fire-§<N>.md patches needed.

Probes consulted:
- §13.1: research-store file count (12 at 1-pattern-per-file granularity);
- §13.4 / §13.5 / §13.10 #4 / §13.12 / §13.13: consumer evidence (none);
- §13.8: self-application.md §3 row count (9, no 10th proposal);
- §13.10 #1: Phase 8 closure status (no curated-store gap);
- §13.10 #2: Phase 9 entry coverage gap recorded f92f60b — DEFER carries forward;
- §13.10 #3: Phase 9 implementation scope (housekeeping only — no new pattern);
- §13.14: rules-lock.json schemaVersion = 1 (no bump);
- §13.16: PENDING T7 self-review.

Sweep adds value most when external signals (consumer issues, version
bumps) accumulate between sessions OR a phase fires a trigger the
originating session didn't notice. Both vectors quiet at 2026-05-08;
unsurprising outcome.

Re-sweep cadence: next phase entry research session. T6 distillation
folds the sweep methodology into rule §1 as 6th checklist item.

Prior-art: skipped — docs-only sweep report companion to T5.5 audit, no new capability surface or explicit dependency added (per CLAUDE.md capability-commit definition).
artyhoo added a commit that referenced this pull request May 10, 2026
…r reviewer REVISE

Closes 1 BLOCKER + 2 MAJOR + 1 MINOR from reviewer pass on 98ef3ba.

P1 (B1) — §10 SSOT schema fixed to 8 columns matching prior-art-evaluations.md §4 precedent (entries #1-#15):
ID | Candidate | Capability matched | First seen | Last reviewed | Verdict | Rationale | Trigger to revisit
Velocity moved from 9th-pseudo-column into Rationale field opener per #6-#10. First seen / Last reviewed = 2026-05-10 for all #16-#21.

P2 (M1) — context7 phrasings backfill:
- §1 (O1): +2 new queries (`/modelcontextprotocol/servers`, `/vercel/platforms` resolve). Material correction: SECURITY.md states reference servers «not production-ready»; §1 maturity column downgraded from «Mature/Official» to «Reference».
- §3 (O3): +1 new query (`/websites/windsurf`); other 7 documented as «derived from O0 sweep».
- §4-§8: each section gets one-line motivation explaining why no new query was needed (analytical / cognitive-probe / pattern-comparison objectives).

P3 (M2) — rule-5 counter-prompt added to §0 as item 3:
2 phrasings against `/renovatebot/renovate` + `/sverweij/dependency-cruiser` + `/tj-actions/changed-files`. NULL result. Confirms «incrementality is gap in every surveyed framework» negative-existence claim. §13.3 `#negative-existence-claim` raised LOW → MEDIUM with explicit residual-risk acknowledgement.

P4 (m1) — Windsurf gap closed AND classification raised:
- §3 row revised: Windsurf Cascade detect-and-install + MCP Marketplace one-click deeplink + `pre_mcp_tool_use` hook surfaced. Second partial-end-to-end candidate alongside AIF.
- §3 conclusion updated: «AIF and Windsurf Cascade» (not just AIF) carry the discover-env-→-propose/install shape.
- §13.3 `#category-sweep-missed` LOW → MEDIUM. `#prompt-list-anchoring` description revised to acknowledge floor-as-ceiling failure caught at reviewer pass.
- §13.4 self-reflexive checklist updated: 3 counter-prompts (was 2); §1.5 line acknowledges reviewer-driven gap closure.

File: 486 lines (under 500 cap). 14 sections intact. Principle 09 doc-authority test green.

Prior-art: skipped — follow-up edit closes reviewer findings on existing research-patch; no new capability per CLAUDE.md hook definition; SSOT proposals (§10) still land in orchestrator session per prior-art-evaluations.md §3 step 1.
artyhoo pushed a commit that referenced this pull request Aug 6, 2026
…un, correct the coverage predicate

MINOR A (W-7) — round 2 re-quoted the sibling patch onto the 22:36:13Z run under an
explicit "single-sourced" claim, but three P14 figures stayed on the superseded run, so
the two patches disagreed where they had previously agreed. Synced to the sibling's
current run: row 6 plugin SessionStart inject 4,618 B / 184 firings -> 4,581 B / 190
(est-tokens 1,155 -> 1,145); row 7 subagent digest 724 -> 728 firings; R3 0.49% -> 0.48%
of total weighted spend [H]. R3's firing rate is NOT force-matched: "10.3 firings/session"
became "10.07 firings/transcript" to adopt the sibling's basis, with the per-session
equivalent (1,904/185 ~ 10.3) stated in-line so the two bases are visibly reconciled
rather than silently swapped.

MINOR B (W-8) — the T6 coverage predicate claimed "13 enumerated; 7 MEASURED, 5
UNMEASURED, 1 source-side" while the table holds 14 rows of which exactly 4 carry the
literal UNMEASURED marker. Round 2 made this self-contradicting in-file, since the new
§0a already named the correct set (5c, 5d, 5e, 9). Predicate corrected to the true
partition 14 / 9 / 4 / 1, with the row ids enumerated. The counts were re-derived by
counting the table mechanically, not restated from memory. No row's status changed and
no UNMEASURED row was back-filled (T-SH-A / W-4) — the table is the authority, the
predicate was the defect.

Numbers-sync only: the corpus was not re-run and no measurement was re-derived.
DECISION-NEEDED #1, #2 and #3 remain intact and unresolved; item-4 branch (c) unchanged;
no /context workaround built.

Prior-art: skipped — figure synchronisation and a counting correction on existing research patches, no new capability
artyhoo pushed a commit that referenced this pull request Aug 6, 2026
Round 3 surfaced a third instance of each of the two classes already fixed twice. The
defect was the method: fixes were applied site-by-site from a handed list, so each round
surfaced another site nobody had enumerated — #backward-check-restates-not-sweeps (T21)
applied to my own corrections. Both classes are now enumerated mechanically and swept to
completion; the enumerations, including the CLEAN rows, are the deliverable.

Class A (W-7) — every numeric claim in the P14 patch that is sourced to, shared with, or
derivable from the sibling patch was enumerated and checked against the sibling's current
single-sourced run block. 16 shared figures verified equal; one site was MISMATCHED, and
it was the only one: the `paths:` edit-time channel line, which reproduced exactly on the
superseded 185-session denominator (175/185=0.95, 105/185=56.8%) instead of the quoted
run's 189 (175/189=0.93, 105/189=55.6%), with the follow-on ~43% diverging from the
sibling's ~44%. All three corrected. No figure was force-matched: R3's per-transcript vs
per-session basis stays distinguished with its basis stated in-line, as accepted last
round.

Class B (W-9) — every count-claim about the price table was enumerated and checked
against a mechanical recount (14 rows; the literal UNMEASURED marker on exactly 4).
Two claims said five UNMEASURED rows and are corrected to four, naming the rows; one
further claim ("Rows 5c/5d/5e are the only blocks with no channel") was imprecise, since
row 9 also carries the marker outside the seat total, and is now stated exactly. Resolved
toward four throughout: no row's status changed and no UNMEASURED row was back-filled,
which would have been the T-SH-A / W-4 violation. The §0a heading "five blocks stay
unpriced" is correct and untouched — five unpriced blocks = four UNMEASURED rows plus
row 8 priced source-side only; that distinction is now made explicit at both corrected
sites so the file no longer argues four and five at once.

Patches remain single-sourced on the 22:36:13Z run; the corpus was not re-run and no
newer figures were propagated. DECISION-NEEDED #1, #2 and #3 intact; item-4 branch (c)
unchanged.

Prior-art: skipped — class-wide enumeration and count corrections on an existing research patch, no new capability
artyhoo added a commit that referenced this pull request Aug 7, 2026
 (#1250)

Operator verdicts 2026-08-07, recorded where the consumer reads them rather than by editing the
S-H research patches (append-only, and read-only for later sessions per the Artifact Ownership
Contract).

#2 = Option A — [H] operative from S-H onward, [W] historical. Recorded at the spec's binding
denominator convention. This is a constatation, not a preference: [W]'s 169-session subset is
physically unrecoverable, because worktree pruning deleted 34 project directories together with
their transcripts (turn-attribution patch :112, cause :92). Binding consequences spelled out —
existing [W] shares stay readable as history but are not comparable past 2026-08-07, no new figure
may be tagged [W], and every [W]-defined threshold is re-adjudicated on [H] or marked
un-adjudicable. The one such threshold is N1's falsifier: annotated in place as un-adjudicable as
written, with its operative [H] restatement and the standing reading 16.5% > 5% (so the discipline
text STAYS), plus the explicit non-conversion caveat the patch itself carries at :168.

#1 = Option B — host-side snapshot of the per-turn billing projection BEFORE any worktree prune.
Recorded as an INPUT CONDITION on the S-D′ kickoff, whose before/after measurement is the consumer
that a shrinking corpus invalidates (-23.5% between the seed and S-H: 247 -> 189 files, 99 -> 65
project dirs). Form is reuse, not build: copy the shape of the existing origin/data/metrics channel
(one appended CSV row per day on a data branch) but NOT its mechanism -- that is a CI cron
(.github/workflows/metrics-collect.yml) collecting GitHub popularity counters, and the corpus lives
at ~/.claude/projects where CI cannot reach. Authoring the snapshotter is explicitly NOT scoped
into S-D′; the clause states the precondition and its form only.

Also records, as the input condition's footnote, that S-D′ kickoff §5 = Option A (operator verdict
same date): with the ADR-8 A/B arm descoped, one baseline consumer remains -- this stage's own
before/after -- and ADR-8's 20-dispatch window travels to the follow-on stage. The §5 resolution
itself lands in kickoff rev 6, a separate PR.

Every citation introduced here was verified mechanically against the cited line, not from recall.

Prior-art: skipped — records operator decisions into an existing spec and kickoff, no new capability

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 7, 2026
…nds, GO) (#1283)

## Summary

Pre-dispatch Phase -1 cold review (1× Opus, 3 rounds → GO) found the merged kickoff undispatchable: step 1 was physically unexecutable (the cell script's EXIT trap deletes the fixture on every exit, `pnpm-monorepo-cell.sh:52`), the container preflight was unstated, and the investigation's strongest lead was missing — `packages/core/audit-self/check-rule-enforced.sh:197` swallows stderr and ignores rc of `eslint --print-config`, so a transient eslint crash prints the exact same «SILENTLY INERT» line as genuine rule inertness. This revision folds in all 2 BLOCKER + 9 MAJOR/MINOR findings; dispatch happens only after this merges (kickoff-staging-placement.md §1).

## Changes

- Step 0 (new): container preflight — node 22, `corepack enable` (pnpm@9.12.3 download), mandatory `FRAMEWORK_ROOT` export; a preflight red is an ENV failure, not a reproduction.
- Step 1: trap-stripped scratch copy + per-iteration `$WORK` echo; IN-SITU rc+stderr capture via anchored sed patch of the consumer's check copy (post-hoc re-runs classify nothing); disk policy (rm on pass, keep failures); run-#1 timing + N≥10 fallback with stated coverage.
- Step 4: binding H2 discriminator on the in-situ record + third outcome (post-hoc rule-present ⇒ transient); gh-unavailable fallback pre-answered.
- Evidence: «root config» label corrected (cwd-relative, `check-rule-enforced.sh:193`; `:146` is a separate invocation); unpinned CORE_DEVDEPS range (`setup.d/70-deps.sh:158-167`, `:297`) named as step 3's target; exact template path (`packages/core/audit-self/check-rule-enforced.sh` → consumer `scripts/` via `install.sh:873`).
- host-verify: bounded 3-roll smoke that can actually fail (`[ "$fails" -lt 3 ]` — the previous trailing-echo form always exited 0, reviewer-probed), `HOST_VERIFY_TIMEOUT=3600` mandated.
- Acceptance: `done.md` default (outside principle 09 scope); research-patch CI gates enumerated (principle 10 scope line, §1.7, sections, ≤100 LOC).

## Prior-art consult

- [x] No capability commits — docs-only kickoff revision.
- [ ] New capability area: n/a.
- [ ] Existing entries: n/a.
- [ ] context7 queries: n/a.

## Test plan

- [x] Phase -1 cold review rounds 1-3 (same reviewer, findings list in commit body); round 3 = GO, both MINOR residuals folded in
- [x] Reviewer empirically probed the host-verify one-liner (bash -n OK; rc=1 at 3/3 fails, rc=0 below)
- [x] check-kickoff-traps.sh edit-time hook green; markdownlint green at commit
- [x] CI on this PR green

## Provenance

n/a

## Review findings

Phase -1 pre-dispatch review, 3 rounds: R1 = 2 BLOCKER + 5 MAJOR + 4 MINOR; R2 (after fixes) = 4 MAJOR + 2 MINOR; R3 = GO + 2 MINOR (both folded in). Full lists in the commit body and session transcript.

## Fidelity verdict

FIDELITY: skipped — docs-only kickoff revision PR, no stage pipeline applies

## Parked questions

n/a

## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files)

### §1.7 Skipped: docs-only revision of an orchestrator-prompts kickoff; no discipline rule introduced or extended, follows existing conventions
artyhoo added a commit that referenced this pull request Aug 8, 2026
…e2becc66 — consumer-matrix-pnpm-flake (#1289)

## Summary

Harvest of the consumer-matrix-pnpm-flake investigation (aif task `66c203e4`). Verdict: **(c) INCONCLUSIVE** — 20/20 in-container reproduction runs passed with the in-situ rc+stderr capture armed; zero failing invocations captured, so the binding discriminator (crash-shaped vs genuine-inertness) had no input. Statistical note: at the observed ~30% CI rate, 0/20 has p≈0.0008 — most plausibly the CI burst was a transient registry-window that has rolled forward; the warm-store-vs-cold-runner gap is recorded as the unclosed alternative. No retry wrapper proposed (T-CMF-A). Reopen trigger armed on the exact CI signature.

## Changes

- `.claude/orchestrator-prompts/consumer-matrix-pnpm-flake/done.md` — the umbrella's research note: reproduction stats, verdict evidence (every file:line re-verified by the cold auditor), environment-gap table, reopen trigger, 3 drive-by observations for maintainer triage (`:197` stderr/rc swallow fix; 16/24 unpinned CORE_DEVDEPS; NODE_ENV=production dep-omission), host-verify contract recorded verbatim.
- Harvest-side edit: two broken links fixed (container-only `.ai-factory/plans/` path de-linked; `../../CLAUDE.md` → `../../../CLAUDE.md`) — caught by the host pre-push link gate.

## Prior-art consult

- [x] No capability commits — docs-only investigation note.
- [ ] New capability area: n/a.
- [ ] Existing entries: n/a.
- [ ] context7 queries: n/a.

## Test plan

- [x] Host pre-push gates green (link check passed after fixes; full hook ran in provisioned worktree)
- [x] Cold fidelity audit round 1 = GO on the audited SHA
- [x] Host-verify smoke executed on the HOST (Darwin): «host smoke: 0/3 failed», host-verify 1/1 passed (`HOST_VERIFY_TIMEOUT=3600`; host env gap per T14: node v24.3.0 vs CI 22, global pnpm 11.9.0 vs fixture-pinned 9.12.3)
- [x] CI on this PR green

## Provenance

kickoff: `.claude/orchestrator-prompts/consumer-matrix-pnpm-flake/kickoff.md` (staging `025aac054c`) · substrate: aif task `66c203e4-bbac-43b1-b02d-3345e2becc66`, no bridge-profile marker (Tier 2 → project defaults: plan = Claude Opus (plan+review), implement = Z.AI GLM-5.2 SDK) · fidelity Round 1 on `3b201a6569c535c378744201a1f7d8121eaaaed3`

## Review findings

Factory review: task completed the aif pipeline (implementing → done). Cold fidelity audit round 1: GO with 4 MINOR (no cold-store run-#1 datapoint; step-2 "plugin-unresolvable window" answered only implicitly; reopen recipe cites ephemeral container path, recoverable from kickoff; host-verify owed) and one KICKOFF-AMBIGUOUS routed to the maintainer: the kickoff calls the `check-rule-enforced.sh:197` stderr/rc fix "a deterministic improvement on ANY outcome" while §Acceptance conditions any fix PR on verdicts (a)/(b) — the diff descoped it yet made it the precondition of its own reopen trigger, leaving that precondition unowned.

### Watch-list

| id  | criterion                                                                                  | why                                                                                                          | defect site                                                                     | reintroduction tell                                                                                                                                                          |
| --- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| W-1 | kickoff §To-investigate 4 + T-CMF-A — no retry/rerun wrapper or quieting of the (c) gate     | hypothesis 2 is unruled-out, not disproven; a retry makes a real intermittent installer defect permanently invisible | none — preventive (no code changed this round; stated at `done.md:12`)             | any `continue-on-error`, retry loop, `\|\| true`, or widened `2>/dev/null` around `tests/consumer-matrix/pnpm-monorepo-cell.sh` or the cell step at `.github/workflows/audit-self.yml:1503-1504` |
| W-2 | kickoff §Acceptance + step 4(c) — verdict stays (c) INCONCLUSIVE with coverage-as-predicates | 0/20 is a coverage statement, not an absence proof (T14); an upgraded verdict silently retires the reopen trigger | none — preventive (`done.md:8`)                                                     | any later edit replacing «neither ruled out nor confirmed» with «no bug» / «infra flake» / «resolved», or deletion of the `## Reopen trigger (binding)` section                    |
| W-3 | kickoff §To-investigate 2 — the `:197` swallowed-stderr/rc defect stays recorded as open     | the reopen trigger's discriminating power depends on it; if it drops, the next occurrence yields the same ambiguous signature and the investigation restarts from zero | `packages/core/audit-self/check-rule-enforced.sh:197` (unchanged; surfaced at `done.md:77`) | observation 1 removed from `done.md`, or a follow-up PR touching `:197` cosmetically without adding rc capture plus a distinct crash-shaped message                                |
| W-4 | kickoff §Acceptance host-verify — the host smoke is owed and is not the container run        | a container-green result is not host evidence — accepting on it is `#container-green-as-acceptance`             | none — preventive (`done.md:85` declares it unexecuted)                             | an acceptance note or PR body citing «20/20 passed» as the host-verify result, or the `HOST_VERIFY_TIMEOUT=3600` override dropped from the operator step (a 900s kill then misreads as failure) |

Round 1: W-1 CLEAN · W-2 CLEAN · W-3 CLEAN · W-4 CLEAN

## Fidelity verdict

FIDELITY: GO
Basis: .claude/orchestrator-prompts/consumer-matrix-pnpm-flake/kickoff.md
Round: 1
Audited-SHA: 3b201a6
Evidence: .claude/orchestrator-prompts/consumer-matrix-pnpm-flake/done.md:8

## Parked questions

- KICKOFF-AMBIGUOUS (routed to maintainer): fix `packages/core/audit-self/check-rule-enforced.sh:197` (capture rc+stderr, distinct crash-shaped message, ~10 LOC) in a separate PR? It is the stated precondition of the reopen trigger and currently unowned.
- Drive-by observation 2 (separate PR candidate): pin the 16/24 unpinned `CORE_DEVDEPS` at `setup.d/70-deps.sh:158-167`, or extend the unpinned-tool-install gate to array-data-driven installs.

## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files)

### §1.7 Skipped: docs-only investigation note under orchestrator-prompts; no discipline rule introduced or extended
artyhoo added a commit that referenced this pull request Aug 8, 2026
…GUOUS + host-verify contract (#1310)

* docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev)

Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A +
description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded
with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1
iteration, external cold fidelity mandatory; Park-4 CC detection =
CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park
did not fire (live runtime-profiles probe, no substring collision); Park-6 ship
the full functional set (create-worktree.sh + worktree-node-modules.sh +
link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added.
Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3).

* docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract

Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300).
The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores
only the env-var NAME in the profile, #4(ii) writes the value to
~/.config/getff/glm.env, and #3 requires the validation ping to run through the
created profile. Verified against source: aif resolves the key from its own runtime
process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219,
:247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) —
so glm.env is a dead end as written and #3 is unreachable without dereferencing the
value, which #1 forbids.

§7b closes it by binding the OUTCOME (value reachable in the aif runtime process env
under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure)
while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked
as a dispatcher-authored completion, not an operator decision — the operator can
override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full
body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id
(:90), both defaults halves, ping through the profile, 10-skills.sh untouched.

§4.1 adds the missing host-verify contract — the stage ships a shell helper, an
install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host
surface destination-environment-verification.md §1 exists for; the umbrella had no
contract on any of its six kickoffs (exit 2).

Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an
ambiguity in an existing stage kickoff against in-repo source citations.

---------

Co-authored-by: Test <test@example.com>
artyhoo pushed a commit that referenced this pull request Aug 8, 2026
…r; the ping is two halves

§7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through
`POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a
reachability check. The dispatcher fired that falsifier itself rather than shipping the trap:

- for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey`
  and `baseUrl` are non-empty — no network call (aif-handoff
  packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the
  Qwen3.8-Max-Preview profile returned ok:true sub-second.
- `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue
  (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider.

So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves —
`/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK
from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is
stated as dispatcher-authored and operator-overridable, matching §7b.

§7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and
`resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env
(packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS
proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.
artyhoo pushed a commit that referenced this pull request Aug 8, 2026
#1318 landed a §7d on the same file while this branch was in flight, so the block authored here is
renumbered §7d → §7e and rebuilt on top of #1318 rather than over it. #1318's content is kept intact.

Two corrections to §7d, both evidence-backed:

§7e.0 — §7d step 1 orders `git merge feature/beta-delivery-ux-92bf00`, which is run 2
(task 92bf0019, 2026-08-08T01:04Z). Run 3 (task e65989fa, commit 53fce45, 14:51Z) is newer and
already carries every §7c correction. Measured: getff-glm-onebutton.sh is 212 lines on 92bf00, still
holding `PATCH "$AIF_URL/project"` (:135) and the vendor-direct ping (:171), versus 361 lines at
53fce45 with the profile-routed path in place. Basing on 92bf00 re-does ~150 accepted lines and
re-opens two closed defects.

§7e.3 — §7d.1 states `POST /runtime-profiles/validate` «exercises the route the flow just built» and
is «exactly what §7a #3 / §7c #3 ask for». For `transport: "api"` it makes no network call at all —
it checks only that apiKey and baseUrl are non-empty (aif-handoff
packages/runtime/src/adapters/claude/index.ts:468-479); live, `/validate` against the
Qwen3.8-Max-Preview profile returned ok:true sub-second, and /runtime-profiles/models returned a
static Claude catalogue for that same Qwen profile. The ping is therefore split in two: /validate for
the route proof, plus one 1-token-scale completion against the baseUrl read back from that response.

Carried over unchanged in substance: §7e.1 (host-path retraction + the container base-URL trap —
localhost:3009 → 000, api:3009 → 200), §7e.2 (runtimeId/providerId required), §7e.4 (§7b #1
undelivered; hasApiKey as its exact value-free verifier), §7e.5 (companion install in a dead field),
§7e.6 (fail-closed mock, it.fails-as-delivery banned). §6 gains T-BDU-D and T-BDU-E.

Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.
artyhoo added a commit that referenced this pull request Aug 8, 2026
… path, bind run-3 blockers (#1319)

* docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev)

Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A +
description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded
with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1
iteration, external cold fidelity mandatory; Park-4 CC detection =
CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park
did not fire (live runtime-profiles probe, no substring collision); Park-6 ship
the full functional set (create-worktree.sh + worktree-node-modules.sh +
link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added.
Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3).

* docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract

Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300).
The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores
only the env-var NAME in the profile, #4(ii) writes the value to
~/.config/getff/glm.env, and #3 requires the validation ping to run through the
created profile. Verified against source: aif resolves the key from its own runtime
process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219,
:247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) —
so glm.env is a dead end as written and #3 is unreachable without dereferencing the
value, which #1 forbids.

§7b closes it by binding the OUTCOME (value reachable in the aif runtime process env
under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure)
while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked
as a dispatcher-authored completion, not an operator decision — the operator can
override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full
body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id
(:90), both defaults halves, ping through the profile, 10-skills.sh untouched.

§4.1 adds the missing host-verify contract — the stage ships a shell helper, an
install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host
surface destination-environment-verification.md §1 exists for; the umbrella had no
contract on any of its six kickoffs (exit 2).

Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an
ambiguity in an existing stage kickoff against in-repo source citations.

* docs(beta-delivery-ux): S4 kickoff §7d — retract the unreachable host path, bind run-3 blockers

§7d.0 retracts the §1 + §7a #1 instruction to re-read
`~/code/aif-handoff/packages/api/src/routes/runtimeProfiles.ts` at stage entry. That path is
host-only: the agent container mounts `$PROJECTS_DIR` (= `$PROJECTS_HOST_ROOT/projects`), so the
repo root holding `packages/api/` sits one level above the mount. The instruction was unexecutable
and run 3 guessed instead of parking — same class as state.md §2 decision 14.

Replacement is an oracle the container can actually reach: the live aif API. §7d.1 pins two probes
(required-field discovery + route-existence discovery) with the dispatcher's own 2026-08-09 outputs
recorded for reproduction, not for trust.

Run-3 blockers bound as corrections: §7d.2 `runtimeId`+`providerId` required in the create body;
§7d.3 the ping route is `POST /runtime-profiles/validate` (the guessed `/<id>/v1/messages` → 404);
§7d.4 §7b #1 still undelivered (the helper verifies reachability but never establishes it);
§7d.5 companion install is prose in a field `engine.sh:18` early-returns past; §7d.6 (MAJOR) the
suite mocks the non-existent endpoint and a body-blind 201, so §4.1 passed over §7d.2/§7d.3 —
the stub becomes fail-closed with a paired-negative that fails on the run-3 helper, and
`it.fails()`-as-delivery is banned.

Adds T-BDU-D: unreachable instructed source → probe, else PARK; never substitute a plausible shape.

Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.1 — name the real base-URL var, and the container trap under it

Own cold-QA of #1319 caught two defects in §7d.1. (1) It invented `$AIF`; the helper actually
resolves `AIF_URL="${RUNTIME_BRIDGE_AIF_URL:-http://localhost:3009}"`
(scripts/getff-glm-onebutton.sh:94), so the probe block now uses that name.

(2) The more load-bearing half: that shipped default is unreachable from the agent container.
Measured 2026-08-09 from aif-handoff-agent-1 — `http://localhost:3009/runtime-profiles` → curl
exit 7 (000), `http://api:3009/runtime-profiles` → 200. `RUNTIME_BRIDGE_AIF_URL` is unset in the
container, so an in-container run of the run-3 helper could not have reached aif at all, which is
consistent with the suite mocking every call (§7d.6). §7d.1 now instructs the export for probing,
explicitly forbids changing the shipped consumer default, and §4 item 1 must quote the base URL
alongside any live end-to-end transcript.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.3/§7d.4 — fire my own falsifier; the ping is two halves

§7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through
`POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a
reachability check. The dispatcher fired that falsifier itself rather than shipping the trap:

- for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey`
  and `baseUrl` are non-empty — no network call (aif-handoff
  packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the
  Qwen3.8-Max-Preview profile returned ok:true sub-second.
- `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue
  (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider.

So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves —
`/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK
from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is
stated as dispatcher-authored and operator-overridable, matching §7b.

§7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and
`resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env
(packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS
proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 9, 2026
…with the three false greens fixed (#1323)

* feat(beta-delivery-ux-s4): GLM one-button helper + aif-handoff guided-install helper + entry verification

S4 (spec §4 A2 + A1) — the GLM executor tier connects with ONE human-entered key, and the
factory profile UPGRADES the aif-handoff companion row from S1's detect+instruct declaration
to a consented guided INSTALL.

Three artefacts:
- scripts/getff-glm-onebutton.sh — the one-button automation (detect | explain | provision).
  Executor of the flow = the consumer's in-session AI agent reading an INSTALL-FOR-AI step
  (the aider pattern). detect probes /runtime-profiles for a Z.ai-shape profile; explain
  prints the one explanation block (z.ai Coding Plan, env-file path); provision runs REST
  create + per-mode defaults + validation ping (real model call per §7a #3).
  KEY-HANDLING INVARIANT (§4 item 2 — binding): the helper references ONLY the env-var NAME
  ANTHROPIC_AUTH_TOKEN; the value lives only in the untracked env file the human creates.
- setup.d/aif-handoff-guided-install.sh — the consented guided INSTALL for the factory profile.
  Sources bridge-guided.sh for bridge_diagnose (SSOT per dual-implementation-discipline.md §7);
  states: up (no-op, detect-first) | docker (consented git clone + docker compose up -d +
  30s health wait) | native (instruct start) | absent (env-level degrade). Decline → env-level
  degradation is a DESIGNED SUCCESS PATH (kickoff §6 T-BDU-B).
- docs/meta-factory/research-patches/2026-08-08-s4-glm-onebutton-entry-verification.md — the
  entry verification record. Bridge unreachable + schema inaccessible at build time (both
  explicit §7 park triggers); §7a resolutions implemented on operator authority (binding) with
  falsifiers UNRESOLVED at build time. engine.sh does NOT gate on @Profile: (comment-only
  convention — the helper is invoked from install.sh under PROFILE=factory).

Prior-art: skipped — bash helpers under scripts/ and setup.d/, not packages/; not a capability-commit per CLAUDE.md definition (no new package.json dependency, no new file ≥50 LOC under packages/core/<new-dir>/, no new file ≥80 LOC under packages/).

* feat(beta-delivery-ux-s4): wire S4 automation into install.sh factory gate + INSTALL-FOR-AI one-button step + tests

Four wiring changes completing S4's scope:
- setup.d/companions.manifest — aif-handoff row install_cmd upgraded from the S1 placeholder
  to the real pointer: setup.d/aif-handoff-guided-install.sh under --profile factory. The
  @Profile: factory marker (S1) is preserved; engine.sh contract unchanged (kind=external-service
  → print + return 0; actual handling routes through the helper invoked from install.sh).
- install.sh — factory gate added after the setup.d layer loop (after line 1115). Gating
  matches setup.d/10-skills.sh:95 exactly: PROFILE=factory OR WITH_AIF_SUITE set. The helper
  is invoked via `bash ... || true` (decline → env-level degrade is a designed-success path).
- INSTALL-FOR-AI.md — new "GLM executor one-button (factory profile ONLY)" subsection under
  the depth-profiles section. Documents the detect→explain→provision flow the consumer's
  in-session AI agent runs; carries the key-handling invariant sentence.
- tests/install-sh/glm-onebutton.test.sh — 20-assertion firing test covering: (a) detect
  cases (present|missing|bridge-unreachable), (b) explain output (env-file path + env-var name
  + cost line + parent dir), (c) provision cases (happy path, step-A failure, preflight
  env-file missing), (d) key-handling invariant grep (no echo/printf/log/_log/_warn references
  the env-var name; indirect expansion present in curl header). Snapshot baselines
  byte-identical (companions.manifest is not part of the installed-tree fingerprint).

Stage boundary respected: setup.d/10-skills.sh and .zcode/ are NOT touched (S5's sole-editor
scope for skill-set wiring). packages/runtime-bridge is NOT touched (S5 vendoring scope).

Prior-art: skipped — wiring edits + bash test; not a capability-commit per CLAUDE.md definition (no new package.json dependency, no new file ≥50 LOC under packages/core/<new-dir>/, no new file ≥80 LOC under packages/).

* fix(beta-delivery-ux-s4): repair one-button helper per §7b/§7c/§7d — PUT /projects/:id, profile-routed validate, key-reachability preflight

Round 3 rework of scripts/getff-glm-onebutton.sh — fixes the four §7c dispatcher
defects from run-2 plus the §7b key-reachability gap (resolved 2026-08-09):

- §7c #1: replace invented PATCH /project (singular, no id) with PUT /projects/:id
  using a full createProjectSchema body (jq-mutated from GET /projects response).
  Aligns with packages/runtime-bridge/src/cli/aifHttp.ts:96 which documents
  PUT /projects/:id as the only parallelEnabled write path.
- §7c #2: write BOTH Task+Review halves of per-mode defaults, with distinct
  preserve-vs-park branches for the Plan half based on defaultPlanRuntimeProfileId
  read from GET /projects. Conflating them silently overwrites a consumer's
  existing top-tier or silently writes a null.
- §7c #3: route the validation ping through POST $AIF_URL/runtime-profiles/validate
  with payload {profileId:$id} — proving key + model + route together via aif.
  Run-2 pinged the Z.ai vendor URL directly, bypassing the runtime the flow built.
- §7c (implicit): drop the invented x-api-key header attribution.
- §7b: print docker-compose env_file wiring instruction for the consumer/AI agent
  to apply (the aif runtime resolves ANTHROPIC_AUTH_TOKEN from its own process.env
  by NAME; a file at $GLM_ENV_FILE is invisible unless deployment loads it). Honor
  §7b #2 design-call: print, do NOT auto-patch deployment files.

Key-handling invariant (kickoff §4 item 2) holds: helper references ONLY the
env-var NAME ANTHROPIC_AUTH_TOKEN (line 31); value lives only in untracked env
file (line 29); post-source reachability check at line 249 uses ${!GLM_ENV_VAR:-}
indirection — never expands the value into a log/echo line. Pre-flight grep
returns empty (rc=1).

INSTALL-FOR-AI.md updated to reference the §7b key-reachability wiring the
helper prints. Tests updated with happy-path stubs for GET /projects, POST
/runtime-profiles, PUT /projects/:id, POST /runtime-profiles/validate, plus
regression guards for all four §7c defects. 25/25 PASS.

Hook: worker-container mirror at /tmp/fix-hook/pre-commit (per memory
project_handoff_root_owned_pre_commit_err path 3 — mktemp substitutes for
root-owned /tmp/pre-commit-err; substantive probes unchanged). markdownlint-cli2
+ prettier skipped (npm install blocked in container); maintainer-side pre-commit
+ CI on PR branch carries those gates.

Prior-art: skipped — refactor only, no new capability (round 3 repairs run-2's
existing payload per dispatcher §7c/§7d resolutions; no new dep, no new module).

* fix(beta-delivery-ux-s4): point aif-handoff clone URL at lee-to upstream + drop park language (§7d.2)

Two changes per §7d.2 dispatcher resolution (host-verified 2026-08-09):

setup.d/aif-handoff-guided-install.sh:
- Default AIF_HANDOFF_REPO_URL → https://github.com/lee-to/aif-handoff.git
  (gh api confirmed 2026-08-09: sst-aif/aif-handoff returns 404;
   lee-to/aif-handoff returns 200). The sst-aif reference is retained only
   in the comment documenting the host-side verification rationale.
- Park language ("PARKED — repo URL unverified") dropped — the §7d.2
  resolution closes the run-1 park with host-verified evidence.
- # shellcheck source= directive restored to the canonical
  setup.d/bridge-guided.sh form (run-2 had drifted it).

docs/meta-factory/research-patches/2026-08-08-s4-glm-onebutton-entry-verification.md:
- Prepended scope slug (<!-- scope:s4-glm-onebutton-entry-verification -->)
  as the first line per principle 10's research-patch annotation requirement.
- Item 5 verdict updated from "PARKED — repo URL unverified" to
  "RESOLVED — default upstream is lee-to/aif-handoff" matching the
  §7d.2 host-verified resolution.

Hook: worker-container mirror at /tmp/fix-hook/pre-commit (per memory
project_handoff_root_owned_pre_commit_err path 3).

Prior-art: skipped — refactor only, no new capability.

* chore(beta-delivery-ux-s4): MINOR-4 manifest field + wire glm-onebutton.test.sh into audit-self shard A (§7d.3 #2)

Two cleanup/wire changes per §7d dispatcher resolutions:

setup.d/companions.manifest:
- Added MINOR-4 resolution comment above the aif-handoff row recording that
  kind=external-service rows have an informational-only `install` field —
  setup.d/engine.sh companion_step returns 0 for this kind without consulting
  it. Actual install routes via install.sh → setup.d/aif-handoff-guided-install.sh
  (mirrors the existing runtime-bridge row precedent). Closes the §7d.4 MINOR-4
  run-2 finding that the manifest carried placeholder install semantics.

.github/workflows/audit-self.yml:
- Added `bash tests/install-sh/glm-onebutton.test.sh` step to the shard A
  sequence (after the bridge-guided test). Closes §7d.3 #2 — the new test
  existed but was not wired into CI. Indentation 6/8 spaces matches adjacent
  entries per ci-tool-pinning §1 (no version-pinned tool install needed —
  bash is system-provided).

Hook: worker-container mirror at /tmp/fix-hook/pre-commit (per memory
project_handoff_root_owned_pre_commit_err path 3).

Prior-art: skipped — refactor only, no new capability.

* fix(beta-delivery-ux-s4): the one-button flow could not complete a single live run — three false greens

Round-4 review found the shipped flow 400s on its first live call while its own suite reported
25/25 green. Three independent false greens, each verified against the live aif on 2026-08-09.

1. Create body omitted the two schema-REQUIRED fields. `createRuntimeProfileSchema` requires
   `runtimeId` and `providerId` (no .optional(), no .nullable()). Firing the helper's exact body:
     POST /runtime-profiles -d '{"name":…,"defaultModel":…,"apiKeyEnvVar":…,"baseUrl":…}'
       → HTTP 400 ZodError, path ["runtimeId"] and ["providerId"]
   With `curl -sf`, step A therefore always returned non-zero and the flow always fell back to
   guided-manual — an objective-3 MISS on every run, by the kickoff's own §4 item 5.

2. Transport was unset, which voided step C entirely. Omitting `transport` resolves the profile to
   SDK, and for SDK transport validateClaudeConnection returns ok unconditionally ("using session
   auth"). Measured against a non-persisted inline profile:
     no transport  → {"ok":true,  "transport":"sdk", "hasApiKey":false}   ← passes with NO key
     transport=api → {"ok":false, "transport":"api", "hasApiKey":false,
                      "message":"Missing API key (expected env var: …)"}  ← real gate
   One missing field was the difference between a ping that cannot fail and a ping that gates.

3. Step C read the verdict from the exit code, but /runtime-profiles/validate answers HTTP 200 even
   when validation fails — the verdict is in `.ok`. Measured: `HTTP 200 {"ok":false,"message":
   "Missing API key …"}` and `curl -sf` exits 0 on it. Step C now parses `.ok`.

Also corrected: comments claiming the validate call performs "profile resolution + key lookup +
model call" and that it "returns auth-error" — it does neither. §7a #3's real model call is now an
explicit §7 PARK with both options and the binding each one breaks (argv exposure of the key vs. no
aif endpoint that completes through a stored profile), instead of being reported as delivered.

§7b #1 is stated honestly rather than claimed closed: the helper prints wiring instructions and does
not wire. What changed is that an un-applied instruction is now a hard MISS at step C instead of a
green run — detection, not wiring, and the comment says so.

Test suite made fail-closed (25 → 33 checks). The stub had two defects that hid the above:
  - the /validate arm was DEAD CODE — `*"/runtime-profiles"*` matched first, so step C was tested
    against the CREATE response. shellcheck flags this automatically (SC2221/SC2222) and fires on
    the original file; it simply is not pointed at tests/install-sh/** today.
  - the create arm answered success regardless of body, hiding defect 1.
Now: ordered arms, an explicit endpoint allowlist (each entry dated by live probe), a body rule
mirroring the live 400, and paired negatives N1/N1b/N2/N2b/N3 that fail on run 3's invented ping
path and run 4's create body.

Gates shown to discriminate, not merely to pass — each fix reverted in turn:
  remove transport            → PASS=32 FAIL=1
  remove runtimeId/providerId → PASS=30 FAIL=3
  drop the .ok parse          → PASS=30 FAIL=3
  restored control            → PASS=33 FAIL=0
refresh-covers-full-delivery 6/6 PASS; shellcheck -S warning clean.

Prior-art: skipped — bug fix to an existing capability plus test hardening, no new capability.

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 9, 2026
…, name frozen against the live registry (#1336)

* docs(beta-delivery-ux): S2 kickoff §8a — operator resolutions of the six #1284 parks (re-dispatch rev)

Records the operator's 2026-08-08 batch: Park-1 schema = flat Candidate A +
description field; Park-2 format = JSON (F-B' shell-sourced rejection recorded
with the WHY); Park-3 economy review = whole line on executor tier, aif cap 1
iteration, external cold fidelity mandatory; Park-4 CC detection =
CLAUDE_CODE_SESSION_ID env-presence capability check; Park-5 conditional park
did not fire (live runtime-profiles probe, no substring collision); Park-6 ship
the full functional set (create-worktree.sh + worktree-node-modules.sh +
link-coordination.sh) under the same $1j gate, fresh-consumer smoke AC added.
Unblocks the S2 re-dispatch (A4 presets + A5 status + A9 parts 2-3).

* docs(beta-delivery-ux): S4 kickoff §7b/§7c — close run-2 KICKOFF-AMBIGUOUS + host-verify contract

Run 2 (aif task 92bf0019) returned FIDELITY STOP with KICKOFF-AMBIGUOUS (PR #1300).
The kickoff's §7a never states how the aif runtime acquires the key VALUE: #1 stores
only the env-var NAME in the profile, #4(ii) writes the value to
~/.config/getff/glm.env, and #3 requires the validation ping to run through the
created profile. Verified against source: aif resolves the key from its own runtime
process.env by that name (aif-handoff packages/runtime/src/resolution.ts:217-219,
:247), and that env comes from the compose env-file (docker-compose.yml:15,59,94) —
so glm.env is a dead end as written and #3 is unreachable without dereferencing the
value, which #1 forbids.

§7b closes it by binding the OUTCOME (value reachable in the aif runtime process env
under the §7a #1 name, verified before the ping, honest objective-3 MISS on failure)
while leaving the wiring mechanism to the worker as a §7 technical fork. It is marked
as a dispatcher-authored completion, not an operator decision — the operator can
override it. §7c carries run 2's settled corrections: PUT /projects/:id with a full
body (aifHttp.ts:96) instead of the invented PATCH /project, no GET /projects/:id
(:90), both defaults halves, ping through the profile, 10-skills.sh untouched.

§4.1 adds the missing host-verify contract — the stage ships a shell helper, an
install-time gate and a tests/install-sh suite, i.e. exactly the container-vs-host
surface destination-environment-verification.md §1 exists for; the umbrella had no
contract on any of its six kickoffs (exit 2).

Prior-art: skipped — dispatch-input documentation only, no new capability; resolves an
ambiguity in an existing stage kickoff against in-repo source citations.

* docs(beta-delivery-ux): S4 kickoff §7d — retract the unreachable host path, bind run-3 blockers

§7d.0 retracts the §1 + §7a #1 instruction to re-read
`~/code/aif-handoff/packages/api/src/routes/runtimeProfiles.ts` at stage entry. That path is
host-only: the agent container mounts `$PROJECTS_DIR` (= `$PROJECTS_HOST_ROOT/projects`), so the
repo root holding `packages/api/` sits one level above the mount. The instruction was unexecutable
and run 3 guessed instead of parking — same class as state.md §2 decision 14.

Replacement is an oracle the container can actually reach: the live aif API. §7d.1 pins two probes
(required-field discovery + route-existence discovery) with the dispatcher's own 2026-08-09 outputs
recorded for reproduction, not for trust.

Run-3 blockers bound as corrections: §7d.2 `runtimeId`+`providerId` required in the create body;
§7d.3 the ping route is `POST /runtime-profiles/validate` (the guessed `/<id>/v1/messages` → 404);
§7d.4 §7b #1 still undelivered (the helper verifies reachability but never establishes it);
§7d.5 companion install is prose in a field `engine.sh:18` early-returns past; §7d.6 (MAJOR) the
suite mocks the non-existent endpoint and a body-blind 201, so §4.1 passed over §7d.2/§7d.3 —
the stub becomes fail-closed with a paired-negative that fails on the run-3 helper, and
`it.fails()`-as-delivery is banned.

Adds T-BDU-D: unreachable instructed source → probe, else PARK; never substitute a plausible shape.

Prior-art: skipped — kickoff prose revision only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.1 — name the real base-URL var, and the container trap under it

Own cold-QA of #1319 caught two defects in §7d.1. (1) It invented `$AIF`; the helper actually
resolves `AIF_URL="${RUNTIME_BRIDGE_AIF_URL:-http://localhost:3009}"`
(scripts/getff-glm-onebutton.sh:94), so the probe block now uses that name.

(2) The more load-bearing half: that shipped default is unreachable from the agent container.
Measured 2026-08-09 from aif-handoff-agent-1 — `http://localhost:3009/runtime-profiles` → curl
exit 7 (000), `http://api:3009/runtime-profiles` → 200. `RUNTIME_BRIDGE_AIF_URL` is unset in the
container, so an in-container run of the run-3 helper could not have reached aif at all, which is
consistent with the suite mocking every call (§7d.6). §7d.1 now instructs the export for probing,
explicitly forbids changing the shipped consumer default, and §4 item 1 must quote the base URL
alongside any live end-to-end transcript.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): S4 kickoff §7d.3/§7d.4 — fire my own falsifier; the ping is two halves

§7d.3 as first written told the worker to route §7a #3's «one real minimal model call» through
`POST /runtime-profiles/validate` and left a falsifier for the case where that endpoint is only a
reachability check. The dispatcher fired that falsifier itself rather than shipping the trap:

- for `transport: "api"`, `validateClaudeConnection` returns ok after checking only that `apiKey`
  and `baseUrl` are non-empty — no network call (aif-handoff
  packages/runtime/src/adapters/claude/index.ts:468-479). Live 2026-08-09: `/validate` against the
  Qwen3.8-Max-Preview profile returned ok:true sub-second.
- `POST /runtime-profiles/models` is not a substitute — it returned a static Claude catalogue
  (Sonnet 4.6, Opus 4.6) for a Qwen profile, so it is not querying the provider.

So §7a #3 and §7c #3 cannot both be satisfied by one aif REST call. §7d.3 now binds both halves —
`/validate` for the route proof, plus one 1-token-scale completion against the `baseUrl` READ BACK
from that response (not a hardcoded `$GLM_BASE_URL`, which was run 2's W-3 defect). Provenance is
stated as dispatcher-authored and operator-overridable, matching §7b.

§7d.4 gains an exact, value-free verifier for §7b #3: `hasApiKey` is `Boolean(resolved.apiKey)` and
`resolved.apiKey` is `normalizeString(env[envVarName])` off the aif runtime's own process.env
(packages/runtime/src/resolution.ts:426, :217-219), so `hasApiKey:true` in the §7d.3(1) response IS
proof that §7b #1's outcome was achieved — without dereferencing the key, without argv exposure.

Prior-art: skipped — kickoff prose correction only, no new capability; markdown is exempt from the
capability-commit LOC triggers per CLAUDE.md.

* docs(beta-delivery-ux): R1 stage kickoff — npm release mechanics (A6)

R1 is the last unstarted stage of the umbrella (S1-S5 all merged). This is its
dispatch input, scoped from the spec (§4 A6 `:278-284`, §11 F-C' `:467-468`) and
the binding input `launch-preannounce-track/s6-u10-handoff.md`.

Three things the kickoff decides, because a worker cannot:

1. Dispatchability (§0.1) — R1 IS aif-dispatchable. `private: true` STAYS on
   `packages/core/package.json` for the whole stage; npm refuses to publish a
   private package, so the publish guard is mechanical rather than a reminder
   (attention-is-not-a-mechanism §1). Verified `npm pack --dry-run` works on a
   private package, so the tarball loop runs with the guard armed.

2. Name architecture (§0.2) — probed the live registry, not the spec's prose:
   unscoped `getff` ALREADY EXISTS at 0.0.1, published 2026-06-23 by the
   maintainer as a reservation stub. So the registry-side freeze is done, 0.0.1
   is spent as a version, and `@getff` SCOPE ownership stays unproven (a 404 on
   `@getff/core` proves the package is free, not the scope) — that is an entry
   probe with a STOP attached.

3. Scope boundary (§0.3) — R1 does NOT execute the `@rules-as-tests/*` rename.
   Measured blast radius 33 tracked files / 55 occurrences; umbrella §3 puts the
   rename in U9 (post-announce), and the acceptance gate is name-independent
   because `npm i <tarball>` installs by path.

Also recorded: two gates of the binding input's §0 are SUPERSEDED by the
2026-07-23 amendment (U9 no longer the rename window; the private-drop ordering),
and the honest gap R1 does not close — U10's `npx getff init` gate needs a package
with `bin: getff`, which does not exist in this repo and which A6 does not ask R1
to build.

Host-verification contract declared (no opt-out): the new tarball cell plus the
existing pnpm start cell, which is the file-copy fallback the §4 rollback doctrine
requires to keep working. `scripts/host-verify.sh --list` parses both, exit 0.

Prior-art: skipped — dispatch-input authoring only; a markdown kickoff adds no
capability, no dependency, and no code module (CLAUDE.md doc-file carve-out).

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 17, 2026
…ll slugs (#1312, #1334) (#1412)

## Summary

`do_refresh` had drifted from the install arm in **both** directions, and the two directions are the two issues this PR closes. #1312: three skill slugs shipped on install but reached no refresh loop — `arch` (in none at all), `claude-glm-executor-handoff` (factory install loop only), and `rule-tests`, which the refresh header **announced** and then skipped because its payload lives under `.claude/skills/`, not `skills/`. #1334: the worktree-scripts arm carried no profile check at all, so any `--refresh` on a `core` project delivered four `env+` artefacts — the inverse defect, and one that makes «what does `core` contain» unanswerable from `--profile` alone.

Both are the same seam, so both get the same resolution: **every depth-gated refresh arm now uses the delivery site's own profile predicate OR presence-on-disk** (presence = prior opt-in, the brownfield upgrade path), and the per-tier slug lists collapse into one shared constant each so the two arms cannot drift again.

## Changes

- **`setup.d/lib.sh:58-60`** — `GETFF_SKILLS_CORE` / `_ENV` / `_FACTORY`: the slug lists as an SSOT read by both arms. Issue #1312's own suggested direction #1; the three drifts it measured were three copies of one list, not three bugs.
- **`setup.d/10-skills.sh:113/127/133`** — the install arm reads the constants; its `▶ Contour surface` / `▶ AIF operator suite` announcements render from them too.
- **`install.sh:684-701`** — the refresh arm reads the same constants, in three loops with three gates: core unconditional, env+ (`env|factory|WITH_AIF_SUITE` OR presence), factory (`factory|WITH_AIF_SUITE` OR presence). `arch`, `pipeline` and `claude-glm-executor-handoff` are refreshed for the first time; `rule-tests` moves here from the plain-copy loop, where its source path could never resolve.
- **`install.sh:643`** — the plain-copy header renders from `_PLAIN_SKILLS`, so it can no longer announce a slug the loop does not carry (#1312's honest-signals half).
- **`install.sh:1030`** — the worktree-scripts arm gains the gate it never had (#1334). Presence probe is `scripts/create-worktree.sh`: the cluster's load-bearing entry point, present in every version of it, and the four ship together by construction — so a consumer who opted in before `getff-work.sh` joined the cluster still receives it.
- **`install.sh:1128`** — tier-home moves from presence-ONLY to the same uniform gate. Without it, `--refresh --profile env` would deepen some arms and not others: the «each arm decides for itself» state `INSTALL-FOR-AI.md` previously had to describe in a paragraph. Named in #1334's own arm table as part of this decision.
- **`tests/install-sh/consumer-upgrade-path.test.sh:447-612`** — TESTs 8-11, the behavioural gates: core refresh delivers no deeper artefact (#1334), a bare `--refresh` keeps an already-opted-in factory payload fresh (#1312), the `▶ Skills` header cannot announce what the run does not touch, and `--refresh --profile env` DOES deliver — which is the paired-negative for TEST 8's absence claim (same paths, same tree, present as soon as the profile says so).
- **`tests/install-sh/refresh-covers-full-delivery.test.sh:296-345`** — extends the existing parity gate (added by #1327 for the same worktree cluster) rather than adding a parallel one: both arms must READ every tier constant, and neither may carry a literal-slug `for _skill in` loop (a fourth copy). Two paired-negative arms.
- **`INSTALL-FOR-AI.md:152/160-166/444`** — re-measured. The old text documented both defects as consumer-observable behaviour; leaving it would have made the shipped doc lie in the opposite direction.

## Prior-art consult

- [x] Not a capability commit per [CLAUDE.md «What is a capability commit?»](../CLAUDE.md) — no new dependency, no new file (all four touched files existed), nothing under `packages/`. Commit carries the escape-hatch trailer with rationale.
- [x] No new capability area surfaced: the fix reuses the gate shape already in this function (`install.sh:621` agents arm, F7 skills arm) and the parity-test precedent from #1327.
- [x] n/a — no SSOT entry matched or needed.
- [x] n/a — no new capability area, so no context7 pass claimed.

## Test plan

- [x] `bash tests/install-sh/consumer-upgrade-path.test.sh` — RED baseline before the fix: `PASS=37 FAIL=7` (the four worktree scripts leaking onto a core refresh ×2 arms; `arch`, `rule-tests`, `claude-glm-executor-handoff` stale after a bare refresh; the announced-then-skipped `rule-tests`; `arch`/`pipeline`/`tier-home` missing under `--refresh --profile env`). After: **`PASS=44 FAIL=0`**.
- [x] `bash tests/install-sh/refresh-covers-full-delivery.test.sh` — before: `PASS=9 FAIL=2`; after: **`PASS=13 FAIL=0`** (both new checks plus their negatives).
- [x] `SNAPSHOT_MODE=compare bash tests/install-sh/snapshot.sh` — exit 0, baselines unchanged (the install arm ships the same bytes; only its source of names moved).
- [x] Full `tests/install-sh/*.test.sh` battery — see the run summary in the review findings section.
- [x] `shellcheck --exclude=SC2034,SC2016,SC2317 setup.d/*.sh install.sh` — exit 0 (CI's exact invocation, `.github/workflows/audit-self.yml:804`).
- [x] Manual smoke: `--profile core` install → bare `--refresh` → `--refresh --profile core` → `--refresh --profile env`, asserting the depth boundary at each step (this is TESTs 8/11 run end-to-end against real installs, not a mocked harness).

## Provenance

n/a — issue-driven fix, not a stage PR. Base: `staging` @ `049b35e851`. In-session (no aif substrate).

## Review findings

n/a — see Test plan for the RED→GREEN evidence.

## Fidelity verdict

FIDELITY: skipped — issue-driven bug fix with no kickoff or spec stage behind it; the acceptance contract is the two issue bodies (#1312, #1334) plus the paired-negative gates listed in the Test plan.

## Parked questions

n/a

### §1.7 Forward-check applied

Checked against the disciplines this diff touches. [`dual-implementation-discipline.md §7`](../.claude/rules/dual-implementation-discipline.md) (single source of truth): the two hand-maintained slug lists collapse to `setup.d/lib.sh:58-60`, read by `setup.d/10-skills.sh:113` and `install.sh:684` — the duplication that drifted three times is gone rather than re-asserted. [`attention-is-not-a-mechanism.md §1`](../.claude/rules/attention-is-not-a-mechanism.md): every claim in this PR lands on a deterministic channel — the depth boundary on `tests/install-sh/consumer-upgrade-path.test.sh:447`, the announce↔deliver honesty on `:554`, the SSOT-consumption invariant on `tests/install-sh/refresh-covers-full-delivery.test.sh:296`; none of them is «a reviewer will read the diff». [`no-paid-llm-in-ci.md`](../.claude/rules/no-paid-llm-in-ci.md): the new gates are bash + real installs, zero API calls. [`effort-worthiness.md §1`](../.claude/rules/effort-worthiness.md): `build-and-verify` contour — a reversible installer change verified live against real consumer trees, no research-grade round. [`evidence-regeneration.md`](../.claude/rules/evidence-regeneration.md): `INSTALL-FOR-AI.md:160` was a dated measurement of the old behaviour, so it was re-measured (2026-08-17) instead of silently inherited.

### §1.7 Backward-check applied

Swept every profile-gated install layer for its refresh counterpart, not just the two the issues named: `setup.d/20-agents.sh:39` (suite agents) → gated arm exists at `install.sh:621`; `setup.d/20-agents.sh:70` (skill-context) → gated arm exists at `install.sh:1146`; `setup.d/30-templates.sh:108` (tier-home) → was presence-only, unified here; `setup.d/85-worktree-scripts.sh:34` (worktree cluster) → the #1334 defect, fixed here; **`setup.d/55-runtime-bridge-vendor.sh:65` (factory vendor payload) → has NO refresh arm at all** — a pre-existing #1312-class gap, outside both issues' scope, so it is recorded in `INSTALL-FOR-AI.md:166` as install-only rather than silently fixed in this PR. Also swept for stale claims about the changed behaviour: `grep -rln "create-worktree\|getff-work.sh" tests/` → only the file updated here; `grep -rn 'for _skill in' setup.d install.sh` → no literal-slug loop survives (now asserted); `INSTALL-FOR-AI.md:152/160-166/444` were the only prose stating the old semantics, and #1329's weakened claim (repaired in #1332) still holds under the new behaviour.

Closes #1312
Closes #1334
artyhoo added a commit that referenced this pull request Aug 18, 2026
* docs(orchestrator): thin the skill to deltas + bindings (D-H9)

Rewrite `.claude/skills/orchestrator/SKILL.md` to the `/arch` model: every slice
that an upstream superpowers skill (or this skill's own references/ file) already
owns collapses to a binding; only project deltas keep prose.

Method: three-way slice classification (DELTA / RE-DESCRIPTION / MIXED) over the
512-line baseline, each RE-DESCRIPTION claim carrying the upstream file:line it
re-describes, read from superpowers 6.2.0 rather than recalled. The per-slice
disposition is the deliverable's spine and ships as
`.claude/orchestrator-prompts/orchestrator-rewrite/report.md`.

Measured: 157 deletions / 119 insertions, 512 -> 474 lines. The prep-doc expected
"roughly halve"; it did not hold, and the ledger records why — re-description was
~31% of the body, and collapsing a slice costs added binding lines. Deltas were
not cut to reach a number (kickoff P-1, T-OR-A).

Three T16 divergences from upstream are now stated in the body instead of being
silently implied: the model ladder (subscription pool vs per-token pricing), the
parallel-batch gate (independent worktrees vs SDD's in-plan ban), and
worktree-by-default (vs upstream's consent step).

Repairs found while classifying: the Phase 0 pointer routed to `executing-plans`,
which itself defers to `subagent-driven-development` when subagents exist; the
Phase 4.5 provenance cited a skill name that does not exist; and the body taught a
bare `git worktree add` recipe that upstream names as the "#1 mistake" when a
native worktree tool is available.

All six named deltas survive, including the "Principle-test allowlist probe"
heading that CLAUDE.md:134 names by name. Install fingerprints recaptured in the
same commit.

Prior-art: skipped — doc-only rewrite of an existing skill body plus the snapshot
regeneration it forces; no new capability, no new dependency, no new module.

* fix(snapshots): recapture install fingerprints AFTER the Prettier pass

The previous commit captured baselines, then the pre-commit shipped-format
gate reformatted two markdown tables in `.claude/skills/orchestrator/SKILL.md`,
and the baselines were never re-taken. They therefore recorded the pre-Prettier
delivered hash (9059fd7c…) while the file installs as 76a07b50…, so
`install-sh battery (shard C)` went red on 8 of 15 stack/mode pairs while a
local compare run before the reformat had been green.

Order matters: capture is only valid after the LAST content-changing step.

Verified on the host after this commit: `SNAPSHOT_MODE=compare` → 15 pass /
0 fail, and `prettier --check` on the skill is clean, so the two cannot
disagree again for this diff.

Prior-art: skipped — regenerated snapshot baselines only, no capability, no
dependency, no new module.

* fix(orchestrator): apply cold-review round 1 — ledger citations + three body defects

Two cold seats reviewed the rewrite. The slice-loss seat returned GO with zero
BLOCKER/MAJOR (25 consumer surfaces enumerated by grep, 25 CLEAN, all six named
deltas and the CLAUDE.md:134 anchor intact). The doc-quality seat returned REVISE,
and its MAJORs were defects in the ledger, not in the skill.

Ledger (`.claude/orchestrator-prompts/orchestrator-rewrite/report.md`):

- ~13 baseline citations were relative to a `sed` window starting at line 12 and
  therefore read 11 low — row 6 cited `:111`, a decision-matrix row, for the Step-0
  restatement that actually sits at `:122`. Every baseline citation is re-measured
  against `git show origin/staging:…` and rewritten. Rows 9-21 were already correct.
- Four `references/` citations pointed at the wrong section of the right file
  (`glossary.md:37-39` for the depth-2 limit is `## See also`; the real line is `:36`).
- Two counts were estimated rather than counted: one Recovery row was removed, not
  two; two anti-pattern bullets, not four.
- The summary counts did not match the rows beneath them; recounted, with the
  row-vs-slice distinction now stated.
- The diff numbers are re-taken at the head under review (168/134). The two earlier
  readings are recorded rather than quietly replaced, because each was stale for a
  different reason worth knowing.

Skill body (`.claude/skills/orchestrator/SKILL.md`):

- The authority header claimed «authoritative for the Mode A/B dispatch vocabulary»
  while the same header disclaimed «dispatch-channel definitions — glossary.md». It
  now owns the choice rule; glossary.md owns the definitions.
- The Mode B condition list omitted the Opus-pool-under-load case that both the quota
  zone table and the decision matrix require, so the anti-pattern pointing at that
  list pointed at an incomplete one.
- The model-tier divergence note attributed «prices per token» to upstream;
  `subagent-driven-development/SKILL.md:181` says the opposite («turn count beats
  token price»). Reworded to what upstream actually argues.
- The slice-loss seat's one falsifier hit: «Batched questions» was carried as a
  project norm when SDD:151-153 already owns it. Now bound, not claimed.
- Restored two normative details the collapse had dropped: that an embedded
  self-review does not count as one of the two Phase -1 reviewers, and the two
  project-specific PR-open details upstream does not carry.

Snapshots recaptured AFTER the format gate this time, per the previous commit's
lesson. Host: compare 15/15, principles 423 pass, `make self-audit` exit 0.

Prior-art: skipped — review-round corrections to a doc rewrite plus the snapshot
regeneration it forces; no new capability, dependency, or module.

* fix(ledger): apply cold re-review round 2 — the citations that survived as copies

The scoped re-review of round 1 verdicted 10 of 13 findings ADDRESSED and 3 not,
with zero test, snapshot, anchor or frontmatter breakage. All three misses, and
both MINORs the round-1 fix itself introduced, were in the ledger:

- Row 13 still carried the −11 window-relative numbers as SECOND-HAND COPIES inside
  another row's justification («the matrix (94-103)», «the blockquote (82-88)»).
  Round 1 corrected the rows those numbers belonged to and never searched for their
  mentions elsewhere. Re-measured to 103-114 and 93-99.
- Row 8 claimed consumers cite the whole `-1 … 4.5` ladder; the two citations
  evidence «Phase -1» only, and one path lacked its directory. The claim is narrowed
  to what the citations support and the paths are complete.
- Three removals had no ledger row at all — the `Agent({…})` JavaScript example, the
  mid-batch bash block, and `### Restoring WIP` — while this file's header promises a
  classification of every slice. Added as rows 21a/21b/21c; 21a records the one
  removal with no surviving verbatim form.
- Narrowing `glossary.md:5-11` to `:5-9` in round 1 silently dropped the Reviewer
  role at `:11`. Restored, with each role's own line named.
- One bullet carried both «~355» and «~344» for the same residue; 512 − 168 = 344.

Notes-lane items fixed in passing: the `rationale.md` opusplan citation overran into
the next heading (`:23-24`, not `:20-26`), and the frontmatter key lines were wrong
(`description:` is at `:3`).

The skill body is untouched this round, so the install fingerprints from the previous
commit remain valid — verified rather than assumed: `SNAPSHOT_MODE=compare` 15 pass /
0 fail, principles 423 pass.

Prior-art: skipped — review-round corrections to a documentation ledger; no new
capability, dependency, or module.

* fix(ledger): round 3 — retract a superlative the previous round's fix invented

The scoped re-review of round 2 resolved 77 citations (57 baseline line numbers,
20 repo-local) with zero baseline failures, and confirmed the header's promise —
a classification of every slice — by walking every deletion hunk of
`git diff -U0 origin/staging`. One finding stood, and it was introduced by the
round-2 fix itself:

- Row 21a claimed the JavaScript example was «the ONE removal with no surviving
  verbatim form». False, and falsifiable in one command: `grep -c 'Batch A'` over
  the new body returns 0, so the parallel-dispatch sketch (baseline 333-337) has no
  verbatim survivor either, and neither does the push/PR command block (386-392,
  `grep -c 'git push -u'` → 0). Both were already recorded under rows 14 and 16 —
  nothing was missing — but the superlative claimed one notch more than the evidence
  carried. Corrected to «one of three», with both counter-examples and their probes
  named.
- The same fix left an internal contradiction: the kept-slice row for the
  `isolation: "worktree"` mandate spanned 130-145, overlapping the 143-154 block
  21a declares removed, so 143-145 were at once «kept in substance» and «gone
  without a trace». The coherent range is 130-141 (`:141` is the «May be skipped»
  clause; `:143` opens the fence).
- Two citations into `references/rationale.md` were off by a line or two. Root
  cause worth recording: the first reading of that file in this umbrella came from
  the PRIMARY checkout, which carries an untracked pre-authority-header copy
  (`git status` there reports `?? …/references/rationale.md`), while the tracked
  copy that ships is two lines longer. Reading a file from the primary checkout
  while working in a worktree can hand you different bytes than the ones under
  review. Re-measured against the worktree copy: `:8-17` and `:4`.
- Ledger row order restored (21a-c had been inserted after 22a/22b).

Skill body untouched again this round, so the fingerprints stay valid — verified,
not assumed: compare 15 pass / 0 fail, principles 423 pass, report.md 219 lines
against the 600 ceiling, Prettier clean.

Prior-art: skipped — review-round corrections to a documentation ledger; no new
capability, dependency, or module.

* fix(ledger): back the block-survival claim with my own measurement, not a reviewer's

Round 3 recorded «every other removed block has a verbatim survivor» on the
re-review's authority. Re-derived it here instead: all eight fenced blocks in the
baseline were tested line-by-line (comments stripped) against the new body. The
claim holds — exactly the three named blocks have zero surviving lines — but the
wording invited reading «a survivor» as «fully preserved», which is false for
three of the remaining five. The per-block counts are now stated (249-260 3/4,
286-292 4/4, 347-350 2/2, 376-380 2/3, 398-401 1/2) so the sentence is checkable
rather than trusted.

Skill body untouched; fingerprints unaffected. report.md 219 lines, Prettier clean.

Prior-art: skipped — precision edit to a documentation ledger; no new capability,
dependency, or module.

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Aug 19, 2026
…ontier (consumer binding, never a fork) (#1488)

* feat(dispatcher): advance-frontier.sh — §2.7 stage choice derived from the dependency frontier (consumer of frontier.sh, never a fork)

Pure consumer of the /pipeline-owned emitter: tees its full recorded output, then
emits ONE verdict line (ADVANCE / ADVANCE-INCOMPLETE / ADVANCE-DEGRADE / HALT-VERIFY /
HALT-BLOCKED / COMPLETE) + FRONTIER-SET / ATTN-UNRESOLVED satellites. HALT-VERIFY
encodes T-FRS1-B: basis=marker-unverified done never advances a consumer — §2.6
is:merged stays the authority, verdict fed back via MO_FRONTIER_DONE/OPEN.
14-arm paired-negative test (marker-lies triple, delegation anti-fork arm,
SKILL-wiring arms red-by-design until the binding commits).

Prior-art: REUSE — consumer binding over .claude/skills/pipeline/helpers/frontier.sh
(ratified emitter, skill-harmonization spec §5.4 / D-H13); helper+test pattern follows
probe-inflight.sh §2.0. No new capability (umbrella kickoff §0).

* docs(skills): bind dispatcher §2.7 + night-mode substrate delegation to the frontier read

§2.7 «Advance» rewritten from two narration lines to the executed helper call with the
full verdict table (§2.0 probe-inflight pattern): branch on the verdict, never pick the
next stage by eye, never re-derive the frontier by hand. §2.6 is:merged check retained
as the merge authority inside HALT-VERIFY's re-entry recipe. §6 gains the stage note.
night-mode's substrate-choice clause now routes advance through §2.7 — one clause, no
second stage-picker. SKILL-wiring test arms go green with this commit.

Prior-art: REUSE — consumer binding over .claude/skills/pipeline/helpers/frontier.sh
(ratified emitter, skill-harmonization spec §5.4 / D-H13); helper+test pattern follows
probe-inflight.sh §2.0. No new capability (umbrella kickoff §0).

* chore(snapshot): regenerate install-sh baselines after shipped-skill edits

SNAPSHOT_MODE=capture fallout of the night-mode/SKILL.md clause extension (the one
edited file on the consumer install surface — hash updated in all 8 lanes). Dispatcher
skill files carry 0 fingerprint rows: repo-internal orchestration, not shipped.
Diff reviewed before commit; SNAPSHOT_MODE=compare passes 15/15.

Prior-art: skipped — snapshot regen after shipped-skill edit, no new capability

* fix(dispatcher): cold-review riders — §2.6 feed-forward + degrade-corner unverified-done clause

- §2.7: §2.6 CLEARs feed forward — invoke the helper with MO_FRONTIER_DONE
  accumulating confirmed-merged ids, so a just-merged stage cannot re-enter
  the frontier because its row still reads done=no
- ADVANCE-DEGRADE verdict + SKILL bullet: rows reading done= in degraded
  output carry no basis= — unverified row text, §2.6 clears them before any
  stage is skipped

Both from the /harvest §4 cold code review (Important #1/#2); T-FRS1-B does
not stop at the verdict path.

---------

Co-authored-by: Test <test@example.com>
artyhoo added a commit that referenced this pull request Sep 1, 2026
…les (issue 1519) (#1548)

## Summary

`install.sh --refresh` deleted consumer-authored eslint rules: the issue-882 stale-rule prune in `generate_eslint_barrel` removed every `eslint-rules-local/<name>.{ts,mjs,d.ts}` trio whose basename was not valid for the current `--stack`, before the issue-1481 preservation loop could keep the barrel entry — so a TypeScript consumer lost its own rule on every refresh (issue 1519, the one open beta phase-1 BLOCKER). The prune now fires only for basenames that are **framework-attributable** (present as a rule `.ts` in any framework rules dir, across all stacks), which keeps the cross-stack #882 prune intact and leaves consumer rules alone; the kept-entry loop gains one guard so a preserved consumer `.ts` is emitted exactly once in the barrel (the RP-1b duplicate-import blocker found by the Phase -1 cold review).

Closes #1519

Stage: `refresh-prune-consumer-rule` P1 (single-stage umbrella). Kickoff: `.claude/orchestrator-prompts/refresh-prune-consumer-rule/kickoff.md` (PR 1527 + revision PR 1543).

## Changes

- **RP-1** — `setup.d/lib.sh:1239-1251`: the `_fw_basenames` computation is hoisted above the prune (one computation; the old site is a pointer comment at `setup.d/lib.sh:1285`). `setup.d/lib.sh:1262-1269`: the prune condition is now «∉ valid-for-stack AND ∈ `_fw_basenames`»; the `· pruned stale rule` message is byte-identical to before, and a kept consumer rule prints nothing (**RP-2**).
- **RP-1b** — `setup.d/lib.sh:1303`: one guard in the issue-1481 kept-entry loop, beside the dead-import check — a basename whose `.ts` is on disk is skipped there, because the generation loops already emit its canonical import + rules-map entry. Same commit as RP-1 (the branch is one commit, `2118e70149`).
- **RP-3** — `tests/install-sh/eslint-barrel-preserve-consumer.test.sh`: new arm (a2) in the EXISTING file (no new test file): a consumer `.ts+.mjs+.d.ts` trio + hand-added barrel entry survives `--refresh` byte-identically (`:176-206`), the barrel carries the import line and the rules-map line **exactly once** via `grep -c … -eq 1` (`:209-221`), and the barrel loads (`barrel_loadable`, `:222`). Helpers `hand_extend_barrel` / `barrel_loadable` take optional `[base] [camel]` and a `consumer_rule_trio` helper was added (`:52-108`); arms (a)-(d) unchanged. Arms (b) and (c) are re-run, not re-written.
- **RP-4** — no delivered-file bytes change: `SNAPSHOT_MODE=compare` cells for ts-server / react-* / go are byte-identical (see Test plan for the two pre-existing cargo cells).

### RED-before-GREEN (RP-3(d)) — the implementer's observations, pasted verbatim from the task record

```text
- [x] T3 RED #1 vs UNPATCHED lib.sh — RP-1 defect observed: `consumer-ts-rule.ts/.mjs/.d.ts` all deleted, entry dropped → `PASS=13 FAIL=5` (fails: 3× byte-identity, import count 0, map count 0).
- [x] T4 RP-1 applied (moved `_fw_basenames` above the prune; prune condition = ∉ valid ∧ ∈ framework-attributable) → RED #2 — RP-1b defect observed: import count = **2**, rules-map count = **2**, `(loadability) barrel import failed` (live, not skipped — installed `@typescript-eslint/utils` + `zod`).
- [x] T5 RP-1b kept-entry guard (skip basename with `.ts` on disk, beside dead-import check) → GREEN `PASS=20 FAIL=0 SKIP=0`; loadability live for both consumer rules.
- [x] T15 paired-negative probe — prune disabled via sed probe: arm (c) RED («cross-stack stray NOT pruned (3 file(s)) — #882 regressed» + stray still in barrel), arm (a2) stays green; lib.sh restored byte-exact afterwards (verified by diff + rerun).
```

### Issue 1519 repro replayed on the patched tree (host, macOS, 2026-09-02)

Temp consumer, `install.sh ts-server --force`, then the issue's own basename `no-inline-rgba-in-tsx` as a `.ts+.mjs+.d.ts` trio + hand-added barrel entry, then `install.sh ts-server --refresh`:

```text
install --force rc=0
install --refresh rc=0
--- refresh log lines mentioning the rule or a prune:
(none — no prune line printed)
--- ls eslint-rules-local/no-inline-rgba-in-tsx.* after refresh:
.../eslint-rules-local/no-inline-rgba-in-tsx.d.ts
.../eslint-rules-local/no-inline-rgba-in-tsx.mjs
.../eslint-rules-local/no-inline-rgba-in-tsx.ts
--- sha256 before: 0dd4a5c7a5c2c913 0dd4a5c7a5c2c913 84a583143a2c9b59
--- sha256 after : 0dd4a5c7a5c2c913 0dd4a5c7a5c2c913 84a583143a2c9b59
--- barrel counts: import=1 map=1
barrel loads; rule registered: true
```

Pre-edit single-prune-site check (kickoff §3), on the branch: `grep -n 'pruned stale rule' install.sh setup.d/*.sh | sort -u` → `setup.d/lib.sh:1267` only.

### Accepted edges (kickoff T19 — stated, not solved)

- A consumer basename that collides with a framework rule of ANOTHER stack is framework-attributable by construction and IS still pruned (that is the #882 case).
- A consumer `.ts` with NO sibling `.mjs` now survives the prune but yields a dead import in the barrel (the generation loops emit the entry for the `.ts`); the kickoff scopes the population to the `.ts+.mjs+.d.ts` layout the installer itself produces. Loud-and-recoverable, not silent data loss; recorded as an observation for a follow-up, not fixed here.

## Prior-art consult

- [x] Not a capability commit (a `*.test.sh` under `tests/` is outside `packages/`; `setup.d/lib.sh` is an edit). Trailer added anyway per kickoff §3: `Prior-art: in-repo REUSE — the framework-attributable predicate already computed for the issue-1481 preservation loop (_fw_basenames, setup.d/lib.sh, PR 1505) is the exact predicate the prune lacked; no new mechanism, no SSOT entry.`
- [x] No new capability area surfaced; no SSOT entry added.
- [x] No existing SSOT entry matched (in-repo reuse only).
- [x] context7 not applicable — no new capability area (kickoff «Prior-art (EXECUTION-PLAN §5.5 Step 1.5): in-repo REUSE only»).

## Test plan

Kickoff §5 `host-verify` block run on the host (macOS, Darwin 25.6.0) against the branch tree:

| command | result |
|---|---|
| `bash tests/install-sh/eslint-barrel-preserve-consumer.test.sh` | PASS=20 FAIL=0 SKIP=0 (loadability live) |
| `bash tests/install-sh/refresh-different-stack-prunes-barrel.test.sh` | PASS=9 FAIL=0 |
| `bash tests/install-sh/refresh-regenerates-barrel.test.sh` | PASS=10 FAIL=0 |
| `bash tests/install-sh/lib-helpers.test.sh` | 20 pass / 0 fail |
| `bash tests/install-sh/refresh-divergence-guard.test.sh` | PASS=23 FAIL=0 |
| `bash tests/install-sh/meta-all-wired.test.sh` | PASS=2 FAIL=0 |
| `npx vitest run --root packages/core principles/41-shell-test-ci-coverage.test.ts` | 8 passed |
| `SNAPSHOT_MODE=compare bash tests/install-sh/snapshot.sh` | 13 pass / 2 fail — `cargo/greenfield` + `cargo/brownfield-clippy` differ by `.ai-factory/refresh-baseline.json` only; **identical failure on a detached `origin/staging` worktree** on this host, so pre-existing host-lane state, not branch-introduced |
| `shellcheck setup.d/lib.sh` (bare) | 8 findings (SC2034/SC2016/SC2317), **finding set identical to `origin/staging`** (line numbers shifted); in the CI form `shellcheck --exclude=SC2034,SC2016,SC2317` (`audit-self.yml:884`) → exit 0. shellcheck never ran in the container (not on PATH there) — this host run is the first |

`bash scripts/run-local-ci-sweep.sh` (diff mode): `meta-all-wired` PASS, `shellcheck` PASS, `install-sh-suite` stopped at `byte-identical.test.sh` + `cargo-entry-lane.test.sh` — both fail identically on a detached `origin/staging` worktree (same cargo host-lane cells / clippy self-check), so pre-existing; every other suite test rc=0 (full per-test run recorded in the harvest session).

- [x] `npm test --workspace=@rules-as-tests/core --run` / principles: covered by CI on this PR (branch touches no `packages/**`).
- [x] Manual smoke: the issue-1519 repro replay above.

## Provenance

kickoff `.claude/orchestrator-prompts/refresh-prune-consumer-rule/kickoff.md` (on staging at `8f609c6f96`) · base SHA `4ba2679ba5` · substrate: aif task `d80087a9-5f04-4db7-88bb-da746f24365b` on the project default runtime profile (no per-task override; plan + implement ran on GLM-5.3, transport sdk) · harvest + cold seats in-session (Claude) · fidelity Round 1.

## Review findings

Factory review: task reached `done` with internal review; harvest-side cold fidelity audit (dialogue-blind, inputs = protocol + kickoff + 3-dot diff) mapped every P1 deliverable and every RP decision to diff evidence: RP-1 `setup.d/lib.sh:1239-1251` + `:1262-1269`, RP-1b `:1303` (kept-entry loop, generation loops untouched), RP-2 silent keep, RP-3 `tests/install-sh/eslint-barrel-preserve-consumer.test.sh:209-221` + `:222`, RP-4 no delivered bytes, §3 off-limits surfaces absent from the diff. Drift: missing none · extra none graded (notes lane: helper parameterisation `:52-108` is instrumental to arm (a2)) · diverged none. Plan spot-check: not required (calibration window closed — 13 merged staging PRs already carry one).

### Watch-list

| id  | criterion | why | defect site | reintroduction tell |
| --- | --- | --- | --- | --- |
| W-1 | kickoff RP-1b — guard lives in the kept-entry loop, generation loops stay authoritative | a `_kept_names` guard on the generation side lets a stale hand-written camelCase identifier win over the canonical one | none — preventive | any `_kept_names` test appearing in the two `eslint-rules-local/*.ts` generation loops, or the `:1303` `.ts` guard removed/moved |
| W-2 | kickoff RP-1 — prune predicate is «∉ valid AND ∈ `_fw_basenames`» | dropping the `_fw_basenames` arm kills issue-882 (strays kept); dropping the `_valid` arm resurrects issue-1519 | `setup.d/lib.sh:1262-1269` | prune loop with a single `case`, or `rm -f` reachable outside the `*" $_eb "*` arm of `_fw_basenames` |
| W-3 | kickoff RP-1 — ONE `_fw_basenames` computation, above the prune | a second copy at the old site (`:1285`) drifts silently from the first | `setup.d/lib.sh:1285` (comment stub) | a second `_fw_basenames=" "` assignment anywhere in `generate_eslint_barrel` |
| W-4 | kickoff RP-3 — exactly-once asserts are `grep -c … -eq 1`, never `grep -q` | `grep -q` passes on the duplicated barrel, which is the RP-1b blocker | `tests/install-sh/eslint-barrel-preserve-consumer.test.sh:210-221` | `grep -q` replacing either count, or the `-eq 1` relaxed to `-ge 1` |
| W-5 | kickoff RP-3 — arms (b)/(c) re-run, not re-written; no new `tests/install-sh/*.test.sh` | a new file re-opens the reuse verdict and needs audit-self wiring | none — preventive | any new `tests/install-sh/*.test.sh`, or edits inside the arm (c) body |
| W-6 | kickoff RP-2 — keep path emits nothing | a `⚠` on kept consumer rules extends the R1 manifest out of scope | `setup.d/lib.sh:1262-1269` | any `echo`/`warn` in the non-fw fall-through, or `refresh_baseline_diverged` referenced from the prune |
| W-7 | arm (a2) fixture premise — `consumer-ts-rule` absent from every framework rules dir (test.sh:166) | if a framework rule ever takes that basename the arm goes RED for the wrong reason (T19 edge) | `tests/install-sh/eslint-barrel-preserve-consumer.test.sh:166` | a `packages/*/eslint-rules/consumer-ts-rule.ts` appearing |

Round 1: W-1 CLEAN · W-2 CLEAN · W-3 CLEAN · W-4 CLEAN · W-5 CLEAN · W-6 CLEAN · W-7 CLEAN

## Fidelity verdict

FIDELITY: GO
Basis: .claude/orchestrator-prompts/refresh-prune-consumer-rule/kickoff.md
Round: 1
Audited-SHA: 2118e70
Evidence: setup.d/lib.sh:1239-1251 (RP-1 _fw_basenames hoisted above the prune); setup.d/lib.sh:1262-1269 (RP-1 predicate, RP-2 silent keep); setup.d/lib.sh:1303 (RP-1b guard in the kept-entry loop); tests/install-sh/eslint-barrel-preserve-consumer.test.sh:210-221 (RP-3 exactly-once count asserts); tests/install-sh/eslint-barrel-preserve-consumer.test.sh:223 (RP-3 loadability)
Findings: none graded. Notes lane: helper parameterisation (test.sh:52-108) is instrumental to arm (a2), defaults preserve arms (a)-(d).

## Parked questions

none. Two observations recorded (not forks): the `.ts`-without-`.mjs` edge and the cross-stack basename collision, both listed under «Accepted edges» above per kickoff T19.

## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files)

### §1.7 Forward-check applied

- build-first-reuse-default (`.claude/rules/build-first-reuse-default.md`): REUSE verdict — the fix reuses the `_fw_basenames` predicate already computed at `setup.d/lib.sh:1239-1251` (formerly `:1265-1273`, PR 1505) instead of a second «framework-attributable» definition; the test extends the existing `tests/install-sh/eslint-barrel-preserve-consumer.test.sh` (CI-wired at `.github/workflows/audit-self.yml:750`) instead of a new file.
- destination-environment-verification (`.claude/rules/destination-environment-verification.md` §1): the kickoff §5 `host-verify` block was run on the host, not accepted from the container run — including `shellcheck setup.d/lib.sh`, which the container could not run at all (see Test plan).
- attention-is-not-a-mechanism (`.claude/rules/attention-is-not-a-mechanism.md` §1): the exactly-once property is a deterministic assert (`grep -c … -eq 1`, `tests/install-sh/eslint-barrel-preserve-consumer.test.sh:209-221`) plus a live `node` import (`:222`), not a reviewer reading the barrel.
- no-paid-llm-in-ci (`.claude/rules/no-paid-llm-in-ci.md`): arm (a2) is bash + node, zero API calls; the cold fidelity seat ran in-session.
- language-discipline: all new comments and messages in `setup.d/lib.sh` and the test are English.

### §1.7 Backward-check applied

Class of this change = «the prune / keep predicates the installer applies to `eslint-rules-local/` on `--refresh`». Surfaces enumerated by `grep -rnE 'pruned stale rule|framework-attributable|not part of the .* stack'` over tracked `*.sh|*.ts|*.md` (excluding kickoffs/retros/research-patches):

- `setup.d/lib.sh:1259-1269` — the ONLY prune site (grep `pruned stale rule` → one hit, `:1267`); SWEPT-CLEAN, the site this PR edits.
- `setup.d/lib.sh:1278-1281` — the issue-1481 preservation-loop comment already states the broader cross-stack predicate; now the prune agrees with it. SWEPT-CLEAN (comment accurate post-change).
- `tests/install-sh/refresh-different-stack-prunes-barrel.test.sh` (arm 3, the #882 contract: a stray from a different `--stack` is pruned) — re-run on the host, PASS=9 FAIL=0. SWEPT-CLEAN.
- `tests/install-sh/refresh-regenerates-barrel.test.sh` (arm 6, `.override.md` protects a fixture from the prune) — re-run, PASS=10 FAIL=0. SWEPT-CLEAN.
- `tests/install-sh/eslint-barrel-preserve-consumer.test.sh:268` arm (c) — «framework-attributable → never preserved» still holds and its paired negative was observed RED by the implementer (T15 line above). SWEPT-CLEAN.
- `docs/superpowers/plans/2026-07-03-eslint-barrel-stack-prune.md:261` — the ORIGINAL #882 plan quotes the old prune body verbatim; it is a dated design-history plan (frozen artefact class), not a consumer-facing doc, so it is NOT edited. GAP-NOTED (historical record, intentionally left).
- Shipped consumer docs (`packages/core/templates/**`, `INSTALL-FOR-AI.md`): grep for the prune message / «framework-attributable» → no hits; nothing describes the old predicate to consumers. SWEPT-CLEAN.
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