instar 1.3.1078 → 1.3.1079
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/package.json +1 -1
- package/scripts/standards-coverage.mjs +88 -5
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/upgrades/1.3.1079.md +83 -0
- package/upgrades/side-effects/standards-false-claim-guard.md +156 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "f8a50d91b77caeaeef502ad348a966480db2d4026c42cee2d94d5e0c19438afd",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1079",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/canonical-migration-contracts.json",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "4db733ce07e33961f4be991c0cb8ea85a1f0f1851567c4952470567030f39933",
|
|
3
3
|
"registrySha256": "f8a50d91b77caeaeef502ad348a966480db2d4026c42cee2d94d5e0c19438afd",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1079"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
* - danglingCount = refs a standard names that are NOT on disk — fails the build if
|
|
17
17
|
* ABOVE ZERO (a guard file removed while a standard still cites it: a broken
|
|
18
18
|
* guarantee, the loudest signal).
|
|
19
|
+
* - falseClaimCount = standards that name NO guard while their own prose asserts
|
|
20
|
+
* running machinery ("a scheduled audit walks the list daily") — a false
|
|
21
|
+
* all-clear in the constitution, read by humans as protection that exists.
|
|
19
22
|
*
|
|
20
23
|
* Self-contained (no dist import) so it runs in CI without a build step — it
|
|
21
24
|
* re-implements the same deterministic parse → extract → verify the auditor does.
|
|
@@ -29,6 +32,10 @@
|
|
|
29
32
|
* STANDARDS_ENFORCED_RATIO_FLOOR — min enforced ratio 0..1 (default 0 — starts
|
|
30
33
|
* loose, ratcheted up as gaps close)
|
|
31
34
|
* STANDARDS_DANGLING_CEILING — max dangling refs (default 0 — zero tolerance)
|
|
35
|
+
* STANDARDS_FALSE_CLAIM_CEILING — max standards asserting an unnamed guard
|
|
36
|
+
* (default 1 — the measured 2026-07-31 count;
|
|
37
|
+
* ratchet to 0 once Cross-Store Coherence is
|
|
38
|
+
* resolved)
|
|
32
39
|
*/
|
|
33
40
|
import fs from 'node:fs';
|
|
34
41
|
import path from 'node:path';
|
|
@@ -71,6 +78,14 @@ const FLOORS = {
|
|
|
71
78
|
enforcedRatio: numEnv('STANDARDS_ENFORCED_RATIO_FLOOR', 0.64),
|
|
72
79
|
// Zero tolerance: a standard must NEVER cite a guard that doesn't exist.
|
|
73
80
|
danglingCeiling: numEnv('STANDARDS_DANGLING_CEILING', 0),
|
|
81
|
+
// A gap that ASSERTS running machinery is a false all-clear, not an honest gap.
|
|
82
|
+
// Set to the measured count on 2026-07-31 (1 — Cross-Store Coherence) rather than
|
|
83
|
+
// 0, following the enforced-ratio precedent of "starts loose": a new check must
|
|
84
|
+
// not fail a build for a pre-existing condition it just became able to see. It
|
|
85
|
+
// already does the load-bearing job at 1 — a NEW standard that claims machinery
|
|
86
|
+
// without naming it fails immediately. RATCHET TO 0 once Cross-Store Coherence
|
|
87
|
+
// either gets its audit built or has the claim amended out of its prose.
|
|
88
|
+
falseClaimCeiling: numEnv('STANDARDS_FALSE_CLAIM_CEILING', 1),
|
|
74
89
|
};
|
|
75
90
|
|
|
76
91
|
// ── Deterministic parse → extract → verify (mirrors the auditor, self-contained) ──
|
|
@@ -123,6 +138,57 @@ function extractRefs(a) {
|
|
|
123
138
|
return { files: dedupe(files).sort(), routes: dedupe(routes).sort(), markers: dedupe(markers).sort() };
|
|
124
139
|
}
|
|
125
140
|
|
|
141
|
+
/**
|
|
142
|
+
* ── FALSE-CLAIM DETECTION ────────────────────────────────────────────────────
|
|
143
|
+
* A standard that says "this is how we behave" and names no guard is an HONEST
|
|
144
|
+
* gap. A standard that says "a scheduled audit walks the list daily" is making a
|
|
145
|
+
* claim of FACT — and if it names no resolvable guard, that claim is a false
|
|
146
|
+
* all-clear sitting in the constitution, read by humans as protection that exists.
|
|
147
|
+
*
|
|
148
|
+
* Earned 2026-07-31: of the 24 documented-only standards, exactly three asserted
|
|
149
|
+
* running machinery. Two were true and merely unnamed (now cited). The third —
|
|
150
|
+
* Cross-Store Coherence — claims "A scheduled coherence audit walks the list on
|
|
151
|
+
* every machine daily". No such audit exists; of its three enumerated invariants
|
|
152
|
+
* one has a per-message delivery-time fail-safe and two have no checker at all.
|
|
153
|
+
* It is the standard earned from two identity stores contradicting each other for
|
|
154
|
+
* 19 days with no tripwire — the reactive shield shipped, the tripwire did not.
|
|
155
|
+
*
|
|
156
|
+
* PRECISION OVER RECALL, deliberately, and it is structural rather than a matter
|
|
157
|
+
* of tuning: this runs ONLY over standards already classified `documented-only`.
|
|
158
|
+
* A standard that asserts machinery AND names a resolvable guard is never
|
|
159
|
+
* examined, so the only way to be flagged is to claim a mechanism and cite
|
|
160
|
+
* nothing. Patterns are assertions of a specific mechanism running, never
|
|
161
|
+
* prescriptions ("must refuse", "should block"), which is what the negative
|
|
162
|
+
* lookbehind excludes.
|
|
163
|
+
*/
|
|
164
|
+
const CLAIM_PATTERNS = [
|
|
165
|
+
/\ba scheduled\s+[a-z-]*\s*(?:audit|job|check|sweep|pass)\b/i,
|
|
166
|
+
/\bwalks the list\b/i,
|
|
167
|
+
/\b(?:checked|runs|fires|re-?runs)\s+on a cadence\b/i,
|
|
168
|
+
/\bfails the build\b/i,
|
|
169
|
+
/\benforcement is\b/i,
|
|
170
|
+
/\bis enforced by\b/i,
|
|
171
|
+
/\bon every machine\s+daily\b/i,
|
|
172
|
+
/\bdaily\b[^.]{0,40}\baudit\b/i,
|
|
173
|
+
];
|
|
174
|
+
// Prescriptive framing near a match means the standard is stating a requirement,
|
|
175
|
+
// not asserting an existing mechanism. "must be checked on a cadence" is a rule;
|
|
176
|
+
// "a scheduled audit walks the list" is a claim.
|
|
177
|
+
const PRESCRIPTIVE_NEAR = /\b(?:must|should|shall|needs? to|ought to|is required to)\b[^.]{0,60}$/i;
|
|
178
|
+
|
|
179
|
+
function detectEnforcementClaims(a) {
|
|
180
|
+
const text = `${a.rule ?? ''}\n${a.inPractice ?? ''}\n${a.appliedThrough ?? ''}`;
|
|
181
|
+
const hits = [];
|
|
182
|
+
for (const re of CLAIM_PATTERNS) {
|
|
183
|
+
const m = re.exec(text);
|
|
184
|
+
if (!m) continue;
|
|
185
|
+
const before = text.slice(Math.max(0, m.index - 60), m.index);
|
|
186
|
+
if (PRESCRIPTIVE_NEAR.test(before)) continue;
|
|
187
|
+
hits.push(m[0].trim());
|
|
188
|
+
}
|
|
189
|
+
return dedupe(hits).sort();
|
|
190
|
+
}
|
|
191
|
+
|
|
126
192
|
const KIND_RANK = { ratchet: 4, gate: 3, lint: 2, 'spec-only': 1 };
|
|
127
193
|
function classifyFileGuard(ref) {
|
|
128
194
|
const base = ref.split('/').pop() ?? ref;
|
|
@@ -189,7 +255,8 @@ function compute() {
|
|
|
189
255
|
generatedAt: new Date().toISOString(),
|
|
190
256
|
registryFound: false,
|
|
191
257
|
total: 0, byKind: { ratchet: 0, gate: 0, lint: 0, 'spec-only': 0, 'documented-only': 0 },
|
|
192
|
-
enforcedRatio: 1, gaps: [],
|
|
258
|
+
enforcedRatio: 1, gaps: [], falseClaimCount: 0, falseClaims: [],
|
|
259
|
+
danglingCount: 0, danglingByStandard: [],
|
|
193
260
|
};
|
|
194
261
|
}
|
|
195
262
|
|
|
@@ -202,6 +269,7 @@ function compute() {
|
|
|
202
269
|
|
|
203
270
|
const byKind = { ratchet: 0, gate: 0, lint: 0, 'spec-only': 0, 'documented-only': 0 };
|
|
204
271
|
const gaps = [];
|
|
272
|
+
const falseClaims = [];
|
|
205
273
|
const danglingByStandard = [];
|
|
206
274
|
let danglingCount = 0;
|
|
207
275
|
|
|
@@ -224,7 +292,12 @@ function compute() {
|
|
|
224
292
|
for (const g of guards) { if (best === null || KIND_RANK[g] > KIND_RANK[best]) best = g; }
|
|
225
293
|
const kind = best ?? 'documented-only';
|
|
226
294
|
byKind[kind] += 1;
|
|
227
|
-
if (kind === 'documented-only')
|
|
295
|
+
if (kind === 'documented-only') {
|
|
296
|
+
gaps.push(a.name);
|
|
297
|
+
// A gap that ASSERTS running machinery is a false claim, not an honest gap.
|
|
298
|
+
const claims = detectEnforcementClaims(a);
|
|
299
|
+
if (claims.length > 0) falseClaims.push({ standard: a.name, claims });
|
|
300
|
+
}
|
|
228
301
|
if (dangling.length > 0) { danglingByStandard.push({ standard: a.name, refs: dangling.sort() }); danglingCount += dangling.length; }
|
|
229
302
|
}
|
|
230
303
|
|
|
@@ -234,7 +307,9 @@ function compute() {
|
|
|
234
307
|
return {
|
|
235
308
|
generatedAt: new Date().toISOString(),
|
|
236
309
|
registryFound: true,
|
|
237
|
-
total, byKind, enforcedRatio, gaps,
|
|
310
|
+
total, byKind, enforcedRatio, gaps,
|
|
311
|
+
falseClaimCount: falseClaims.length, falseClaims,
|
|
312
|
+
danglingCount, danglingByStandard,
|
|
238
313
|
};
|
|
239
314
|
}
|
|
240
315
|
|
|
@@ -258,8 +333,12 @@ function main() {
|
|
|
258
333
|
console.error(`[standards-coverage] registry=${report.registryFound} total=${report.total} ` +
|
|
259
334
|
`enforced-ratio=${report.enforcedRatio} (ratchet ${report.byKind.ratchet} / gate ${report.byKind.gate} / ` +
|
|
260
335
|
`lint ${report.byKind.lint} / spec-only ${report.byKind['spec-only']} / gap ${report.byKind['documented-only']}) ` +
|
|
336
|
+
`false-claims=${report.falseClaimCount} ` +
|
|
261
337
|
`dangling=${report.danglingCount}`);
|
|
262
|
-
console.error(`[standards-coverage] floors: enforced-ratio>=${FLOORS.enforcedRatio} dangling<=${FLOORS.danglingCeiling}`);
|
|
338
|
+
console.error(`[standards-coverage] floors: enforced-ratio>=${FLOORS.enforcedRatio} dangling<=${FLOORS.danglingCeiling} false-claims<=${FLOORS.falseClaimCeiling}`);
|
|
339
|
+
for (const fc of report.falseClaims) {
|
|
340
|
+
console.error(`[standards-coverage] FALSE CLAIM — "${fc.standard}" asserts running machinery (${fc.claims.map((c) => `"${c}"`).join(', ')}) but names no resolvable guard.`);
|
|
341
|
+
}
|
|
263
342
|
}
|
|
264
343
|
|
|
265
344
|
if (CHECK) {
|
|
@@ -273,10 +352,14 @@ function main() {
|
|
|
273
352
|
? ` — ${report.danglingByStandard.map((d) => `${d.standard}: [${d.refs.join(', ')}]`).join('; ')}`
|
|
274
353
|
: ''));
|
|
275
354
|
}
|
|
355
|
+
if (report.falseClaimCount > FLOORS.falseClaimCeiling) {
|
|
356
|
+
failures.push(`false claims ${report.falseClaimCount} > ceiling ${FLOORS.falseClaimCeiling}` +
|
|
357
|
+
` — ${report.falseClaims.map((f) => `${f.standard}: asserts [${f.claims.join(', ')}] but names no resolvable guard`).join('; ')}`);
|
|
358
|
+
}
|
|
276
359
|
if (failures.length > 0) {
|
|
277
360
|
process.stderr.write('\n❌ standards-coverage check failed:\n');
|
|
278
361
|
for (const f of failures) process.stderr.write(` - ${f}\n`);
|
|
279
|
-
process.stderr.write('\nFix: build a guard for an unguarded standard (raise the ratio),
|
|
362
|
+
process.stderr.write('\nFix: build a guard for an unguarded standard (raise the ratio), repair the dangling reference (the cited guard file was renamed/removed), or resolve a false claim — a standard whose prose asserts running machinery must either NAME the guard that runs it, or stop claiming it.\n');
|
|
280
363
|
process.exit(1);
|
|
281
364
|
}
|
|
282
365
|
if (!QUIET) console.error('✅ standards-coverage check passed.');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-31T05:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-07-31T05:09:09.114Z",
|
|
5
|
+
"instarVersion": "1.3.1079",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "f8a50d91b77caeaeef502ad348a966480db2d4026c42cee2d94d5e0c19438afd",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1079",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/canonical-migration-contracts.json",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "4db733ce07e33961f4be991c0cb8ea85a1f0f1851567c4952470567030f39933",
|
|
3
3
|
"registrySha256": "f8a50d91b77caeaeef502ad348a966480db2d4026c42cee2d94d5e0c19438afd",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1079"
|
|
5
5
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The CI standards-coverage check now distinguishes a standard that is **unguarded** from one that
|
|
9
|
+
**claims a guard it does not name**. Both were previously counted the same way — as
|
|
10
|
+
`documented-only` — which meant a rule could describe running machinery in the present tense while
|
|
11
|
+
nothing existed, and the audit would report it identically to a rule that never claimed anything.
|
|
12
|
+
|
|
13
|
+
`scripts/standards-coverage.mjs` gains `falseClaimCount` + `falseClaims[]`, a per-standard
|
|
14
|
+
`FALSE CLAIM —` line on stderr naming the phrases that triggered it, and a
|
|
15
|
+
`STANDARDS_FALSE_CLAIM_CEILING` floor checked under `--check`.
|
|
16
|
+
|
|
17
|
+
The distinction matters because a human reads the constitution. An honest gap says "nobody is
|
|
18
|
+
watching this." A false claim says "a scheduled audit walks the list on every machine daily" — and
|
|
19
|
+
is read as protection that exists.
|
|
20
|
+
|
|
21
|
+
## What to Tell Your User
|
|
22
|
+
|
|
23
|
+
Nothing changes in how the agent behaves. This is a build-time check on our own constitution: a
|
|
24
|
+
rule may no longer say "a scheduled check does this daily" unless it names the thing that actually
|
|
25
|
+
does it. One rule currently fails that test — the one written after two of our identity records
|
|
26
|
+
disagreed for nineteen days without anything noticing — and it now says so out loud on every build
|
|
27
|
+
instead of blending in with rules that never made a claim.
|
|
28
|
+
|
|
29
|
+
## Summary of New Capabilities
|
|
30
|
+
|
|
31
|
+
No new endpoint, route, or operator control. One new environment variable
|
|
32
|
+
(`STANDARDS_FALSE_CLAIM_CEILING`, default `1`) and additional named lines in CI output.
|
|
33
|
+
|
|
34
|
+
## Evidence
|
|
35
|
+
|
|
36
|
+
- Live run: `total=81 … gap 22 false-claims=1 dangling=0`, with the flagged standard and its matched
|
|
37
|
+
phrases named on stderr.
|
|
38
|
+
- The ceiling **bites** rather than only passing: default `1` → exit 0;
|
|
39
|
+
`STANDARDS_FALSE_CLAIM_CEILING=0` → exit 1 with the standard named in the failure text.
|
|
40
|
+
- Four tests in `tests/unit/standards-coverage-ratchet.test.ts` pin both sides of the boundary —
|
|
41
|
+
a claim with no guard fails; the same claim with a resolvable guard passes; a prescriptive "must"
|
|
42
|
+
passes; an ordinary unguarded standard passes. Verified against the real script on temp fixtures
|
|
43
|
+
(exit 1 / 0 / 0 / 0).
|
|
44
|
+
|
|
45
|
+
## Why now
|
|
46
|
+
|
|
47
|
+
Measured on 2026-07-31: of the 22 `documented-only` standards, exactly **three** assert running
|
|
48
|
+
machinery. Two were true and had simply never named their guard — both now carry citations. The
|
|
49
|
+
third, **Cross-Store Coherence Is an Invariant**, is false. Of its three enumerated invariants, one
|
|
50
|
+
has a per-message delivery-time fail-safe (`senderValidationGate`), two have no checker at all, and
|
|
51
|
+
the "scheduled coherence audit [that] walks the list on every machine daily" does not exist. It is
|
|
52
|
+
the standard earned from two identity stores contradicting each other for nineteen days with no
|
|
53
|
+
tripwire — the reactive shield shipped, the tripwire did not.
|
|
54
|
+
|
|
55
|
+
## Precision is structural, not tuned
|
|
56
|
+
|
|
57
|
+
The detector runs **only** over standards the script has already classified `documented-only`. A
|
|
58
|
+
standard that asserts machinery *and* cites a resolvable guard is never examined, so the only way to
|
|
59
|
+
be flagged is to claim a mechanism and point at nothing. Prescriptive framing is excluded — "must be
|
|
60
|
+
checked on a cadence by machinery" is a requirement being stated, not a claim being made.
|
|
61
|
+
|
|
62
|
+
Measured over the live registry: **1 flagged of 22 gaps across 81 standards, zero false positives on
|
|
63
|
+
the other 21.**
|
|
64
|
+
|
|
65
|
+
## Scope limit, stated plainly
|
|
66
|
+
|
|
67
|
+
This closes a **bookkeeping** gap, not a safety gap. It checks that a standard claiming a guard has
|
|
68
|
+
named one — never that the named guard is sufficient, or that it does its job. A rule can cite a
|
|
69
|
+
real, resolvable, running guard that works badly and this check is satisfied. That is the same limit
|
|
70
|
+
the existing coverage measure carries, restated rather than quietly inherited.
|
|
71
|
+
|
|
72
|
+
## Default is deliberately loose
|
|
73
|
+
|
|
74
|
+
The ceiling starts at **1**, the currently measured count, following the same "starts loose"
|
|
75
|
+
approach the enforced-ratio floor used: a new check must not fail a build for a pre-existing
|
|
76
|
+
condition it has only just become able to see. It is still load-bearing at that value — a **new**
|
|
77
|
+
standard that claims an unnamed guard fails immediately. Ratchet it to `0` once Cross-Store
|
|
78
|
+
Coherence either gets the audit it describes or has the claim amended out of its prose.
|
|
79
|
+
|
|
80
|
+
## Rollback
|
|
81
|
+
|
|
82
|
+
`STANDARDS_FALSE_CLAIM_CEILING=<n>` in the environment, with no code change. Reverting the commit
|
|
83
|
+
leaves the two existing floors untouched.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Side-Effects Review — Standards False-Claim Guard
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `standards-false-claim-guard`
|
|
4
|
+
**Date:** `2026-07-31`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Tier:** `1` (single build script + its unit tests; no runtime surface, no new authority)
|
|
7
|
+
**Second-pass reviewer:** `not required at Tier 1 — the PR is the review surface`
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
`scripts/standards-coverage.mjs` already answers "does this standard NAME a resolvable guard?".
|
|
12
|
+
It cannot answer "does this standard CLAIM one?". This adds that second question, scoped to
|
|
13
|
+
standards the script has already classified `documented-only`.
|
|
14
|
+
|
|
15
|
+
- **`detectEnforcementClaims(article)`** — matches assertions of a specific running mechanism
|
|
16
|
+
(`a scheduled <x> audit/job/check`, `walks the list`, `checked/runs/fires on a cadence`,
|
|
17
|
+
`fails the build`, `enforcement is`, `is enforced by`, `on every machine daily`) over the
|
|
18
|
+
Rule / In-practice / Applied-through fields. Prescriptive framing within 60 chars before a match
|
|
19
|
+
(`must`, `should`, `shall`, `needs to`, `ought to`, `is required to`) is excluded.
|
|
20
|
+
- **`falseClaimCount` + `falseClaims[]`** on the report, and a per-standard `FALSE CLAIM —` line on
|
|
21
|
+
stderr naming the matched phrases.
|
|
22
|
+
- **`STANDARDS_FALSE_CLAIM_CEILING`** floor (default **1**, the measured count) checked in
|
|
23
|
+
`--check`.
|
|
24
|
+
- Four tests in `tests/unit/standards-coverage-ratchet.test.ts` pinning both sides of the boundary.
|
|
25
|
+
|
|
26
|
+
Files: `scripts/standards-coverage.mjs`, `tests/unit/standards-coverage-ratchet.test.ts`.
|
|
27
|
+
|
|
28
|
+
## Decision-point inventory
|
|
29
|
+
|
|
30
|
+
- Claim detection over an article's prose — **add** — read-only classification of a markdown field;
|
|
31
|
+
decides a COUNT, never an action.
|
|
32
|
+
- `falseClaimCeiling` comparison in `--check` — **add** — can fail a CI build. This is the only
|
|
33
|
+
authority the change carries.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Over-block
|
|
38
|
+
|
|
39
|
+
The realistic over-block is a standard flagged for *describing a requirement* rather than asserting
|
|
40
|
+
a fact. Two structural mitigations, neither of them threshold-tuning:
|
|
41
|
+
|
|
42
|
+
- **Scope.** The detector runs ONLY over articles already classified `documented-only`. An article
|
|
43
|
+
that asserts machinery *and* cites a resolvable guard is never examined. There is no
|
|
44
|
+
configuration that widens this — it is a property of where the call sits in the loop.
|
|
45
|
+
- **Prescriptive exclusion.** A negative lookbehind drops a match preceded by `must` / `should` /
|
|
46
|
+
`shall` / `needs to` / `ought to` / `is required to` within 60 characters, which is the shape a
|
|
47
|
+
requirement takes ("must be checked on a cadence by machinery").
|
|
48
|
+
|
|
49
|
+
Measured over the live registry: **1 flagged of 22 gaps / 81 standards.** The one flagged is
|
|
50
|
+
verified false by hand (Cross-Store Coherence — its named daily audit does not exist; of its three
|
|
51
|
+
enumerated invariants, one has a per-message delivery-time fail-safe and two have no checker).
|
|
52
|
+
Zero false positives across the other 21.
|
|
53
|
+
|
|
54
|
+
Blast radius if it over-blocks anyway: a CI failure with a message naming the standard and the
|
|
55
|
+
exact phrases. The fix is a one-word prose edit or a `STANDARDS_FALSE_CLAIM_CEILING` bump. No
|
|
56
|
+
runtime behaviour, no agent action, no user-facing surface.
|
|
57
|
+
|
|
58
|
+
## 2. Under-block
|
|
59
|
+
|
|
60
|
+
Under-blocking is the expected and accepted state. The pattern list is deliberately narrow —
|
|
61
|
+
precision over recall — so a standard asserting machinery in wording not covered (e.g. "this is
|
|
62
|
+
policed continuously") is missed. That is the safe direction: a missed false claim leaves the
|
|
63
|
+
status quo (an ordinary gap), while a false positive would fail a build for a rule that is honest.
|
|
64
|
+
|
|
65
|
+
The ceiling starting at 1 rather than 0 is also deliberate under-blocking: it accepts the existing
|
|
66
|
+
false claim rather than failing a build for a pre-existing condition the check has only just become
|
|
67
|
+
able to see. It remains load-bearing at 1 — a *new* standard claiming an unnamed guard fails
|
|
68
|
+
immediately.
|
|
69
|
+
|
|
70
|
+
## 3. Level-of-abstraction fit
|
|
71
|
+
|
|
72
|
+
Correct layer. The question "is this standard's self-description honest?" is a property of the
|
|
73
|
+
standards registry, and the registry parser is the only component that already has each article's
|
|
74
|
+
fields in memory and its guard-resolution verdict. Putting it anywhere else would mean re-parsing
|
|
75
|
+
the constitution.
|
|
76
|
+
|
|
77
|
+
It is deliberately NOT in `StandardsEnforcementAuditor` (the library the `/conformance` route
|
|
78
|
+
serves): that surface is observe-only and non-gating by design, and this needs to fail a build.
|
|
79
|
+
The two implementations already disagree by one article (81 vs 82), recorded rather than averaged;
|
|
80
|
+
adding a gating check to the non-gating side would deepen that.
|
|
81
|
+
|
|
82
|
+
## 4. Signal vs authority compliance
|
|
83
|
+
|
|
84
|
+
This is a **brittle, low-context filter with build-failing authority**, which is exactly the shape
|
|
85
|
+
*Signal vs. Authority* warns about. Justification for why it is acceptable here rather than a
|
|
86
|
+
signal:
|
|
87
|
+
|
|
88
|
+
- The domain is **not** agent behaviour or message content — it is a fixed, human-authored,
|
|
89
|
+
version-controlled document of 81 articles. The input is bounded and inspectable, not adversarial
|
|
90
|
+
or open-world.
|
|
91
|
+
- The authority is **a CI failure on a docs file**, not a blocked action, message, or session. The
|
|
92
|
+
cost of a wrong verdict is one prose edit; nothing is lost and nothing is delayed for a user.
|
|
93
|
+
- The same file already carries two comparable deterministic gates (the enforced-ratio floor and
|
|
94
|
+
the zero dangling ceiling) with the same shape and authority.
|
|
95
|
+
|
|
96
|
+
Had this needed to judge *meaning* rather than *presence of an assertion pattern*, the correct
|
|
97
|
+
implementation would be an LLM gate per *Intelligent Prompts*. It does not: it reports a phrase
|
|
98
|
+
match to a human, who decides.
|
|
99
|
+
|
|
100
|
+
## 4b. Judgment-point check (Judgment Within Floors)
|
|
101
|
+
|
|
102
|
+
No judgment point is delegated. The check reports a count and the matched phrases; a human decides
|
|
103
|
+
whether to build the guard or amend the prose. The ceiling is an operator-visible constant with an
|
|
104
|
+
env override.
|
|
105
|
+
|
|
106
|
+
## 5. Interactions
|
|
107
|
+
|
|
108
|
+
- **Enforced-ratio floor** — independent. A false claim is also counted as a gap, so it already
|
|
109
|
+
depresses the ratio; this adds a *distinct* signal, it does not double-count into the ratio.
|
|
110
|
+
- **Dangling-ref ceiling** — disjoint by construction: dangling means a citation that does not
|
|
111
|
+
resolve; false-claim means no citation at all.
|
|
112
|
+
- **`/conformance/coverage`** — untouched. That route continues to report `documented-only` for the
|
|
113
|
+
same article; the two surfaces will now differ in *detail* (this one names it a false claim),
|
|
114
|
+
which is the intended asymmetry between a gating build check and an observe-only route.
|
|
115
|
+
|
|
116
|
+
## 6. External surfaces
|
|
117
|
+
|
|
118
|
+
None. No route, no config file, no user-visible message, no network call. The only new surface is
|
|
119
|
+
one env var (`STANDARDS_FALSE_CLAIM_CEILING`) and additional stderr lines in CI output.
|
|
120
|
+
|
|
121
|
+
## 6b. Operator-surface quality
|
|
122
|
+
|
|
123
|
+
The failure message names the standard, the matched phrases, and the two acceptable resolutions
|
|
124
|
+
("must either NAME the guard that runs it, or stop claiming it"). It teaches the required shape
|
|
125
|
+
rather than only reporting a number, per the refusal-message convention used elsewhere in the file.
|
|
126
|
+
|
|
127
|
+
## 7. Multi-machine posture
|
|
128
|
+
|
|
129
|
+
Not applicable. A build-time script over a git-tracked file; no state, no per-machine posture, no
|
|
130
|
+
replication, no lease interaction.
|
|
131
|
+
|
|
132
|
+
## 8. Rollback cost
|
|
133
|
+
|
|
134
|
+
Near zero, and there are three independent levers:
|
|
135
|
+
|
|
136
|
+
1. `STANDARDS_FALSE_CLAIM_CEILING=<n>` in the environment — no code change.
|
|
137
|
+
2. Revert the commit — the two prior floors are untouched by it.
|
|
138
|
+
3. Delete the `CLAIM_PATTERNS` entries — the check degrades to reporting 0 and passes.
|
|
139
|
+
|
|
140
|
+
No migration, no persisted state, nothing to un-write.
|
|
141
|
+
|
|
142
|
+
## Conclusion
|
|
143
|
+
|
|
144
|
+
Ship at Tier 1. The change is one build script and its tests; its only authority is a CI failure on
|
|
145
|
+
a documentation file; its precision is structural rather than tuned; and its default ceiling cannot
|
|
146
|
+
fail any build that passes today.
|
|
147
|
+
|
|
148
|
+
## Evidence pointers
|
|
149
|
+
|
|
150
|
+
- Finding that earned it: `docs/findings/2026-07-31-constitution-asserts-an-audit-that-does-not-exist.md`
|
|
151
|
+
- Live measurement: `total=81 … gap 22 false-claims=1 dangling=0`, with
|
|
152
|
+
`FALSE CLAIM — "Cross-Store Coherence Is an Invariant" asserts running machinery ("A scheduled
|
|
153
|
+
coherence audit", "on every machine daily", "walks the list") but names no resolvable guard.`
|
|
154
|
+
- Boundary verification against the real script on temp fixtures: claim-without-guard → exit 1;
|
|
155
|
+
claim WITH a resolvable guard → exit 0; prescriptive "must" → exit 0; ordinary unguarded standard
|
|
156
|
+
→ exit 0.
|