feat(make-claim-liveness): principle 36 — a script's make <target> claim must be true - #1366
Merged
Merged
Conversation
added 4 commits
August 10, 2026 12:14
…claim must be true tests/consumer-matrix/python-unfamiliar-stack-cell.sh:39 claimed it runs under `make consumer-matrix`. It never did — the target invokes only the pnpm-monorepo and npm-tarball cells. Same class as the ci-success needs-completeness defect: a header asserting a coverage property about itself that no mechanism verifies, and which is false. The claim is corrected to CI-only rather than made true by wiring the cell into the target. This cell is the only one of the three that mutates host state OUTSIDE its tmpdir: :346 runs `npm install -g @ast-grep/cli@0.44.1`, landing in `npm prefix -g`/bin — on a stock Homebrew macOS that is /opt/homebrew/bin, where ast-grep is already a brew-owned symlink into Cellar (verified on the host: `npm prefix -g` = /opt/homebrew, no npm-managed @ast-grep under lib/node_modules). A documented local-verify target must not collide with the operator's package manager. The header records the trigger to revisit: scope the install to $WORK and the cell becomes host-safe, at which point it should join the target. Principle 36 makes the class mechanically detectable at pre-push (principlesMetaSection already runs test:principles) rather than leaving it to a reader — the #hope-as-gate anti-pattern that let the original claim ship. It reuses `make -n` as the resolver, so make judges itself: variable expansion, @-silenced lines and transitive prerequisites all come free, and no second copy of make's semantics can drift. A precondition arm asserts the Makefile stays free of `include` / `$(shell …)`, the two constructs that would make `-n` unsafe. Anchor measured before shipping, not assumed: backticked `make <target>` in a comment line scores 0 false positives on the real corpus, against 14/17 for a bare `make \w+` regex over ordinary English prose. Escape hatch `# make-claim: allow <reason ≥20 chars>` mirrors the `ci-tool-pin: allow` token and covers the legitimate case of documenting a target's ABSENCE — the corrected header is its first production consumer. Paired negatives (principle 02): N36-1 false claim → RED (the shipped defect reconstructed), N36-5 true claim → GREEN (anti-tautology), N36-2 escape → GREEN, N36-3 short-rationale escape → RED, N36-4 nonexistent target → RED, N36-6 executable line → not a claim. Verified end-to-end: reverting the header makes the live gate fail naming `python-unfamiliar-stack-cell.sh:39`, and restoring it returns 10/10 green. Prior-art: prior-art-evaluations.md#244 (GNU make `-n` as target-recipe resolver + the absence of any tool verifying a script's prose claim about which build target invokes it — HYBRID: ADOPT the resolver, BUILD only the claim-extraction slice. checkmake, its MegaLinter descriptor, shellcheck, lychee and the comment-consistency literature all surveyed; T16 problem-class mismatch recorded — checkmake is single-file syntactic, this check is cross-file semantic).
…245) Merge-forward per .claude/rules/git-conflict-merge-forward.md (rebase / force-push is a dead end on a published branch). Sole conflict: this branch and #1362 both appended an SSOT row numbered 244. Resolution — staging's #1362 row KEEPS 244 (already merged, its own trailer cites that ID); this branch's GNU-make-resolver row is renumbered to 245 and ordered after it. Verified post-resolve: no conflict markers, no duplicate IDs across the table, sequence ends 243 · 244 · 245, markdownlint 0 errors, and the diff against staging is a single added line (the first resolve attempt also ate the blank line before the closing `---`, which turned the whole table into a setext heading — MD003 at :186; restored). CORRECTION to 4e4231a's trailer: it cites `prior-art-evaluations.md#244`, which after this merge is #1362's actionlint row. The correct citation for principle 36 is **#245**. Principle 11 F1 still resolves via the verbatim SSOT path match (row 245's "Capability matched" cell names packages/core/principles/36-make-target-claim-liveness.test.ts), and the squash body at merge time carries #245. Two staging commits landed mid-branch and each retires something this PR asserted: - #1362 wired `shipped-prettier` + `framework-fresh-install-validate-multistack` into `ci-success` `needs:` and gated it — exactly the GAP-FOUND in this PR's §1.7 Backward-check surface 2. Now CLOSED upstream; the PR body is updated so it does not go on asserting a defect that no longer exists. - #1363 sized the spawn-heavy hook suites to their real runtime — the local `test:hooks` flake this branch diagnosed (12 failures, all "timed out in 5000ms", all under hooks/; the same file passes 5/5 in 22.76s under --no-file-parallelism --testTimeout=30000). Note for the reviewer: #1362 independently BUILT a needs-completeness gate for the sibling surface while this PR built the make-target-claim gate. Two instances of one class — "an artifact asserts a falsifiable property about a channel it does not own" — were gated within an hour of each other, by different sessions, neither aware of the other. That is corroboration that the class is real, not a one-off. Prior-art: prior-art-evaluations.md#245 (GNU make `-n` as target-recipe resolver + the absence of any tool verifying a script's prose claim about which build target invokes it — HYBRID: ADOPT the resolver, BUILD only the claim-extraction slice).
…aken by #1362) Second collision from the same merge. #1362 landed `packages/core/principles/36-ci-needs-completeness.test.ts` on staging while this branch was in flight, and its SSOT row 244 names "principle 36" in its retirement trigger. That slot is now spoken for, so this branch's gate takes 37. Renamed via `git mv` (history preserved) and swept for stale references rather than renaming the file alone: header prose, both `describe` titles, the in-test "principle 36 must re-justify its mechanism" self-reference, all 10 `N36-*` arm ids → `N37-*`, and the verbatim path in SSOT row 245's "Capability matched" cell — which is the cell principle 11 F1 path-matches against, so a stale path there would have silently broken the prior-art resolution this branch depends on. `grep -rn "36-make-target\|N36-\|Principle 36 —"` over both files → no hits. `npx vitest run principles/` → 39 files, 370 passed, 1 skipped; principle 36 (#1362's) and principle 37 (this branch's) both green side by side. Trailer note: this commit takes a POSITIVE citation, not the escape hatch. The pre-push substance gate is right to demand it — `git mv` + a content edit registers as a new ≥80-LOC file under `packages/`, and the CLAUDE.md relocation carve-out ("byte-identical to a blob already tracked elsewhere") does NOT apply here, because the rename also rewrote the 36 → 37 references inside the file (15 insertions, 15 deletions). The first attempt used `Prior-art: skipped` and was correctly blocked. Prior-art: prior-art-evaluations.md#245 (GNU make `-n` as target-recipe resolver + the absence of any tool verifying a script's prose claim about which build target invokes it — HYBRID: ADOPT the resolver, BUILD only the claim-extraction slice. This commit relocates that same capability to slot 37; the SSOT row's "Capability matched" cell is updated in this commit to the new path.)
Third merge-forward on this branch; staging is moving fast today. #1368 appended its own row 245 while this branch held that number. Resolution is the same shape as the 244 → 245 one: the row already ON staging keeps its ID (its commit trailer cites it), and this branch's GNU-make-resolver row steps aside — now **246**. Verified post-resolve: no conflict markers, no duplicate IDs across the whole table, sequence ends 244 · 245 · 246, the blank line before the closing `---` is intact (its loss during the first resolve turned the table into a setext heading — MD003), and markdownlint reports 0 errors. Trailer bookkeeping: commits 4e4231a and bf5ef84 cite `#244` and `#245` respectively; after this merge the row is `#246`. Both citations are stale by renumber, not by substance — no verdict, rationale or evidence changed, only the row's position behind two upstream inserts. Principle 11 F1 resolves via the verbatim SSOT path match (row 246's "Capability matched" cell names packages/core/principles/37-make-target-claim-liveness.test.ts), which has been kept correct across every renumber, and the squash body at merge time carries #246. Prior-art: prior-art-evaluations.md#246 (GNU make `-n` as target-recipe resolver + the absence of any tool verifying a script's prose claim about which build target invokes it — HYBRID: ADOPT the resolver, BUILD only the claim-extraction slice).
7 tasks
artyhoo
pushed a commit
that referenced
this pull request
Aug 10, 2026
…sion with #1366) `gh pr list --state open` shows PR #1366 (branch claude/clever-kowalevski-975fb2, opened 2026-08-10T09:28:32Z) already adding `packages/core/principles/37-make-target-claim-liveness.test.ts`. It has the earlier claim on the slot, so this branch moves to 38 rather than making the two collide at merge. No registry or contiguity assertion exists over principle numbers (`ls packages/core/principles` + grep found none), so a gap would have been harmless anyway — but two files claiming one slot would not be. Renames the file and updates every reference: the test's own header, the discipline-self-check.yml and workflow-integrity.yml comments that name the enforcing gate, the run-local-ci-sweep.sh block header, and SSOT #245. Also drops a stale line in the discipline-self-check.yml header that still described the guard as `base=staging` after 9c00b25 changed it to `!= 'main'`. Trailer note: this is a rename, not a new capability — but the detector counts the new path as a ≥80-LOC add under `packages/`, and correctly refuses the escape hatch for it (the byte-identical carve-out does not apply, since the file's own header text changed 37→38). Citing the entry the artifact has carried since it was introduced is the accurate answer, not a bypass. Prior-art: prior-art-evaluations.md#245 (actionlint RuleGlob / zizmor / poseidon/wait-for-status-checks / blend/require-conditional-status-checks / GitHub-native required_status_checks API — verdict BUILD. Unchanged by this commit: the capability is the same required-context registrability gate, moved from slot 37 to slot 38.)
artyhoo
added a commit
that referenced
this pull request
Aug 10, 2026
…ger, and gate registrability (#1371) ## Summary Completes the half #1368 declared and explicitly deferred, and closes the gap that deferral left in the gate. #1368 (merged 10:26Z today) built the declaration model: every job declares `# required-context: yes|no` at the job, and principle 37 asserts both in-repo lists equal the declared set. It marked `discipline-self-check.yml`'s job `no`, naming the precondition in the marker itself: «To make it required, the path filter must first move from `on.pull_request.paths` into the job body … so the context always reports.» The 2026-08-10 operator ruling requires that context on `staging`, so the precondition had to be met. Two things here: the §1.7 context becomes registrable and is declared required, and principle 37 gains the arm that makes «declared required ⇒ actually reportable» a mechanism instead of a comment. **Scope note, stated plainly:** this branch was opened before #1368 existed (my in-flight probe ran at 09:33Z; #1368 was created at 09:39Z) and originally carried its own parallel list-gating design. That work is discarded — #1368's declare-at-the-job model is better, and this PR is rebased onto it. The net diff is 5 files. ## Changes **1. `discipline-self-check.yml` — registrable, and declared required.** The `paths:` trigger filter is removed, and the marker flips to `# required-context: yes`. GitHub's guidance is the reason the filter and the requirement are incompatible: «Workflows skipped due to path filtering, branch filtering, or commit messages will remain in a pending state and block merging. To avoid this, do not require workflows that can be skipped.» Removed rather than reimplemented in the job body. §1.7 already has a declared out-of-scope mechanism — the `### §1.7 Skipped: <≥60 chars>` marker, which `.github/pull_request_template.md:61` emits as the DEFAULT for every PR. A scope list inside the job body would have been a third hand-maintained copy of «what counts as discipline-bearing», in a PR whose subject is that such copies drift. **Measured before removing it:** of the 40 most recent merged base=staging PRs, 38 already carry a valid §1.7 section (11 Skipped markers, 27 Forward/Backward pairs). #1363 and #1337 are the only two that would newly redden, each needing the one template-default line. Also adds `if: base.ref != 'main'`: a promote PR aggregates ~90 squashed commits and has no single meaningful forward/backward pair, so today it gets a guaranteed RED. Deliberately not the siblings' `== 'staging'` — theirs is subject-scoped (a kickoff's fidelity verdict, a stale-base rebuild against staging), whereas §1.7 also applies to the base=`epic/*` PRs `CLAUDE.md:121` permits agents to merge, and `== 'staging'` would have silently dropped that coverage. Safe because a job skipped by `if:` reports `skipped`, which protection accepts — «if a job is skipped due to a conditional, it reports success». **2. Both derived lists gain the newly-declared context**, per #1368's rule that the three move together. The sweep's `REGISTRATION STATE` line is also rewritten to name contexts instead of «the last three» / «the first two»: appending a sixth entry above it silently falsified both phrases — the same positional-drift shape principle 37 exists to catch. **3. Principle 37 arms (i)–(k) — registrability.** The existing arms assert the declared set is *restated consistently*. None asserts a declared entry is *safe to register at all*: `hasPullRequestTrigger` (`:112`) checks only that a `pull_request:` trigger EXISTS, never that it is unfiltered. So flipping any marker to `yes` inside a `paths:`-filtered workflow passes every existing arm and then freezes every PR matching none of the filter. That is exactly the hazard #1368 wrote into the `discipline-self-check.yml` marker and deferred — and prose is not a mechanism (`.claude/rules/attention-is-not-a-mechanism.md:1`). - **(i)** every `required-context: yes` job sits behind an unfiltered `pull_request:` trigger (no trigger at all is equally an offender). - **(j)** paired negative — re-adding the removed filter must be detected. - **(k)** parser scope: `pull_request:` only (`workflow-integrity.yml` filters *both* `push:` and `pull_request:`, so a file-wide scan would look identical here); inline `on: [push, pull_request]` resolves to `[]` rather than a false «no trigger» RED; job-level `if:` is never flagged, because flagging it would forbid the promote-flow guards all three PR-body gates rely on. Added to principle 37 rather than shipped as a parallel principle: that file already owns the declaration model and its parsers, so a second principle would have had to duplicate them. ## Operator action required **Order matters — register only AFTER this merges.** `discipline-self-check.yml` on `staging` is still `paths:`-filtered until then; registering its context first deadlocks every non-matching PR. Live state re-read 2026-08-10: `{"strict":false,"checks":[{"app_id":15368,"context":"ci-success"},{"app_id":15368,"context":"fidelity-verdict-in-pr-body"},{"app_id":15368,"context":"stale-revert-in-pr-diff"}]}` — three registered against six now declared. ```bash gh api -X PATCH repos/artyhoo/getff/branches/staging/protection/required_status_checks --input - <<'JSON' { "strict": false, "checks": [ {"context": "Template render probes — P1/P4/P6 (deterministic)", "app_id": 15368}, {"context": "capability PR carries Prior-art line in PR body (squash-survival)", "app_id": 15368}, {"context": "ci-success", "app_id": 15368}, {"context": "fidelity-verdict-in-pr-body", "app_id": 15368}, {"context": "stale-revert-in-pr-diff", "app_id": 15368}, {"context": "§1.7 forward+backward sections present in PR description", "app_id": 15368} ] } JSON ``` - Payload generated from the declared set, not retyped; `strict: false` + `app_id: 15368` reproduce the current pinning. Passing bare `contexts[]=` would drop the app pinning. - This registers all six declared contexts, including the two #1368 left declared-but-unregistered. Register a subset if you want them staged separately — but every unregistered declared context is one that can go red while the PR merges. - **PRs open at registration time will not report the new checks until they get a fresh PR event** (a push or a body edit — all the relevant workflows carry `types: [… edited …]`). - Verify: `gh api repos/artyhoo/getff/branches/staging/protection --jq '.required_status_checks.contexts[]'` → 6 lines. **Effect on the 6 currently-open PRs, measured (`gh pr list --state open`):** #1366 carries a valid §1.7 section. The other five — #1331 plus four Dependabot bumps (#1230, #1217, #1216, #1149) — do not, and will show a red §1.7 once this merges. This does **not** create a new blocked class: those four are *already* blocked by an existing required check — `gh pr checks 1230` shows `fidelity-verdict-in-pr-body fail` today, because Dependabot's body carries no `## Fidelity verdict` either, and Dependabot cannot edit its own body. Whatever already handles that for these PRs handles this too. Worth an explicit decision eventually; out of scope here. ## Test plan - [x] `npx vitest run packages/core/principles/37-required-context-completeness.test.ts` → 11/11 (8 pre-existing + 3 new). - [x] **Paired-negative on disk, not just in-parser.** The `paths:` filter was re-added to the real `discipline-self-check.yml` and the suite re-run: arms (i) and (j) both RED, with the deadlock message naming the job. Restored → 11/11 green. - [x] `npm --prefix packages/core run test:principles` → 40 files, 381 passed, 1 skipped, 0 failed. - [x] `bash scripts/run-local-ci-sweep.test.sh` → ALL PASS (the sweep's own paired-negative, after editing its header block). - [x] `actionlint` on both changed workflows → clean; `bash -n scripts/run-local-ci-sweep.sh` → clean; `npm run format:check` → clean. - [x] Full `bash scripts/run-local-ci-sweep.sh` on the pre-merge tree: PASS through `vitest-composition`; `format-check`, `rule-index-check`, `alwayson-budget`, `test:render`, `test:ir` also green individually. - [x] Live branch protection re-read before writing the operator command. **Pre-existing failures on this host, NOT introduced here** (both reproduced on a clean `staging` checkout, so out of scope per CLAUDE.md «PR strategy»): - `packages/core/hooks/getff-work.test.ts` — 3 failures (`CC-DEFERRAL`, `NON-CC-PRINT`, `NO-LAUNCH-FLAG`, all `expected 1 to be +0`). Verified identical on `/Users/art/code/rules-as-tests-aif` at `staging`. - `packages/core/principles/20-bundle-classification.test.ts` — one 30 s timeout under full-suite parallel load; passes in isolation in 45 s (~3.3 s/test), and green in the 40-file run above. Same spawn-heavy class #1363 addressed. ## Provenance n/a — not a stage PR. Direct implementation of the 2026-08-10 operator ruling, rebased onto #1368 after that PR merged mid-flight. ## Review findings n/a — not a stage PR. Three findings came from this branch's own pre-handoff self-review and were fixed before handoff: the `!= 'main'` guard scoping (`== 'staging'` would have dropped epic/* coverage); the inline `on: [push, pull_request]` grammar in the arm-(i) parser (an unhandled inline form would have false-REDded the one unconditionally safe shape); and the sweep's positional «last three» claim, which this PR's own sixth list entry falsified. **Also surfaced, not fixed** (no drive-by, per CLAUDE.md «PR strategy»): `origin/staging` currently carries **two** principle-37 files — `37-make-target-claim-liveness.test.ts` (#1366) and `37-required-context-completeness.test.ts` (#1368) — merged within minutes of each other. Nothing asserts principle-slot uniqueness, which is the same missing-mechanism class as everything else in this PR. ## Fidelity verdict FIDELITY: skipped — no kickoff or spec governs this change; it implements a direct operator ruling recorded in the task brief, not a stage of a planned umbrella. ## Parked questions n/a — not a stage PR. ## §1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files) ### §1.7 Forward-check applied **`no-paid-llm-in-ci.md §2`** — arms (i)–(k) are `readFileSync` + regex, zero API-billed calls; the workflow edits remove a trigger filter and add no step (`packages/core/principles/37-required-context-completeness.test.ts:71` imports only `node:fs`/`node:path`/`node:url`). **`ci-tool-pinning.md §1`** — no bare `run:` tool install added or altered; `.github/workflows/discipline-self-check.yml:79` still has no install step. **`attention-is-not-a-mechanism.md §1`** — this is the rule's own case: the deferred fix lived as a prose marker comment on a job, i.e. detection resting on someone reading it; arm (i) replaces it with a deterministic gate, and the surviving best-effort job at `workflow-integrity.yml:32` stays explicitly labelled non-load-bearing. **`rule-enforcement-channel-selection.md §3`** — mechanically detectable → gate; a principle test is the earliest firing channel (`packages/core/hooks/pre-push.ts:1269` runs `test:principles` at pre-push, CI as backstop). **`build-first-reuse-default.md §3`** — DeepWiki `rhysd/actionlint` + context7 `/github/docs` ×3 phrasings + WebSearch ×2 ran before the BUILD verdict; recorded as SSOT #247 in `docs/meta-factory/prior-art-evaluations.md:319` and cited by the commit trailer. The same rule drove the *shape*: the arms were folded into the existing principle 37 rather than shipped as a parallel principle, reusing its declaration model and parsers instead of duplicating them. **`language-discipline.md §1`** — all added comments, test prose and commit bodies are English. **CLAUDE.md capability-commit gate** — the diff adds no new file ≥80 LOC and no dependency; the trailer cites the SSOT entry anyway, since the capability is real even though the artifact is an extension. ### §1.7 Backward-check applied Delegated cold to `agents/backward-sweep-auditor.md` per T21 — handed only the two class predicates, never the diff, branch or PR, so it could not restate this change. (Dispatched against the pre-merge tree; both classes are structural and unaffected by the rebase, and finding B-2 below is about files this PR does touch, so it was re-checked by hand against the merged state.) **Class A — «a hand-maintained list whose correct contents are determined by a population declared elsewhere, with nothing asserting the mirror».** Population 13, all reached. SWEPT-CLEAN: `packages/core/principles/27-*` (install.sh copy-list ⊇ import graph), `.claude/rules/00-rule-index.md` + AGENTS.md (`scripts/render-rule-index.mjs --check`, `packages/core/hooks/pre-push.ts:1253`), agents/rules/skills header coverage (`packages/core/principles/09-doc-authority-hierarchy.ts:228`), `tests/agnosticism` probes (`run-audit.sh:14` globs, no hand list), `.claude/hooks/**` channel markers. **Six GAP-FOUND, none in this diff:** (1) the Tier-0 core set exists in three copies — `packages/core/principles/31-rule-channel-declaration.ts:58` (3 entries) vs `scripts/render-rule-index.mjs:56` and `scripts/render-rule-channels.mjs:75` (2 each), the latter's comment at `:70` claiming a «verbatim» mirror while differing, and `--check` regenerates *from* the stale copy so it stays green; (2) `packages/core/principles/09-doc-authority-hierarchy.ts:28` `REQUIRED_HEADER_DOCS` is static, and 5 `docs/meta-factory/*.md` carry no header; (3) `tests/install-sh/meta-all-wired.test.sh:23` loops only `tests/install-sh/*.test.sh`, leaving 29 of 127 tracked `*.test.sh` unasserted, 13 invoked by nothing; (4) `setup.d/LAYERS.md:3` declares itself authoritative for the layer list but carries 13 rows against 16 globbed by `install.sh:1142`; (5) `setup.d/10-skills.sh:108` names 12 of 14 skills, in sync but unasserted; (6) `scripts/run-local-ci-sweep.sh:123` gate table (low severity — a stale row yields a false LOCAL green and gates no merge). **Class B — «a declared gate whose channel cannot fire where it is relied upon».** Population 9, all reached. SWEPT-CLEAN: `guard-liveness-fullsweep.yml:20`, `pr-body-prior-art.yml:28`, `audit-self.yml:549` lychee vacuity guard, `audit-self.yml:1058` toolchain non-vacuity, `scripts/host-verify-coverage.sh:4`. `workflow-integrity.yml`'s own `paths:` filter is clean precisely because it is declared `required-context: no` — which arm (i) now asserts rather than assumes. **Three GAP-FOUND:** (1) 113 of 262 `packages/core` test files execute at no channel — `packages/core/vitest.config.ts:9` allowlists 22 dirs, pre-push runs 4 `test:*` scripts and CI 7, so `validator/gate-conflict.test.ts` and six other self-described gates never run; `audit-self.yml:389` states the class in the repo's own words and `:394` defers it; (2) all three PR-body required gates skip on base=`epic/*` while `CLAUDE.md:121` permits agents to merge there; (3) changed-markdown link integrity is pre-push-only and degrades to a warning when lychee is absent (`packages/core/hooks/pre-push.ts:1499`), while `link-checker.yml:26` is paths-filtered to `lychee.toml`. **Acted on within scope:** B-2 is the one finding this PR's own files carry, and it is fixed here for the §1.7 gate — `discipline-self-check.yml:77` uses `!= 'main'`, so base=`epic/*` stays covered. The cold agent reached that defect independently, on the two sibling gates, without seeing the diff; the two siblings keep `== 'staging'` and are **not** changed here, since narrowing them is a different subject with a recorded prior decision (`docs/superpowers/specs/2026-07-23-acceptance-contour-design.md:161`). The other eight findings are sibling surfaces outside this umbrella and are surfaced, not fixed — the Tier-0 three-copy set is the closest structural twin of what principles 36 and 37 each fixed once. Prior-art: prior-art-evaluations.md#247 (actionlint RuleGlob / zizmor / poseidon/wait-for-status-checks / blend/require-conditional-status-checks / GitHub-native required_status_checks API — verdict BUILD, narrow complement to #245. actionlint validates `paths:` glob SYNTAX but per DeepWiki on `rhysd/actionlint`, 2026-08-10, «does not analyze the implications for branch protection» and «does not have a feature to cross-check a list of status-check context strings against job `name:` fields»; the marketplace answers to skipped-but-required are RUNTIME waiters that add a job to wait on other jobs, never a static assertion that a declaration is registrable.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
tests/consumer-matrix/python-unfamiliar-stack-cell.sh:39claimed it runs undermake consumer-matrix. It never did — Makefile:34-38 invokes only the pnpm-monorepo and npm-tarball cells. Same class as theci-successneeds-completeness defect: a header asserting a coverage property about itself that no mechanism verifies, and which is false. Different surface, so it was deliberately left out of that PR per the CLAUDE.md no-drive-by rule.Two things ship here: the claim is corrected, and the class becomes mechanically detectable.
The decision: (b) correct the header, not (a) wire the cell in
Not the cheap option — the reason (a) is wrong is specific, and it is about host safety rather than cost.
The cell does not degrade when
ast-grepis absent. It installs it:python-unfamiliar-stack-cell.sh:346runsnpm install -g "$ASTGREP_PKG"unconditionally. That lands innpm prefix -g/bin. Verified on a stock Homebrew macOS host:npm prefix -g/opt/homebrew→ writes to/opt/homebrew/binreadlink /opt/homebrew/bin/ast-grep../Cellar/ast-grep/0.44.1/bin/ast-grep— brew-owned symlinkls /opt/homebrew/lib/node_modules/@ast-grepSo
make consumer-matrix— the documented local-verify entry point — would either fail on the collision or replace a brew-managed binary. Neither sibling cell does any global install; this is the only one that mutates state outside its tmpdir. A local-verify target must not do that to an operator's package manager.The header now records the trigger to revisit: scope the install to the cell (
npm install --prefix "$WORK/tools"), and it becomes host-safe and should join the target. That keeps (a) available rather than closing it off.Incidental finding while checking (a): the cell's Node-strip loop removes every PATH dir containing node/npm/npx. On this host that is
/opt/homebrew/bin, which also holdspython3andgit— both survive only because/usr/binfallbacks exist. Recorded, not acted on; the cell is CI-only, where the strip is exercised as designed.The gate: principle 37
packages/core/principles/37-make-target-claim-liveness.test.ts— every backtickedmake <target>in a comment line oftests/**/*.sh+scripts/**/*.shmust name a target whose resolved recipe actually invokes the claiming file.Resolver is reused, not rebuilt.
make -n <target>is make judging itself:$(CURDIR)expansion,@-silenced lines and transitive prerequisites all come free (make -n self-auditresolves all three prerequisites). A Makefile regex would be a second copy of make's semantics, free to drift — the failure dual-implementation-discipline.md exists to prevent. A precondition arm asserts the Makefile stays free ofinclude/$(shell …), the two constructs that make-nunsafe.False-positive risk was measured, not assumed — this was the reason to weigh before building. Over the real corpus:
make \w+`make <target>`in a comment linePopulation is only 3 sites today, which argues against a gate — except attention had already failed on 1 of those 3, through a stage PR and its review. Both existing cells got their header by copy-paste, which is exactly how the false one was born.
Channel:
principlesMetaSection()at pre-push.ts:1267 already runstest:principles, so this fires at pre-push, not only CI — no new hook machinery.Escape hatch:
# make-claim: allow <reason ≥20 chars>, mirroringci-tool-pin: allow(ci-tool-pinning.md §3). It covers documenting a target's absence; the corrected header is its first production consumer.Paired negatives
TODO/ baremake xon an executable linePlus a vacuity guard: the live corpus must yield ≥3 claims, so a silently-broken regex cannot make the positive arm pass trivially.
End-to-end mutation proof. Reverting the header makes the live gate fail with:
…with the other 9 arms still green. Restoring the header returns 10/10.
Verification
npm run test:principles→ 39 files, 370 passed, 1 skipped (post-merge; fix(ci-success): wire the two unwired jobs, and gate the wiring so it cannot drift again #1362's principle 36 and this branch's 37 both green)npx tsc --noEmit -p packages/core/tsconfig.json→ exit 0npm run format:check→ clean§1.7 Forward-check applied
Capability-commit gate: new file
packages/core/principles/37-make-target-claim-liveness.test.tsis ≥80 LOC underpackages/, so it is a capability commit — carries aPrior-art:trailer citing new SSOT row prior-art-evaluations.md:319 (#246), landed in the same commit per CLAUDE.md. VerdictHYBRIDis already in the registered set at 11-build-first-reuse-default.test.ts:89, so no VERDICTS extension was needed and principle 11 passes unmodified. Research per build-first-reuse-default.md §3: context7 on GNU Make + WebSearch ×3 phrasings; checkmake/shellcheck/lychee surveyed, T16 problem-class mismatch recorded in the SSOT row.no-paid-llm-in-ciholds — the gate isexecFileSync+ regex, zero API calls. Escape-token shape follows the precedent at ci-tool-pinning.md §3, and the comment-line carve-out mirrors unpinned-tool-install.ts:75 rather than inventing a second convention.§1.7 Backward-check applied
Class = an artifact asserts a falsifiable property about a channel it does not own, and nothing verifies it. Complete enumeration of surfaces where that class occurs, each probed:
grep -rnoE 'make [a-z-]+' tests/ scripts/→ exactly 3: pnpm-monorepo-cell.sh:46 SWEPT-CLEAN (true), npm-tarball-cell.sh:41 SWEPT-CLEAN (true), python cell GAP — fixed here. Now gated.ci-successneeds-completeness — GAP-FOUND at sweep time, CLOSED UPSTREAM before merge. When this sweep ran,scripts/ci-success-gate.sh:6claimed the job "needs:every audit-self PR job" whileshipped-prettier(audit-self.yml:99) andframework-fresh-install-validate-multistack(audit-self.yml:1094) were absent fromneeds:. fix(ci-success): wire the two unwired jobs, and gate the wiring so it cannot drift again #1362 landed on staging mid-branch and fixed exactly that, gate included (packages/core/principles/36-ci-needs-completeness.test.ts). Merged forward — nothing left to do here. The finding is kept rather than deleted because it is what makes this PR's class claim non-hypothetical: two instances of the class were independently gated within an hour, by sessions unaware of each other.Class: A — companion test shipped at <path>claims — every path extracted from.claude/rules/*.mdresolves on disk (0 missing). SWEPT-CLEAN today, but verified by nothing; latent instance of the same class.run-local-ci-sweep.shCOVERED/UNREACHABLE lists (run-local-ci-sweep.sh:20-55) — prose claims about which CI jobs a green sweep predicts. Unverified by construction; already a known open item, untouched here.setup.d/**— deliberately OUT of scope: shipped into consumer repos that have no Makefile of ours, so a make-target claim there would concern a different build file. Documented in the test header, not silently omitted.Exemption mechanism: the
make-claim: allowsentinel, with its own arms — N37-2 proves the exemption preserves intent, N37-3 proves removing the rationale breaks it, so the hatch is not a blanket bypass (37-make-target-claim-liveness.test.ts:290).Fidelity verdict
FIDELITY: skipped — direct operator request, not a stage PR: there is no kickoff or spec substrate to audit WHAT-conformance against, and this body declares no
## Provenance. The self-assessment below stands on its own evidence.Conforms. Scope as asked: the false claim is fixed, the fix is the reasoned option rather than the cheap one, and the general problem was weighed on measured evidence (0/3 vs 14/17 false-positive rate) before the gate was built. Paired negatives ship with the check, as required. One deviation from a literal reading of the brief: the scope is
tests/**/*.shplusscripts/**/*.sh— same class, same anchor, zero current hits there, cheap to include and it forecloses the obvious next copy-paste. Surface 2 of the backward sweep was a real defect of this exact class, found by the sweep and then closed upstream by #1362 before this PR merged — it is not covered by principle 37 and did not need to be. Two collisions with that PR were absorbed here rather than papered over: the SSOT row renumbered twice (244 → 245 → 246, stepping aside for #1362 and #1368) and this gate renumbered principle 36 → 37. Staging absorbed three merge-forwards during this branch; each conflict was a pure ID collision, resolved by yielding the number to whichever row was already merged.Follow-ups (not in this PR)
npm install --prefix "$WORK/tools"; unlocks option (a) and lets the cell joinconsumer-matrix.Prior-art: prior-art-evaluations.md#246 (GNU make
-nas target-recipe resolver + the absence of any tool verifying a script's prose claim about which build target invokes it — verdict HYBRID: ADOPTmake -nas the resolution engine, BUILD only the claim-extraction slice. checkmake, its MegaLinter descriptor, shellcheck and lychee surveyed; T16 problem-class mismatch recorded — checkmake is single-file syntactic, this check is cross-file semantic. Restated here becausePrior-art:commit trailers are dropped at squash.)