clearotron 0.2.0 → 0.3.0-beta.0
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/.env.example +52 -0
- package/INSTALL.md +9 -7
- package/README.md +2 -1
- package/bin/example.mjs +97 -32
- package/bin/onboard.mjs +58 -20
- package/bin/start.mjs +7 -0
- package/bin/stop.mjs +65 -3
- package/build-info.json +2 -2
- package/docs/RELEASES.md +6 -4
- package/docs/architecture/04-configuration-reference.md +1 -1
- package/driver/CHANGELOG.md +51 -0
- package/driver/ask-ledger.mjs +69 -1
- package/driver/declination-call.mjs +32 -0
- package/driver/driver.config.mjs +20 -0
- package/driver/engine/mcp/recording-server.mjs +4 -0
- package/driver/gateway.mjs +8 -3
- package/driver/knockout-assess-record.mjs +5 -1
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +123 -3
- package/driver/predelivery-lint.mjs +23 -5
- package/driver/publish/knockout.mjs +12 -6
- package/driver/publish/render-knockout.mjs +133 -23
- package/driver/publish/report-data.mjs +13 -3
- package/driver/publish/seed-pool.mjs +24 -9
- package/driver/record-carry.mjs +139 -0
- package/driver/reference-score.mjs +53 -3
- package/driver/reference-strip-signatures.mjs +68 -0
- package/driver/register-digest-record.mjs +31 -1
- package/driver/repairs.mjs +1 -1
- package/driver/result-noun-fields.mjs +7 -0
- package/driver/skills/knockout-assess/SKILL.md +10 -4
- package/driver/stages-knockout.mjs +1 -1
- package/driver/stages.mjs +1 -1
- package/driver/suite-census.json +108 -30
- package/driver/unit-inventory.mjs +47 -0
- package/driver/unit-state-verdict.mjs +8 -8
- package/driver/verify-knockout.mjs +9 -1
- package/driver/verify.mjs +2 -2
- package/driver/whatif-memo-run.mjs +45 -4
- package/mcp-server/CHANGELOG.md +10 -0
- package/mcp-server/lib/brief.mjs +15 -0
- package/mcp-server/lib/driver.mjs +6 -0
- package/mcp-server/lib/knockout.mjs +435 -0
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/lib/whatif.mjs +10 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +69 -4
- package/package.json +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +10 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/ai-page-render-check.mjs +2 -1
- package/scripts/clearances-render-check.mjs +2 -1
- package/scripts/drain-preflight.mjs +2 -2
- package/scripts/env-audit.mjs +20 -0
- package/scripts/freeze-example-run.mjs +3 -3
- package/scripts/headless-page.mjs +274 -0
- package/scripts/home-render-check.mjs +2 -1
- package/scripts/live-surface-check.mjs +86 -17
- package/scripts/mint-reference-strip-backlog.mjs +41 -0
- package/scripts/release-await-cut.mjs +95 -7
- package/scripts/release-version-pr-checks.mjs +25 -1
- package/scripts/render-check.mjs +61 -2
- package/scripts/report-frame-check.mjs +12 -0
- package/scripts/report-screenshot.mjs +62 -2
- package/scripts/revisit-render-check.mjs +3 -2
- package/scripts/score.mjs +14 -0
- package/shared/access-audience.mjs +215 -0
- package/shared/tracked-files.mjs +31 -0
- package/scripts/deploy-test.sh +0 -309
|
@@ -168,6 +168,36 @@ const FINDING_COLUMNS = Object.freeze(
|
|
|
168
168
|
// finding it there together with screen_verdict / class / status.
|
|
169
169
|
const NEGATIVE_COLUMNS = Object.freeze(["Mark", "Search Term / Variant", "Result", "Notes"]);
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* The "Mark" cell for one Negative-results row.
|
|
173
|
+
*
|
|
174
|
+
* A `duplicate-of-surfaced` row is not a statement about the MARK. It is a statement about one
|
|
175
|
+
* registration of a mark whose position is already reported above it — the digest's own rule is "one
|
|
176
|
+
* row per POSITION, never one per registration of the same right". Printing the bare mark under a
|
|
177
|
+
* column headed "Mark" says the opposite of what the row means, and the document then reads as both
|
|
178
|
+
* "keep this mark, here is the reasoning" in the incumbent table and "no separate row for this mark"
|
|
179
|
+
* here. Nine readers scan this file, and one of them is the drafting seat that decides what the client
|
|
180
|
+
* is shown.
|
|
181
|
+
*
|
|
182
|
+
* Naming the record removes the contradiction and changes no judgment the digest made: the row is
|
|
183
|
+
* still a drop, still on the same ground, still carrying the seat's own reason.
|
|
184
|
+
*
|
|
185
|
+
* ✕ EVERY OTHER GROUND IS LEFT ALONE, deliberately. `off-field`, `dead-status` and `out-of-class` are
|
|
186
|
+
* about the record on its own terms, where the bare mark is what a reader wants and is not ambiguous.
|
|
187
|
+
* A blanket change to this column would touch every negative row in every report for a defect that
|
|
188
|
+
* only exists on the duplicate class.
|
|
189
|
+
*
|
|
190
|
+
* The office comes from the record uri (`/mark/wo/…` -> `WO`); every uri in the archived corpus carries
|
|
191
|
+
* a two-letter office in that position. When it cannot be read the BARE MARK is printed rather than a
|
|
192
|
+
* broken qualifier — a missing qualifier is a smaller defect than "OSLER DELPHI — UNDEFINED record".
|
|
193
|
+
*/
|
|
194
|
+
export function negativeMarkCell(row) {
|
|
195
|
+
const mark = String(row?.cells?.mark ?? "").trim();
|
|
196
|
+
if (row?.ground !== "duplicate-of-surfaced" || !mark) return mark;
|
|
197
|
+
const office = String(row?.cells?.uri ?? "").split("/")[2] ?? "";
|
|
198
|
+
return /^[a-z]{2}$/i.test(office) ? `${mark} \u2014 ${office.toUpperCase()} record` : mark;
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
// ── THE FLOOR, AND WHAT ITS ZERO MEANS ────────────────────────────────────────────────────────────
|
|
172
202
|
//
|
|
173
203
|
// `validators.registerFindings` carried `nonEmpty(c)` with no character number, so there is no
|
|
@@ -353,7 +383,7 @@ export function renderRegisterFindings(model, facts = emptyFacts()) {
|
|
|
353
383
|
out.push(DIGEST_SECTIONS.negative, "");
|
|
354
384
|
out.push(model.negative_rows.length
|
|
355
385
|
? table(NEGATIVE_COLUMNS, model.negative_rows.map((r) => [
|
|
356
|
-
r
|
|
386
|
+
negativeMarkCell(r), r.variant || r.cells.mark, r.drop_reason,
|
|
357
387
|
[`URI ${r.cells.uri}`, r.screen_verdict ? `screen_verdict=${r.screen_verdict}` : "",
|
|
358
388
|
r.cells.classes ? `class=${r.cells.classes}` : "", r.cells.status ? `status=${r.cells.status}` : ""]
|
|
359
389
|
.filter(Boolean).join("; "),
|
package/driver/repairs.mjs
CHANGED
|
@@ -555,7 +555,7 @@ export function countRecoveryLanes(history, { total = 0 } = {}) {
|
|
|
555
555
|
|
|
556
556
|
// ── CAP PARKS: A PROVIDER SAYING "NOT YET" IS NOT A STAGE FAILING (tracker issue 103) ──────────────
|
|
557
557
|
//
|
|
558
|
-
// Owner, watching
|
|
558
|
+
// Owner, watching a run spend 4 of its 6 recovery parks against one subscription cap:
|
|
559
559
|
//
|
|
560
560
|
// "surely it can work out when the cap expires and try after that time and not just keep trying
|
|
561
561
|
// and then die."
|
|
@@ -95,6 +95,13 @@ export const RESULT_NOUN_FIELDS = Object.freeze([
|
|
|
95
95
|
{ file: "driver/skills-store-provenance.mjs", noun: "outcome", sites: 6, atWriteSite: 0, verdict: "out-of-scope" },
|
|
96
96
|
{ file: "driver/status-snapshot.mjs", noun: "outcome", sites: 1, atWriteSite: 0, verdict: "out-of-scope" },
|
|
97
97
|
{ file: "mcp-server/lib/events.mjs", noun: "recovered", sites: 1, atWriteSite: 0, verdict: "out-of-scope" },
|
|
98
|
+
// Read at the writing site, as this table requires. All three sites are in `knockoutSearches`, a
|
|
99
|
+
// READ-ONLY projection that composes the rows `list_searches` answers with: it opens no artifact and
|
|
100
|
+
// writes nothing, so nothing here can reach `_driver/*.json` or `run.jsonl`. Each value is a literal
|
|
101
|
+
// chosen from the run's own stored record — "found" or "no-hit" from whether the mark has findings,
|
|
102
|
+
// and "recorded" for a register term whose stored row says it did NOT answer. None is a call's return
|
|
103
|
+
// read back as a verdict, which is the defect this table exists to find.
|
|
104
|
+
{ file: "mcp-server/lib/knockout.mjs", noun: "outcome", sites: 3, atWriteSite: 0, verdict: "out-of-scope" },
|
|
98
105
|
{ file: "mcp-server/lib/ops.mjs", noun: "settled", sites: 1, atWriteSite: 0, verdict: "out-of-scope" },
|
|
99
106
|
{ file: "mcp-server/server.mjs", noun: "outcome", sites: 1, atWriteSite: 0, verdict: "out-of-scope" },
|
|
100
107
|
{ file: "scripts/backfill-started-at.mjs", noun: "outcome", sites: 5, atWriteSite: 0, verdict: "out-of-scope" },
|
|
@@ -162,9 +162,14 @@ your job. These two fields are how what you concluded reaches the reader instead
|
|
|
162
162
|
every id against that store and refuses one it does not hold — so the report can state a filing's
|
|
163
163
|
source as a fact rather than as a word you typed about your own sourcing.
|
|
164
164
|
|
|
165
|
-
- **`registerReads`** — on the MARK. Rows of `{recordId, read}` for a filing you weighed that did
|
|
166
|
-
become a `findings[]` record. `read` is what you concluded about *that* filing: whether it bears
|
|
167
|
-
rating, and why. It prints on that filing's card.
|
|
165
|
+
- **`registerReads`** — on the MARK. Rows of `{recordId, read, band?}` for a filing you weighed that did
|
|
166
|
+
**not** become a `findings[]` record. `read` is what you concluded about *that* filing: whether it bears
|
|
167
|
+
on the rating, and why. It prints on that filing's card. `band` is **optional** and is how you rate that
|
|
168
|
+
filing on its own — in the framework's band words, the same vocabulary as the mark's rating. Send it
|
|
169
|
+
whenever you formed a view on the filing itself: a registered right is often the one hard legal fact on
|
|
170
|
+
the page, and with no band its card is the only one carrying no rating while softer uses around it carry
|
|
171
|
+
one, which reads as though the registration mattered least. Omit it and the read still prints, claiming
|
|
172
|
+
no rating.
|
|
168
173
|
- **`weighedFilings`** — on a FINDING. The `recordId`s whose evidence your reasoning for that conflict
|
|
169
174
|
actually used. The report derives the finding's source labelling from this, so list one only if you
|
|
170
175
|
reasoned from it.
|
|
@@ -205,7 +210,8 @@ line, which describes the card and claims nothing about the rating. So:
|
|
|
205
210
|
"classesDriving": [8], "bullets": ["..."], "purpleNotes": ["..."],
|
|
206
211
|
"registerEstimate": "...", "parodyNote": null, "crowdedField": false,
|
|
207
212
|
"registerReads": [ { "recordId": "<verbatim from the filings you were given>",
|
|
208
|
-
"read": "<what you concluded about THAT filing>"
|
|
213
|
+
"read": "<what you concluded about THAT filing>",
|
|
214
|
+
"band": "<OPTIONAL — your rating of THAT filing, in the ladder's words>" } ],
|
|
209
215
|
"basis": "<ONE sentence: why this band, for this name, in these classes>",
|
|
210
216
|
"factors": ["<2-4 load-bearing observations, one line each>"],
|
|
211
217
|
"counterFactors": ["<1-3: what holds it at this band rather than the next>"],
|
|
@@ -301,7 +301,7 @@ export const KO_STAGES = {
|
|
|
301
301
|
// a finding with no register evidence. Omitting either field is always safe: the card keeps its
|
|
302
302
|
// neutral line, which describes the card and is true.
|
|
303
303
|
existsSync(K.registerRecords)
|
|
304
|
-
? `WHEN YOU WEIGH ONE OF THOSE FILINGS, SAY SO BY ITS OWN ID — copy "recordId" verbatim from the file above. "registerReads" on the MARK: rows of {recordId, read} for a filing you weighed that did NOT become a findings[] record; "read" is what you concluded about THAT filing — whether it bears on the rating and why — and it prints on that filing's card in the reader's own report. "weighedFilings" on a FINDING: the recordIds whose evidence your reasoning for that conflict actually used, because the report derives that finding's source labelling from it. Both are optional and both are joined against the filings you were given, so an id we do not hold is refused by name. A filing you did not weigh simply gets no row: do not invent a read to fill one, and never write "not weighed" as a read.`
|
|
304
|
+
? `WHEN YOU WEIGH ONE OF THOSE FILINGS, SAY SO BY ITS OWN ID — copy "recordId" verbatim from the file above. "registerReads" on the MARK: rows of {recordId, read, band?} for a filing you weighed that did NOT become a findings[] record; "read" is what you concluded about THAT filing — whether it bears on the rating and why — and it prints on that filing's card in the reader's own report. "band" is OPTIONAL and is your rating of THAT filing on its own, in the framework's band words: send it whenever you formed a view on the filing itself, because a registered right left unbanded is the only card on the page with no rating while softer uses beside it carry one, which reads as though the registration mattered least. Omit it and your read still prints, claiming no rating. "weighedFilings" on a FINDING: the recordIds whose evidence your reasoning for that conflict actually used, because the report derives that finding's source labelling from it. Both are optional and both are joined against the filings you were given, so an id we do not hold is refused by name. A filing you did not weigh simply gets no row: do not invent a read to fill one, and never write "not weighed" as a read.`
|
|
305
305
|
: "",
|
|
306
306
|
`A DEGRADED mark's row must carry degraded:true and the purple "Manual verification recommended"`,
|
|
307
307
|
`note; a mark WITH a payload must never claim degraded (the validator joins both against the disk).`,
|
package/driver/stages.mjs
CHANGED
|
@@ -3267,7 +3267,7 @@ export const STAGES = {
|
|
|
3267
3267
|
`DECLINATIONS: what you do not deliver, you decline BY NAME through the \`record_declination\` tool. A record that reached your findings surface leaves this stage as a finding in ${P.findings} or as a declination, and there is no third way out.`,
|
|
3268
3268
|
...(Array.isArray(findingsSurface) && findingsSurface.length ? [
|
|
3269
3269
|
`DECLINATIONS (MANDATORY): the register digest carried ${findingsSurface.length} record(s) onto your findings surface. Each one leaves this stage by one of two routes and there is no third: it becomes a finding in ${P.findings}, or you decline it BY NAME with a reason and a ground. A record you simply do not mention is reported as a defect of this run, named individually in its trace and shipped as an open doubt, so silence costs you more than a declination ever will.`,
|
|
3270
|
-
`Decline by calling the \`record_declination\` tool. Its schema names the fields it takes and what each is for — read them there, not here. The one thing to know before you call: you cite a record by its POSITION in the list below — there is no field for a mark name or a uri, so a record you were not handed cannot be expressed at all. The reason vocabulary is closed: ${DECLINATION_REASON_TOKENS.join(" / ")}, each an omission synthesis-rules.md already authorises, and if your ground is none of them the rules do not let you omit the record. The grounds you write are one or two lines in your OWN words on why THIS record does not earn a line — never machine-parsed, and what the reviewing lawyer reads. Send them in one batch where you can; a refused row never voids its neighbours, and the answer names what is still undecided so you can finish in this turn.`,
|
|
3270
|
+
`Decline by calling the \`record_declination\` tool. Its schema names the fields it takes and what each is for — read them there, not here. The one thing to know before you call: you cite a record by its POSITION in the list below — there is no field for a mark name or a uri, so a record you were not handed cannot be expressed at all. The reason vocabulary is closed: ${DECLINATION_REASON_TOKENS.join(" / ")}, each an omission synthesis-rules.md already authorises, and if your ground is none of them the rules do not let you omit the record. ONE OF THEM IS NOT AVAILABLE ON A LIVE RECORD IN AN INSTRUCTED CLASS: \`not-worth-the-line\` is a statement about the report's budget rather than about the mark, and a live registration inside one of the matter's own classes is never omitted for want of room — carry it, or decline it on something true of THAT MARK. The call is refused if you try, so it costs you a turn. The grounds you write are one or two lines in your OWN words on why THIS record does not earn a line — never machine-parsed, and what the reviewing lawyer reads. Send them in one batch where you can; a refused row never voids its neighbours, and the answer names what is still undecided so you can finish in this turn.`,
|
|
3271
3271
|
`A REFUSAL FROM THAT TOOL IS ABOUT BOOKKEEPING, NEVER ABOUT YOUR LEGAL JUDGMENT. There is exactly ONE case: declining a mark IDENTICAL to the applied-for mark, live, in one of the matter's own filed classes, on a discretionary ground — because synthesis-rules.md orders that an on-point identical mark in the relevant class is never dropped, "regardless of filer profile", so such a declination contradicts the instruction you are already following. Nothing else is refused. If that record really is the applicant's own, or already delivered under another record, say exactly that with own-right or duplicate-of-delivered and it is accepted. Whether goods are related, whether a name is off-field, whether a conflict is worth the line — those are your calls and the tool does not have an opinion about any of them.`,
|
|
3272
3272
|
`The records on your findings surface, by position:`,
|
|
3273
3273
|
...findingsSurface.map((r, i) => ` ${i}. ${r.mark ?? "(unnamed)"}${r.owner ? ` — ${r.owner}` : ""}${r.tier ? ` [${r.tier}]` : ""}${r.uri ? ` ${r.uri}` : ""}`),
|
package/driver/suite-census.json
CHANGED
|
@@ -189,6 +189,12 @@
|
|
|
189
189
|
"skips": 1,
|
|
190
190
|
"todos": 0
|
|
191
191
|
},
|
|
192
|
+
"a-door-that-is-not-there-is-not-a-door-that-agrees.test.mjs": {
|
|
193
|
+
"tests": 19,
|
|
194
|
+
"asserts": 69,
|
|
195
|
+
"skips": 0,
|
|
196
|
+
"todos": 0
|
|
197
|
+
},
|
|
192
198
|
"a-doubt-cannot-answer-itself.test.mjs": {
|
|
193
199
|
"tests": 17,
|
|
194
200
|
"asserts": 40,
|
|
@@ -201,6 +207,18 @@
|
|
|
201
207
|
"skips": 0,
|
|
202
208
|
"todos": 0
|
|
203
209
|
},
|
|
210
|
+
"a-drop-with-a-stated-reason-is-checked.test.mjs": {
|
|
211
|
+
"tests": 13,
|
|
212
|
+
"asserts": 32,
|
|
213
|
+
"skips": 0,
|
|
214
|
+
"todos": 0
|
|
215
|
+
},
|
|
216
|
+
"a-dynamic-import-of-a-path-is-a-file-url.test.mjs": {
|
|
217
|
+
"tests": 3,
|
|
218
|
+
"asserts": 6,
|
|
219
|
+
"skips": 1,
|
|
220
|
+
"todos": 0
|
|
221
|
+
},
|
|
204
222
|
"a-fact-about-a-named-party.test.mjs": {
|
|
205
223
|
"tests": 30,
|
|
206
224
|
"asserts": 80,
|
|
@@ -286,8 +304,8 @@
|
|
|
286
304
|
"todos": 0
|
|
287
305
|
},
|
|
288
306
|
"a-memo-over-a-delivered-report-leaves-it-alone.test.mjs": {
|
|
289
|
-
"tests":
|
|
290
|
-
"asserts":
|
|
307
|
+
"tests": 13,
|
|
308
|
+
"asserts": 52,
|
|
291
309
|
"skips": 0,
|
|
292
310
|
"todos": 0
|
|
293
311
|
},
|
|
@@ -375,9 +393,15 @@
|
|
|
375
393
|
"skips": 0,
|
|
376
394
|
"todos": 0
|
|
377
395
|
},
|
|
378
|
-
"a-
|
|
396
|
+
"a-queued-what-if-is-answered-before-its-run-archives.test.mjs": {
|
|
379
397
|
"tests": 3,
|
|
380
|
-
"asserts":
|
|
398
|
+
"asserts": 12,
|
|
399
|
+
"skips": 0,
|
|
400
|
+
"todos": 0
|
|
401
|
+
},
|
|
402
|
+
"a-real-install-starts-with-an-empty-archive.test.mjs": {
|
|
403
|
+
"tests": 6,
|
|
404
|
+
"asserts": 16,
|
|
381
405
|
"skips": 0,
|
|
382
406
|
"todos": 0
|
|
383
407
|
},
|
|
@@ -483,6 +507,12 @@
|
|
|
483
507
|
"skips": 0,
|
|
484
508
|
"todos": 0
|
|
485
509
|
},
|
|
510
|
+
"a-screenshot-of-an-error-page-is-not-a-report.test.mjs": {
|
|
511
|
+
"tests": 21,
|
|
512
|
+
"asserts": 55,
|
|
513
|
+
"skips": 4,
|
|
514
|
+
"todos": 0
|
|
515
|
+
},
|
|
486
516
|
"a-secret-file-is-compared-not-printed.test.mjs": {
|
|
487
517
|
"tests": 5,
|
|
488
518
|
"asserts": 16,
|
|
@@ -501,6 +531,12 @@
|
|
|
501
531
|
"skips": 0,
|
|
502
532
|
"todos": 0
|
|
503
533
|
},
|
|
534
|
+
"a-shared-proprietor-is-not-a-bucket-collision.test.mjs": {
|
|
535
|
+
"tests": 6,
|
|
536
|
+
"asserts": 14,
|
|
537
|
+
"skips": 0,
|
|
538
|
+
"todos": 0
|
|
539
|
+
},
|
|
504
540
|
"a-signal-immune-fixture-is-reaped-by-its-owner.test.mjs": {
|
|
505
541
|
"tests": 10,
|
|
506
542
|
"asserts": 20,
|
|
@@ -543,6 +579,12 @@
|
|
|
543
579
|
"skips": 0,
|
|
544
580
|
"todos": 0
|
|
545
581
|
},
|
|
582
|
+
"a-unit-is-attributed-by-what-it-runs-not-only-by-what-it-declares.test.mjs": {
|
|
583
|
+
"tests": 10,
|
|
584
|
+
"asserts": 27,
|
|
585
|
+
"skips": 0,
|
|
586
|
+
"todos": 0
|
|
587
|
+
},
|
|
546
588
|
"a-verb-is-a-module-until-it-is-the-command.test.mjs": {
|
|
547
589
|
"tests": 3,
|
|
548
590
|
"asserts": 8,
|
|
@@ -676,8 +718,8 @@
|
|
|
676
718
|
"todos": 0
|
|
677
719
|
},
|
|
678
720
|
"an-experiment-receipt-names-its-engine.test.mjs": {
|
|
679
|
-
"tests":
|
|
680
|
-
"asserts":
|
|
721
|
+
"tests": 8,
|
|
722
|
+
"asserts": 27,
|
|
681
723
|
"skips": 0,
|
|
682
724
|
"todos": 0
|
|
683
725
|
},
|
|
@@ -687,12 +729,24 @@
|
|
|
687
729
|
"skips": 1,
|
|
688
730
|
"todos": 0
|
|
689
731
|
},
|
|
732
|
+
"an-observed-model-id-says-whether-it-is-pinned.test.mjs": {
|
|
733
|
+
"tests": 4,
|
|
734
|
+
"asserts": 10,
|
|
735
|
+
"skips": 0,
|
|
736
|
+
"todos": 0
|
|
737
|
+
},
|
|
690
738
|
"an-ordered-field-is-a-field-the-tool-can-express.test.mjs": {
|
|
691
739
|
"tests": 1,
|
|
692
740
|
"asserts": 6,
|
|
693
741
|
"skips": 0,
|
|
694
742
|
"todos": 0
|
|
695
743
|
},
|
|
744
|
+
"an-unread-probe-is-not-a-failed-measurement.test.mjs": {
|
|
745
|
+
"tests": 5,
|
|
746
|
+
"asserts": 11,
|
|
747
|
+
"skips": 0,
|
|
748
|
+
"todos": 0
|
|
749
|
+
},
|
|
696
750
|
"anchor-and-script-aware-quote.test.mjs": {
|
|
697
751
|
"tests": 12,
|
|
698
752
|
"asserts": 40,
|
|
@@ -724,8 +778,8 @@
|
|
|
724
778
|
"todos": 0
|
|
725
779
|
},
|
|
726
780
|
"ask-ledger.test.mjs": {
|
|
727
|
-
"tests":
|
|
728
|
-
"asserts":
|
|
781
|
+
"tests": 26,
|
|
782
|
+
"asserts": 119,
|
|
729
783
|
"skips": 0,
|
|
730
784
|
"todos": 0
|
|
731
785
|
},
|
|
@@ -1324,8 +1378,8 @@
|
|
|
1324
1378
|
"todos": 0
|
|
1325
1379
|
},
|
|
1326
1380
|
"declination-is-stated-not-inferred.test.mjs": {
|
|
1327
|
-
"tests":
|
|
1328
|
-
"asserts":
|
|
1381
|
+
"tests": 24,
|
|
1382
|
+
"asserts": 69,
|
|
1329
1383
|
"skips": 0,
|
|
1330
1384
|
"todos": 0
|
|
1331
1385
|
},
|
|
@@ -1373,7 +1427,7 @@
|
|
|
1373
1427
|
},
|
|
1374
1428
|
"deployment-hostnames.test.mjs": {
|
|
1375
1429
|
"tests": 13,
|
|
1376
|
-
"asserts":
|
|
1430
|
+
"asserts": 36,
|
|
1377
1431
|
"skips": 0,
|
|
1378
1432
|
"todos": 0
|
|
1379
1433
|
},
|
|
@@ -1882,8 +1936,8 @@
|
|
|
1882
1936
|
"todos": 0
|
|
1883
1937
|
},
|
|
1884
1938
|
"example-replay.test.mjs": {
|
|
1885
|
-
"tests":
|
|
1886
|
-
"asserts":
|
|
1939
|
+
"tests": 8,
|
|
1940
|
+
"asserts": 31,
|
|
1887
1941
|
"skips": 0,
|
|
1888
1942
|
"todos": 0
|
|
1889
1943
|
},
|
|
@@ -2433,6 +2487,12 @@
|
|
|
2433
2487
|
"skips": 2,
|
|
2434
2488
|
"todos": 0
|
|
2435
2489
|
},
|
|
2490
|
+
"no-run-codenames-in-tracked-files.test.mjs": {
|
|
2491
|
+
"tests": 6,
|
|
2492
|
+
"asserts": 13,
|
|
2493
|
+
"skips": 4,
|
|
2494
|
+
"todos": 0
|
|
2495
|
+
},
|
|
2436
2496
|
"no-test-writes-inside-the-checkout.test.mjs": {
|
|
2437
2497
|
"tests": 16,
|
|
2438
2498
|
"asserts": 35,
|
|
@@ -2826,7 +2886,7 @@
|
|
|
2826
2886
|
"production-dependencies.test.mjs": {
|
|
2827
2887
|
"tests": 4,
|
|
2828
2888
|
"asserts": 7,
|
|
2829
|
-
"skips":
|
|
2889
|
+
"skips": 3,
|
|
2830
2890
|
"todos": 0
|
|
2831
2891
|
},
|
|
2832
2892
|
"products.test.mjs": {
|
|
@@ -2964,7 +3024,7 @@
|
|
|
2964
3024
|
"reading-the-demo-does-not-edit-the-repository.test.mjs": {
|
|
2965
3025
|
"tests": 2,
|
|
2966
3026
|
"asserts": 8,
|
|
2967
|
-
"skips":
|
|
3027
|
+
"skips": 1,
|
|
2968
3028
|
"todos": 0
|
|
2969
3029
|
},
|
|
2970
3030
|
"reason-cut-is-visible.test.mjs": {
|
|
@@ -3076,8 +3136,8 @@
|
|
|
3076
3136
|
"todos": 0
|
|
3077
3137
|
},
|
|
3078
3138
|
"reference-score-owner-identity.test.mjs": {
|
|
3079
|
-
"tests":
|
|
3080
|
-
"asserts":
|
|
3139
|
+
"tests": 10,
|
|
3140
|
+
"asserts": 34,
|
|
3081
3141
|
"skips": 0,
|
|
3082
3142
|
"todos": 0
|
|
3083
3143
|
},
|
|
@@ -3137,7 +3197,7 @@
|
|
|
3137
3197
|
},
|
|
3138
3198
|
"register-count.test.mjs": {
|
|
3139
3199
|
"tests": 20,
|
|
3140
|
-
"asserts":
|
|
3200
|
+
"asserts": 148,
|
|
3141
3201
|
"skips": 0,
|
|
3142
3202
|
"todos": 0
|
|
3143
3203
|
},
|
|
@@ -3160,8 +3220,8 @@
|
|
|
3160
3220
|
"todos": 0
|
|
3161
3221
|
},
|
|
3162
3222
|
"register-digest-record.test.mjs": {
|
|
3163
|
-
"tests":
|
|
3164
|
-
"asserts":
|
|
3223
|
+
"tests": 32,
|
|
3224
|
+
"asserts": 114,
|
|
3165
3225
|
"skips": 0,
|
|
3166
3226
|
"todos": 0
|
|
3167
3227
|
},
|
|
@@ -3238,8 +3298,8 @@
|
|
|
3238
3298
|
"todos": 0
|
|
3239
3299
|
},
|
|
3240
3300
|
"release-pipeline.test.mjs": {
|
|
3241
|
-
"tests":
|
|
3242
|
-
"asserts":
|
|
3301
|
+
"tests": 87,
|
|
3302
|
+
"asserts": 334,
|
|
3243
3303
|
"skips": 0,
|
|
3244
3304
|
"todos": 0
|
|
3245
3305
|
},
|
|
@@ -3856,8 +3916,8 @@
|
|
|
3856
3916
|
"todos": 0
|
|
3857
3917
|
},
|
|
3858
3918
|
"start-background-units.test.mjs": {
|
|
3859
|
-
"tests":
|
|
3860
|
-
"asserts":
|
|
3919
|
+
"tests": 9,
|
|
3920
|
+
"asserts": 26,
|
|
3861
3921
|
"skips": 0,
|
|
3862
3922
|
"todos": 0
|
|
3863
3923
|
},
|
|
@@ -4252,8 +4312,8 @@
|
|
|
4252
4312
|
"todos": 0
|
|
4253
4313
|
},
|
|
4254
4314
|
"the-narrative-is-checked-against-its-depth-rules.test.mjs": {
|
|
4255
|
-
"tests":
|
|
4256
|
-
"asserts":
|
|
4315
|
+
"tests": 19,
|
|
4316
|
+
"asserts": 70,
|
|
4257
4317
|
"skips": 0,
|
|
4258
4318
|
"todos": 0
|
|
4259
4319
|
},
|
|
@@ -4335,6 +4395,12 @@
|
|
|
4335
4395
|
"skips": 0,
|
|
4336
4396
|
"todos": 0
|
|
4337
4397
|
},
|
|
4398
|
+
"the-reference-strip-left-sentences-unfinished.test.mjs": {
|
|
4399
|
+
"tests": 6,
|
|
4400
|
+
"asserts": 13,
|
|
4401
|
+
"skips": 3,
|
|
4402
|
+
"todos": 0
|
|
4403
|
+
},
|
|
4338
4404
|
"the-repair-order-and-the-tool-schema-agree.test.mjs": {
|
|
4339
4405
|
"tests": 6,
|
|
4340
4406
|
"asserts": 27,
|
|
@@ -4347,6 +4413,12 @@
|
|
|
4347
4413
|
"skips": 1,
|
|
4348
4414
|
"todos": 0
|
|
4349
4415
|
},
|
|
4416
|
+
"the-report-carries-what-the-assessment-wrote.test.mjs": {
|
|
4417
|
+
"tests": 10,
|
|
4418
|
+
"asserts": 41,
|
|
4419
|
+
"skips": 0,
|
|
4420
|
+
"todos": 0
|
|
4421
|
+
},
|
|
4350
4422
|
"the-report-shell-keeps-what-a-repair-does-not-resend.test.mjs": {
|
|
4351
4423
|
"tests": 6,
|
|
4352
4424
|
"asserts": 28,
|
|
@@ -4690,8 +4762,8 @@
|
|
|
4690
4762
|
"todos": 0
|
|
4691
4763
|
},
|
|
4692
4764
|
"verify-knockout.test.mjs": {
|
|
4693
|
-
"tests":
|
|
4694
|
-
"asserts":
|
|
4765
|
+
"tests": 2,
|
|
4766
|
+
"asserts": 11,
|
|
4695
4767
|
"skips": 0,
|
|
4696
4768
|
"todos": 0
|
|
4697
4769
|
},
|
|
@@ -4922,6 +4994,12 @@
|
|
|
4922
4994
|
"skips": 0,
|
|
4923
4995
|
"todos": 0
|
|
4924
4996
|
},
|
|
4997
|
+
"knockout-audit-tools.test.mjs": {
|
|
4998
|
+
"tests": 16,
|
|
4999
|
+
"asserts": 73,
|
|
5000
|
+
"skips": 0,
|
|
5001
|
+
"todos": 0
|
|
5002
|
+
},
|
|
4925
5003
|
"lexsearch.test.mjs": {
|
|
4926
5004
|
"tests": 5,
|
|
4927
5005
|
"asserts": 11,
|
|
@@ -5043,8 +5121,8 @@
|
|
|
5043
5121
|
"todos": 0
|
|
5044
5122
|
},
|
|
5045
5123
|
"whatif.test.mjs": {
|
|
5046
|
-
"tests":
|
|
5047
|
-
"asserts":
|
|
5124
|
+
"tests": 10,
|
|
5125
|
+
"asserts": 32,
|
|
5048
5126
|
"skips": 0,
|
|
5049
5127
|
"todos": 0
|
|
5050
5128
|
}
|
|
@@ -641,6 +641,53 @@ export function unitInventoryVerdict({
|
|
|
641
641
|
* @param {string|null} raw the value `systemctl show -p WorkingDirectory` printed
|
|
642
642
|
* @returns {{path: string|null, prefixes: string, why: string|null}}
|
|
643
643
|
*/
|
|
644
|
+
/**
|
|
645
|
+
* Which checkout is a unit actually serving — and which piece of evidence said so.
|
|
646
|
+
*
|
|
647
|
+
* A unit can be attributed two ways and they are not equally good.
|
|
648
|
+
*
|
|
649
|
+
* `WorkingDirectory` is a DECLARATION. It is what the unit file asks for, and it is frequently not a
|
|
650
|
+
* checkout at all: a unit written as `WorkingDirectory=~` reports `!/home/<user>`, which resolves to a
|
|
651
|
+
* home directory that is not a git tree, and the unit then drops out of the comparison entirely. That is
|
|
652
|
+
* not a rare shape — it is what a unit looks like when its author put the checkout in `ExecStart=`
|
|
653
|
+
* instead, which is the normal thing to do, because systemd cannot expand a variable in
|
|
654
|
+
* `WorkingDirectory=` and can in `ExecStart=`.
|
|
655
|
+
*
|
|
656
|
+
* The RUNNING COMMAND LINE is an OBSERVATION: the absolute path the live process was actually started
|
|
657
|
+
* with. So it wins, and that order is the point rather than a preference. The incident this comparison
|
|
658
|
+
* exists for is a deployment that served its portal from one clone while running its runner from
|
|
659
|
+
* another — and in that state the declaration and the process disagree, with the process telling the
|
|
660
|
+
* truth. Preferring the declaration would take the wrong side of exactly the case the check is for.
|
|
661
|
+
*
|
|
662
|
+
* WHEN BOTH RESOLVE AND DISAGREE, that IS the finding, and it is returned rather than quietly resolved:
|
|
663
|
+
* a unit whose declared tree and running tree differ has been repointed since it started, and it is
|
|
664
|
+
* serving the older one until something restarts it.
|
|
665
|
+
*
|
|
666
|
+
* PURE — the caller does the reading (systemd, the unit file, the process table) and this decides.
|
|
667
|
+
*
|
|
668
|
+
* @param {object} a
|
|
669
|
+
* @param {string|null} a.declaredTree a git tree resolved from `WorkingDirectory`, or null
|
|
670
|
+
* @param {string|null} a.runningTree a git tree resolved from the live process's argv, or null
|
|
671
|
+
* @param {string|null} a.declaredWhy why the declaration gave nothing, if it gave nothing
|
|
672
|
+
* @param {string|null} a.runningWhy why the process gave nothing, if it gave nothing
|
|
673
|
+
* @returns {{clone: string|null, source: string|null, disagreement: string|null, why: string|null}}
|
|
674
|
+
*/
|
|
675
|
+
export function unitClone({ declaredTree = null, runningTree = null, declaredWhy = null, runningWhy = null } = {}) {
|
|
676
|
+
const norm = (p) => String(p ?? "").replace(/\/+$/, "") || null;
|
|
677
|
+
const declared = norm(declaredTree);
|
|
678
|
+
const running = norm(runningTree);
|
|
679
|
+
const disagreement = declared && running && declared !== running
|
|
680
|
+
? `the unit declares ${declared} and its running process was started from ${running} — it is serving the second until something restarts it`
|
|
681
|
+
: null;
|
|
682
|
+
if (running) return { clone: running, source: "the running command line", disagreement, why: null };
|
|
683
|
+
if (declared) return { clone: declared, source: "WorkingDirectory", disagreement, why: null };
|
|
684
|
+
// BOTH REASONS, never the first one that came to hand. A caller that reports only "no WorkingDirectory"
|
|
685
|
+
// sends the next reader to fix a unit file when the process table was the half that could not be read.
|
|
686
|
+
const parts = [declaredWhy, runningWhy].filter(Boolean);
|
|
687
|
+
return { clone: null, source: null, disagreement: null,
|
|
688
|
+
why: parts.length ? parts.join("; and ") : "neither the unit's WorkingDirectory nor its running command line named a checkout" };
|
|
689
|
+
}
|
|
690
|
+
|
|
644
691
|
export function unitWorkingDirectory(raw) {
|
|
645
692
|
const s = String(raw ?? "").trim();
|
|
646
693
|
if (!s) return { path: null, prefixes: "", why: "the unit reported no WorkingDirectory" };
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
//
|
|
15
15
|
// Anything that is neither `active` nor `inactive` is a fault. `prelim-driver` is a `Type=oneshot` fired
|
|
16
16
|
// by a 90-second timer, so EVERY drain passes through `activating` — and `activating` landed in that
|
|
17
|
-
// list. The filter tolerated the oneshot at rest and failed it for working.
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// list. The filter tolerated the oneshot at rest and failed it for working. The hourly test-instance
|
|
18
|
+
// deploy gates on this script's exit code, so a deploy that happened to land inside a drain window
|
|
19
|
+
// reported the instance unhealthy and exited 1 on a deployment that was fine.
|
|
20
20
|
//
|
|
21
21
|
// The bug is not the enumeration — was right to give this three outcomes instead of two. The bug is
|
|
22
22
|
// INVERSE ENUMERATION over an incomplete vocabulary: listing the two states you have seen and calling
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
// The ruling on says "starting and stopping are both tolerated for a one-shot". That is descriptive
|
|
40
40
|
// of the case that was observed, not a restriction: the issue's own Scope section carries no unit-type
|
|
41
41
|
// qualifier, the arm has never been unit-type-aware, and it already tolerates `inactive` for everything.
|
|
42
|
-
// It is also the wrong place to draw the line — deploy
|
|
42
|
+
// It is also the wrong place to draw the line — the hourly deploy RESTARTS the long-running services
|
|
43
43
|
// immediately before running this check, so `trademark-portal=activating` is the same race with a
|
|
44
44
|
// different unit, and a oneshot-only fix would leave it standing. The unit's Type is still carried into
|
|
45
45
|
// the message so a reader can tell a oneshot mid-fire from a service mid-restart.
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
// This is not caution for its own sake. An earlier draft of this fix also widened the count to treat
|
|
64
64
|
// transitional units as up, on the reasoning that a oneshot mid-fire is "not nothing". The consequence:
|
|
65
65
|
// {5 services activating, 3 inactive} returned `pass` with a message that began "0 active" — a green
|
|
66
|
-
// tick on the deploy's final gate having confirmed that ZERO services were running. deploy
|
|
66
|
+
// tick on the deploy's final gate having confirmed that ZERO services were running. The hourly deploy
|
|
67
67
|
// restarts the long-running services immediately before running this check, so that is the ordinary
|
|
68
|
-
// shape of the
|
|
68
|
+
// shape of the instance, not a corner. Widening a count to make a message read better is how a guard dies.
|
|
69
69
|
|
|
70
70
|
/** The six documented ActiveState values of systemd 255, mapped to what the check must do about them. */
|
|
71
71
|
export const ACTIVE_STATE_MEANING = Object.freeze({
|
|
@@ -134,8 +134,8 @@ export function unitsActiveVerdict({ units, probe }) {
|
|
|
134
134
|
// CHANGES NOTHING HERE. The count below is origin/main's, predicate and message unchanged:
|
|
135
135
|
// `active`, literally, and nothing else. An earlier draft of this fix widened it to count
|
|
136
136
|
// transitional units as up, which defeated this guard outright — {5 activating, 3 inactive} then
|
|
137
|
-
// returned `pass` with a message that began "0 active", on the deploy's FINAL gate, on
|
|
138
|
-
// deploy
|
|
137
|
+
// returned `pass` with a message that began "0 active", on the deploy's FINAL gate, on an instance
|
|
138
|
+
// where the hourly deploy has just restarted every long-running service. Scope names one thing, the
|
|
139
139
|
// state vocabulary of the FAULT branch above. Transitional units are named in the messages here so
|
|
140
140
|
// they are never swallowed; they are counted as up nowhere.
|
|
141
141
|
const activeCount = (units ?? []).filter((u) => u.active === "active").length;
|
|
@@ -442,7 +442,7 @@ export const validators = {
|
|
|
442
442
|
// — the register reads and the per-finding weighed lists, both joined to the store.
|
|
443
443
|
if (m.registerReads !== undefined && m.registerReads !== null) {
|
|
444
444
|
if (!Array.isArray(m.registerReads))
|
|
445
|
-
return { ok: false, reason: `mark "${m.name}": registerReads must be an ARRAY of { recordId, read } rows, or omitted entirely` };
|
|
445
|
+
return { ok: false, reason: `mark "${m.name}": registerReads must be an ARRAY of { recordId, read, band? } rows, or omitted entirely` };
|
|
446
446
|
const held = registerRecordIdsFor(runDir, m.name);
|
|
447
447
|
for (const row of m.registerReads) {
|
|
448
448
|
const id = String(row?.recordId ?? "").trim();
|
|
@@ -451,6 +451,14 @@ export const validators = {
|
|
|
451
451
|
if (!read) return { ok: false, reason: `mark "${m.name}": registerReads row "${id}" has an empty read. Omit the row rather than sending an empty one: a filing with no read keeps the card's neutral line, which is true` };
|
|
452
452
|
if (!held.has(id))
|
|
453
453
|
return { ok: false, reason: `mark "${m.name}": registerReads cites "${id}", which is not a record this run holds for that mark. The id must be copied from the filings you were handed — the driver joins it against the store and never takes your word for it` };
|
|
454
|
+
// The band is OPTIONAL and, when present, is the same closed vocabulary the mark's own rating
|
|
455
|
+
// uses — the card prints it as this filing's rating, so a word outside the frozen ladder would
|
|
456
|
+
// put a chip on the page in a dialect the framework does not define. Absence stays free: a read
|
|
457
|
+
// with no band prints the read and no chip, which is what every run before this one produced.
|
|
458
|
+
if (row?.band != null && String(row.band).trim()) {
|
|
459
|
+
if (ladder.length && bandIdx(ladder, row.band) < 0)
|
|
460
|
+
return { ok: false, reason: `knockout_band_unknown:${m.name}: registerReads row "${id}" carries band "${row.band}", which is not in the frozen ladder (${ladder.join(" / ")}) — rate the filing in the framework's own vocabulary, or omit the band and let the read stand alone` };
|
|
461
|
+
}
|
|
454
462
|
}
|
|
455
463
|
}
|
|
456
464
|
for (const f of (Array.isArray(m.findings) ? m.findings : [])) {
|
package/driver/verify.mjs
CHANGED
|
@@ -170,8 +170,8 @@ function needs(content, markers, label, names = []) {
|
|
|
170
170
|
//
|
|
171
171
|
// THE CLASS. Every gate below keys on prose the MODEL composes, so each is one phrasing drift away
|
|
172
172
|
// from killing a run that produced the section perfectly. Measured across three runs and two engines:
|
|
173
|
-
//
|
|
174
|
-
//
|
|
173
|
+
// one run on codex wrote "## Negative-results matrix" and was rejected twice, killing the run; another
|
|
174
|
+
// on anthropic wrote "## Negative results (per-cell detail)" — different spelling AND
|
|
175
175
|
// different trailing words, same skill. PR 336 widened the regex to `[\s-]`, which fixed those two
|
|
176
176
|
// instances and left the class exactly where it was: the gate still asks the model to guess a spelling.
|
|
177
177
|
//
|