clearotron 0.2.3 → 0.2.4
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 +36 -37
- package/CONTRIBUTING.md +8 -4
- package/INSTALL.md +24 -4
- package/README.md +7 -6
- package/bin/example.mjs +6 -5
- package/bin/onboard.mjs +175 -9
- package/bin/start.mjs +66 -4
- package/build-info.json +2 -2
- package/demo/README.md +1 -1
- package/docs/GLOSSARY.md +85 -0
- package/docs/README.md +1 -0
- package/docs/architecture/01-product-overview.md +21 -9
- package/docs/architecture/05-config-governance.md +5 -0
- package/docs/decisions/0006-what-the-public-repository-carries.md +30 -5
- package/driver/CHANGELOG.md +34 -0
- package/driver/README.md +25 -6
- package/driver/connotation-search.mjs +1 -1
- package/driver/contract-audit.mjs +5 -1
- package/driver/contract-e3-baseline.json +11 -11
- package/driver/doubt-selection.mjs +1 -1
- package/driver/drainer-identity.mjs +1 -1
- package/driver/effort-model.mjs +2 -2
- package/driver/engine/probe.mjs +45 -5
- package/driver/gateway.mjs +2 -2
- package/driver/outbox-backoff.mjs +1 -1
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +206 -101
- package/driver/plain-register.mjs +16 -2
- package/driver/portal-config-view.mjs +30 -1
- package/driver/portal-local-auth.mjs +5 -1
- package/driver/portal-service.mjs +53 -2
- package/driver/predelivery-lint.mjs +54 -25
- package/driver/publish/render.mjs +109 -14
- package/driver/search-policy.mjs +1 -1
- package/driver/stage-context.mjs +13 -0
- package/driver/stages.mjs +51 -4
- package/driver/suite-census.json +97 -31
- package/driver/systemd/clearotron-worker.service +3 -3
- package/driver/tokens.mjs +1 -1
- package/driver/unit-inventory.mjs +34 -4
- package/mcp-server/CHANGELOG.md +2 -0
- package/mcp-server/package.json +1 -1
- package/package.json +4 -10
- package/portal-ui/dist/assets/{index-CEYvXdqH.js → index-KFAHMgdT.js} +73 -34
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +2 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/added-reference-check.mjs +45 -89
- package/scripts/e2e.mjs +2 -2
- package/scripts/generated-files-are-current.mjs +125 -0
- package/scripts/mint-names-in-force.mjs +4 -2
- package/scripts/mint-public-residue.mjs +74 -0
- package/scripts/mint-reference-strip-backlog.mjs +12 -1
- package/scripts/mint-suite-census.mjs +5 -2
- package/scripts/render-check.mjs +13 -2
- package/scripts/report-frame-check.mjs +8 -1
- package/scripts/report-screenshot.mjs +6 -3
- package/scripts/revisit-render-check.mjs +6 -3
- package/scripts/score.mjs +1 -1
- package/scripts/strip-tracker-citations.mjs +9 -1
- package/scripts/test-full.mjs +226 -0
- package/shared/README.md +2 -1
- package/shared/browser-temp-root.mjs +142 -0
- package/shared/checkout-move.mjs +1 -1
- package/shared/client-door.mjs +4 -3
- package/shared/identifier-scan.mjs +1 -1
- package/shared/install-auth.mjs +35 -0
- package/shared/invocation.mjs +5 -1
- package/shared/listen.mjs +55 -3
- package/shared/names-in-force.mjs +4 -1
- package/shared/os-advice.mjs +91 -0
- package/shared/reap-on-exit.mjs +42 -0
- package/shared/reference-guard-classes.mjs +351 -0
- package/shared/suite-census.mjs +31 -3
- package/shared/withheld-paths-access.mjs +36 -18
|
@@ -10415,6 +10415,7 @@ var api = {
|
|
|
10415
10415
|
accountNames: Object.fromEntries(Object.entries(asRecord(b["accountNames"])).filter(([, v]) => typeof v === "string" && v)),
|
|
10416
10416
|
concurrentRuns: asNumber(b["concurrentRuns"]),
|
|
10417
10417
|
engineMode: b["engineMode"] === "demo" ? "demo" : b["engineMode"] === "engine-unproven" ? "engine-unproven" : null,
|
|
10418
|
+
engineProgramDisputed: b["engineProgramDisputed"] === true ? true : b["engineProgramDisputed"] === false ? false : null,
|
|
10418
10419
|
setupRoute: b["setupRoute"] === "packaged" ? "packaged" : b["setupRoute"] === "checkout" ? "checkout" : null,
|
|
10419
10420
|
brand: typeof b["brand"] === "string" ? b["brand"] : "",
|
|
10420
10421
|
stopControl: (() => {
|
|
@@ -10716,7 +10717,9 @@ var api = {
|
|
|
10716
10717
|
apiBilled: bill["apiBilled"] === true,
|
|
10717
10718
|
missing: asStrings(bill["missing"])
|
|
10718
10719
|
},
|
|
10719
|
-
binaryPresent: e["binaryPresent"] === true
|
|
10720
|
+
binaryPresent: e["binaryPresent"] === true,
|
|
10721
|
+
program: asString(e["program"]),
|
|
10722
|
+
install: asString(e["install"])
|
|
10720
10723
|
};
|
|
10721
10724
|
})() : null,
|
|
10722
10725
|
providers: Array.isArray(b["providers"]) ? asArray(b["providers"]).map((p) => {
|
|
@@ -14548,6 +14551,42 @@ function Missing({ go, reason }) {
|
|
|
14548
14551
|
});
|
|
14549
14552
|
}
|
|
14550
14553
|
//#endregion
|
|
14554
|
+
//#region src/contract/engineState.ts
|
|
14555
|
+
function engineRowFaults(engine, { programDisputed = false } = {}) {
|
|
14556
|
+
return [
|
|
14557
|
+
...engine.known ? [] : [`This build does not ship an engine called ${engine.id}.`],
|
|
14558
|
+
...engine.billing.missing.length ? [`Set to bill an API key, and ${engine.billing.missing.join(" and ")} is not set — a run is refused rather than billed to the subscription.`] : [],
|
|
14559
|
+
...engine.binaryPresent ? [] : engine.program && engine.install ? [`The engine program \`${engine.program}\` cannot be found or run on this machine. Install it with \`${engine.install}\`, then restart this service so it re-reads its PATH.`] : ["The engine program cannot be found or run on this machine."],
|
|
14560
|
+
...programDisputed && engine.binaryPresent ? ["The engine program is on this machine, but the engine could not find it when it last started — so a new search will refuse. Restart the engine service, or install the CLI where the service can see it."] : []
|
|
14561
|
+
];
|
|
14562
|
+
}
|
|
14563
|
+
function engineNotice({ programDisputed }) {
|
|
14564
|
+
if (programDisputed === true) return {
|
|
14565
|
+
state: "disputed",
|
|
14566
|
+
headline: "The engine program is on this machine, but the engine could not find it when it last started.",
|
|
14567
|
+
before: "So a new search will refuse. Restart the engine service, or install the CLI where the service can see it — installing it again where it already is will not change this.",
|
|
14568
|
+
namesSetupCommand: false,
|
|
14569
|
+
after: ""
|
|
14570
|
+
};
|
|
14571
|
+
return {
|
|
14572
|
+
state: "absent",
|
|
14573
|
+
headline: "No search engine is attached to this install.",
|
|
14574
|
+
before: "Everything else works — the example report, its audit trail and the assistant connection are live right now. To start new searches, install a reasoning CLI and sign in, then run the setup wizard again",
|
|
14575
|
+
namesSetupCommand: true,
|
|
14576
|
+
after: "Then restart the service: this reading was taken when it started, and it will not notice a new install until it starts again."
|
|
14577
|
+
};
|
|
14578
|
+
}
|
|
14579
|
+
function engineRow(engine, { programDisputed = false } = {}) {
|
|
14580
|
+
const faults = engineRowFaults(engine, { programDisputed });
|
|
14581
|
+
return {
|
|
14582
|
+
ok: faults.length === 0,
|
|
14583
|
+
name: engine.vendor ?? engine.id,
|
|
14584
|
+
mono: engine.id,
|
|
14585
|
+
state: engine.billing.apiBilled ? "API key" : "Subscription",
|
|
14586
|
+
faults
|
|
14587
|
+
};
|
|
14588
|
+
}
|
|
14589
|
+
//#endregion
|
|
14551
14590
|
//#region src/contract/compose.ts
|
|
14552
14591
|
function parseNames(raw) {
|
|
14553
14592
|
return raw.split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
|
|
@@ -14781,6 +14820,8 @@ var isKnownTerritory = (entry) => {
|
|
|
14781
14820
|
const e = String(entry).trim().toLowerCase();
|
|
14782
14821
|
return e.length > 0 && ALL_TERRITORIES$1.some((t) => t.toLowerCase() === e);
|
|
14783
14822
|
};
|
|
14823
|
+
var isTerritoryCode = (entry) => /^[A-Za-z]{2}$/.test(String(entry).trim());
|
|
14824
|
+
var isTerritoryEntry = (entry) => isKnownTerritory(entry) || isTerritoryCode(entry);
|
|
14784
14825
|
function machineryFor(d, product) {
|
|
14785
14826
|
const knockout = product?.pipeline === "knockout";
|
|
14786
14827
|
return {
|
|
@@ -16590,6 +16631,8 @@ function NewClearance({ ctx }) {
|
|
|
16590
16631
|
busy,
|
|
16591
16632
|
demoMode: ctx.me.engineMode === "demo",
|
|
16592
16633
|
setupRoute: ctx.me.setupRoute,
|
|
16634
|
+
programDisputed: ctx.me.engineProgramDisputed,
|
|
16635
|
+
onSettings: ctx.me.role === "staff" ? () => ctx.go("/portal/admin/config") : null,
|
|
16593
16636
|
saveOpen,
|
|
16594
16637
|
saveName,
|
|
16595
16638
|
saveText,
|
|
@@ -16761,7 +16804,7 @@ function NameWall({ count, allowed, first, canScreen, screenName, onScreenAll })
|
|
|
16761
16804
|
}) : null]
|
|
16762
16805
|
});
|
|
16763
16806
|
}
|
|
16764
|
-
function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready, busy, demoMode, setupRoute, saveOpen, saveName, saveText, saveNote, saveDone, editing, canSave, blockedBy, onSaveOpen, onSaveName, onSaveText, onSaveCancel, onSave, onReview, onSeeSaved }) {
|
|
16807
|
+
function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready, busy, demoMode, setupRoute, programDisputed, onSettings, saveOpen, saveName, saveText, saveNote, saveDone, editing, canSave, blockedBy, onSaveOpen, onSaveName, onSaveText, onSaveCancel, onSave, onReview, onSeeSaved }) {
|
|
16765
16808
|
return (0, import_jsx_runtime.jsxs)("div", {
|
|
16766
16809
|
className: "composer-footer",
|
|
16767
16810
|
children: [
|
|
@@ -16974,21 +17017,30 @@ function Footer({ startedFrom, tier, detail, units, cost, duration, runs, ready,
|
|
|
16974
17017
|
})
|
|
16975
17018
|
]
|
|
16976
17019
|
}) : null,
|
|
16977
|
-
demoMode ? (
|
|
16978
|
-
|
|
16979
|
-
|
|
16980
|
-
|
|
16981
|
-
|
|
16982
|
-
|
|
16983
|
-
|
|
16984
|
-
|
|
16985
|
-
|
|
16986
|
-
|
|
16987
|
-
|
|
16988
|
-
|
|
16989
|
-
|
|
16990
|
-
|
|
16991
|
-
|
|
17020
|
+
demoMode ? (() => {
|
|
17021
|
+
const n = engineNotice({ programDisputed });
|
|
17022
|
+
return (0, import_jsx_runtime.jsxs)("div", {
|
|
17023
|
+
className: "footer-demo-note",
|
|
17024
|
+
role: "status",
|
|
17025
|
+
style: {
|
|
17026
|
+
fontSize: 12.5,
|
|
17027
|
+
lineHeight: 1.45
|
|
17028
|
+
},
|
|
17029
|
+
children: [
|
|
17030
|
+
(0, import_jsx_runtime.jsx)("strong", { children: n.headline }),
|
|
17031
|
+
" ",
|
|
17032
|
+
n.before,
|
|
17033
|
+
n.namesSetupCommand ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsx)(SetupCommand, { route: setupRoute })] }) : null,
|
|
17034
|
+
n.after ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", n.after] }) : null,
|
|
17035
|
+
onSettings ? (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [" ", (0, import_jsx_runtime.jsx)("button", {
|
|
17036
|
+
type: "button",
|
|
17037
|
+
className: "link-btn",
|
|
17038
|
+
onClick: onSettings,
|
|
17039
|
+
children: "See what this install has"
|
|
17040
|
+
})] }) : null
|
|
17041
|
+
]
|
|
17042
|
+
});
|
|
17043
|
+
})() : (0, import_jsx_runtime.jsxs)("span", {
|
|
16992
17044
|
style: {
|
|
16993
17045
|
display: "inline-flex",
|
|
16994
17046
|
alignItems: "center",
|
|
@@ -18647,10 +18699,10 @@ var PROFILE_FIELDS = [
|
|
|
18647
18699
|
commaSeparated: true,
|
|
18648
18700
|
picker: "territories",
|
|
18649
18701
|
item: {
|
|
18650
|
-
ok:
|
|
18651
|
-
expected: "a territory from the picker below"
|
|
18702
|
+
ok: isTerritoryEntry,
|
|
18703
|
+
expected: "a territory from the picker below, or a two-letter code"
|
|
18652
18704
|
},
|
|
18653
|
-
hint: "One per line or comma-separated — pick from the list below. A region counts as one entry: European Union covers its member states, so there is no need to add them. Anything not in the list is kept and flagged rather than refused, because a search can name a territory this list does not carry."
|
|
18705
|
+
hint: "One per line or comma-separated — pick from the list below, or type a two-letter code like US, EU or GB; both are understood. A region counts as one entry: European Union covers its member states, so there is no need to add them. Anything not in the list is kept and flagged rather than refused, because a search can name a territory this list does not carry."
|
|
18654
18706
|
},
|
|
18655
18707
|
{
|
|
18656
18708
|
key: "platforms",
|
|
@@ -20683,20 +20735,7 @@ function Auth({ auth }) {
|
|
|
20683
20735
|
}) : null] });
|
|
20684
20736
|
}
|
|
20685
20737
|
function Engine({ engine, programDisputed = false }) {
|
|
20686
|
-
|
|
20687
|
-
const faults = [
|
|
20688
|
-
...engine.known ? [] : [`This build does not ship an engine called ${engine.id}.`],
|
|
20689
|
-
...engine.billing.missing.length ? [`Set to bill an API key, and ${engine.billing.missing.join(" and ")} is not set — a run is refused rather than billed to the subscription.`] : [],
|
|
20690
|
-
...engine.binaryPresent ? [] : ["The engine program cannot be found or run on this machine."],
|
|
20691
|
-
...programDisputed && engine.binaryPresent ? ["The engine program is on this machine, but the engine could not find it when it last started — so a new search will refuse. Restart the engine service, or install the CLI where the service can see it."] : []
|
|
20692
|
-
];
|
|
20693
|
-
return (0, import_jsx_runtime.jsx)(Row$1, {
|
|
20694
|
-
ok: faults.length === 0,
|
|
20695
|
-
name: engine.vendor ?? engine.id,
|
|
20696
|
-
mono: engine.id,
|
|
20697
|
-
state: billed,
|
|
20698
|
-
faults
|
|
20699
|
-
});
|
|
20738
|
+
return (0, import_jsx_runtime.jsx)(Row$1, { ...engineRow(engine, { programDisputed }) });
|
|
20700
20739
|
}
|
|
20701
20740
|
function Provider({ p }) {
|
|
20702
20741
|
const faults = p.remedy ? [p.remedy] : p.configured ? [] : p.provider === null ? [`No register is selected. Set ${p.missing.join(" and ")}.`] : !p.known ? [`This build does not ship a provider called ${p.provider}.`] : [`Set ${p.missing.join(" and ")}.`];
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
-->
|
|
50
50
|
<link rel="preconnect" href="https://api.fontshare.com" crossorigin />
|
|
51
51
|
<link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
|
|
52
|
-
<script type="module" crossorigin src="/portal/assets/index-
|
|
52
|
+
<script type="module" crossorigin src="/portal/assets/index-KFAHMgdT.js"></script>
|
|
53
53
|
<link rel="stylesheet" crossorigin href="/portal/assets/index-1ziUJX1E.css">
|
|
54
54
|
</head>
|
|
55
55
|
<body>
|
package/portal-ui/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "portal-ui",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.2.
|
|
5
|
+
"version": "0.2.4",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
7
7
|
"description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
|
|
8
8
|
"engines": {
|
|
@@ -2,110 +2,59 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
3
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// WHAT THIS CHANGE ADDS IS REFUSED. What is already here is not this check's business.
|
|
6
6
|
//
|
|
7
7
|
// node scripts/added-reference-check.mjs [--base <ref>]
|
|
8
8
|
//
|
|
9
9
|
// ── WHY THIS IS DIFF-SHAPED AND NOT A SWEEP ──────────────────────────────────────────────────────
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
11
|
+
// The classes below have a standing population in this tree: citations into a tracker nobody outside
|
|
12
|
+
// can open, account names, home directories, and the words this project uses for how it is built. A
|
|
13
|
+
// guard that refused them all would refuse every pull request from its first day, and a guard
|
|
14
|
+
// everybody bypasses protects nothing. So this one asks a smaller question that has a clean answer:
|
|
15
|
+
// did THIS change add another.
|
|
16
16
|
//
|
|
17
|
-
// The
|
|
18
|
-
//
|
|
17
|
+
// The standing population is not therefore accepted. It is counted, per file and per class, in
|
|
18
|
+
// `driver/test/fixtures/public-residue-backlog.json`, and the floor beside it refuses any file that
|
|
19
|
+
// grows. Between the two, the number can only fall: this check stops the inflow and the floor stops
|
|
20
|
+
// the backsliding. Neither works alone — a diff guard with no floor watches the total drift upward one
|
|
21
|
+
// repaired-and-reintroduced line at a time, and a floor with no diff guard is a number that goes stale
|
|
22
|
+
// the first time somebody adds a file.
|
|
19
23
|
//
|
|
20
24
|
// ── WHAT IT READS, AND WHAT IT DELIBERATELY DOES NOT ─────────────────────────────────────────────
|
|
21
25
|
//
|
|
22
26
|
// COMMENTS AND PROSE ONLY. A hash followed by digits is not always a reference: a three-digit one is
|
|
23
27
|
// also a CSS colour, and a composite key or a fixture string can hold anything. Reading only comment
|
|
24
|
-
// markdown keeps the check away from every context where the token means something else,
|
|
25
|
-
// lets it refuse without a table of exceptions that would rot.
|
|
28
|
+
// and markdown text keeps the check away from every context where the token means something else,
|
|
29
|
+
// which is what lets it refuse without a table of exceptions that would rot.
|
|
26
30
|
//
|
|
27
|
-
// SO A TEST NAME IS OUT OF SCOPE, and that is a real hole rather than an oversight:
|
|
28
|
-
//
|
|
29
|
-
// it — widening to string literals means deciding what to do about CSS and
|
|
30
|
-
// that decision belongs with the cleanup, not with this.
|
|
31
|
+
// SO A TEST NAME IS OUT OF SCOPE, and that is a real hole rather than an oversight: most of the
|
|
32
|
+
// standing citation population is test names, which are string literals. Named here so the next reader
|
|
33
|
+
// does not have to rediscover it — widening to string literals means deciding what to do about CSS and
|
|
34
|
+
// composite keys first, and that decision belongs with the cleanup, not with this.
|
|
31
35
|
//
|
|
32
36
|
// LINK TARGETS ARE EXEMPT. A markdown anchor and a URL fragment are addresses, not references.
|
|
33
37
|
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
import { execFileSync } from "node:child_process";
|
|
37
|
-
|
|
38
|
-
// THE TWELVE FILES RESTORED VERBATIM FROM THE FROZEN TIP AND KEPT. Thirteen were restored; the
|
|
39
|
-
// duplicate-skip arms were dropped in the same branch, so twelve reach main. They came across
|
|
40
|
-
// byte-exact, which is what made their arms trustworthy and also brought their references with them.
|
|
41
|
-
// Exempted for that one merge and removed by the cleanup; see the note above.
|
|
42
|
-
// RESTORED_VERBATIM IS GONE (tracker issue 188). It exempted twelve files by literal path so that a
|
|
43
|
-
// byte-for-byte restoration could land without the guard refusing its own restored text. Their tokens
|
|
44
|
-
// are now retired like everything else, so the list exempts nothing real — and a stale exemption list is
|
|
45
|
-
// worse than none: it silently covers files nobody is checking any more, and it is invisible in a diff
|
|
46
|
-
// that does not touch this file.
|
|
47
|
-
|
|
48
|
-
const TOKEN = /#[0-9]{3,}/g;
|
|
49
|
-
|
|
50
|
-
// A `#` COMMENT IS A COMMENT WHEREVER THE FILE FORMAT SAYS SO, not only in YAML (tracker issue 188).
|
|
51
|
-
//
|
|
52
|
-
// This read `#` as a comment for YAML alone, so the same sentence was refused in a .yml file and waved
|
|
53
|
-
// through in .env.example, a systemd unit or a shell script. Those comments are exactly as publicly
|
|
54
|
-
// visible, and `# REQUIRED — tracker issue 774 removed the code default` was sitting in .env.example on the public
|
|
55
|
-
// tree while this guard reported the tree clean. Found while measuring the class for the retirement pass:
|
|
56
|
-
// the guard's own rule flagged 359 tokens, and thousands more sat in files it had never classified.
|
|
57
|
-
//
|
|
58
|
-
// Extensionless is deliberate: a systemd unit or a dotfile often has no extension worth matching, so the
|
|
59
|
-
// KNOWN `#`-comment names are listed and everything else keeps the source rule.
|
|
60
|
-
const HASH_COMMENT = /(^|\/)(\.env[^/]*|[^/]*\.(ya?ml|sh|bash|service|timer|path|socket|conf|ini|toml|properties)|Dockerfile[^/]*|Makefile|\.gitignore|\.gitattributes)$/;
|
|
61
|
-
|
|
62
|
-
/** Is this added line one the check reads at all? Comments in source, everything in markdown. */
|
|
63
|
-
export const isProse = (path, line) => {
|
|
64
|
-
if (/\.mde?$/.test(path) || path.endsWith(".md")) return true;
|
|
65
|
-
const t = line.trim();
|
|
66
|
-
if (HASH_COMMENT.test(path)) return t.startsWith("#");
|
|
67
|
-
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// ── A COLOUR IS NOT A CITATION, AND THE DIGITS ALONE CANNOT SAY WHICH ───────────────────────────
|
|
71
|
-
//
|
|
72
|
-
// `TOKEN` matches digits only, so a six-digit hex colour is read as its leading digits and refused as a
|
|
73
|
-
// reference. An earlier sweep acted on exactly that reading and rewrote sixteen colour literals as issue
|
|
74
|
-
// text, two of them live mermaid `classDef` directives — so the documentation's diagrams rendered
|
|
75
|
-
// broken, and thirteen comments stated a value that was no longer there. Restoring them then hit this
|
|
76
|
-
// guard, whose refusal told the author to write the very text that had caused it.
|
|
38
|
+
// `demo/**` AND `driver/skills/**` ARE NOT READ. The reasons are in shared/reference-guard-classes.mjs
|
|
39
|
+
// beside the rule itself, because that is where the next person changing it will be looking.
|
|
77
40
|
//
|
|
78
|
-
//
|
|
41
|
+
// ── WHERE THE TABLE LIVES ────────────────────────────────────────────────────────────────────────
|
|
79
42
|
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// whole point of the check, and the arm asserts that direction too.
|
|
89
|
-
const HEX_COLOUR = /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})\b/g;
|
|
90
|
-
const COLOUR_PROPERTY = /(?:^|[;{\s(,])(?:color|background|background-color|border|border-color|fill|stroke|outline|box-shadow|text-shadow)\s*:\s*$/i;
|
|
91
|
-
|
|
92
|
-
/** Strip hex colours, so what remains is only tokens that could be a reference. */
|
|
93
|
-
export const withoutColourValues = (line) => String(line).replace(HEX_COLOUR, (m, offset, whole) => {
|
|
94
|
-
if (/[a-fA-F]/.test(m.slice(1))) return ""; // letters ⇒ not a decimal number
|
|
95
|
-
return COLOUR_PROPERTY.test(whole.slice(0, offset)) ? "" : m; // all digits ⇒ the property decides
|
|
96
|
-
});
|
|
43
|
+
// shared/reference-guard-classes.mjs, with the census that reads the same table. Two definitions of
|
|
44
|
+
// "what a public tree must not acquire" is one definition and one imitation of it, and the imitation
|
|
45
|
+
// is whichever the reader did not run.
|
|
46
|
+
import { execFileSync } from "node:child_process";
|
|
47
|
+
import {
|
|
48
|
+
CLASSES, offendingClasses, isProse, isScannable,
|
|
49
|
+
withoutColourValues, withoutLinkTargets,
|
|
50
|
+
} from "../shared/reference-guard-classes.mjs";
|
|
97
51
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
.replace(/https?:\/\/\S+/g, "") // bare URLs and their fragments
|
|
102
|
-
.replace(/<[^>]*>/g, ""); // angle-bracket autolinks
|
|
52
|
+
// Re-exported because scripts/retire-bare-refs.mjs and the arms beside this file import them from
|
|
53
|
+
// here, and moving the table should not move every caller in the same commit.
|
|
54
|
+
export { CLASSES, offendingClasses, isProse, isScannable, withoutColourValues, withoutLinkTargets };
|
|
103
55
|
|
|
104
56
|
/** Every offending token on one added line, or an empty array. */
|
|
105
|
-
export const offendingTokens = (path, line) =>
|
|
106
|
-
if (!isProse(path, line)) return [];
|
|
107
|
-
return [...withoutColourValues(withoutLinkTargets(line)).matchAll(TOKEN)].map((m) => m[0]);
|
|
108
|
-
};
|
|
57
|
+
export const offendingTokens = (path, line) => offendingClasses(path, line).map((c) => c.token);
|
|
109
58
|
|
|
110
59
|
const baseArg = () => {
|
|
111
60
|
const i = process.argv.indexOf("--base");
|
|
@@ -143,14 +92,21 @@ function main() {
|
|
|
143
92
|
// difference between "looked and found nothing" and "had nothing to look at".
|
|
144
93
|
const hits = [];
|
|
145
94
|
for (const { path, line } of added) {
|
|
146
|
-
for (const
|
|
95
|
+
for (const c of offendingClasses(path, line)) hits.push({ path, ...c, line: line.trim().slice(0, 100) });
|
|
147
96
|
}
|
|
148
97
|
console.log(`added-reference-check: read ${added.length} added line(s) against ${base}`);
|
|
149
98
|
if (!hits.length) return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
99
|
+
|
|
100
|
+
// GROUPED BY CLASS, because the remedy is per class and a flat list makes the reader derive it eight
|
|
101
|
+
// times. Each heading is said once, then the lines it applies to.
|
|
102
|
+
console.error(`\n${hits.length} line(s) added that a public tree must not carry:\n`);
|
|
103
|
+
for (const { id, why } of CLASSES) {
|
|
104
|
+
const mine = hits.filter((h) => h.id === id);
|
|
105
|
+
if (!mine.length) continue;
|
|
106
|
+
console.error(` ${id} — ${why}`);
|
|
107
|
+
for (const h of mine) console.error(` ${h.path}: ${h.token}\n ${h.line}`);
|
|
108
|
+
console.error("");
|
|
109
|
+
}
|
|
154
110
|
process.exit(1);
|
|
155
111
|
}
|
|
156
112
|
|
package/scripts/e2e.mjs
CHANGED
|
@@ -630,7 +630,7 @@ export function bandForScenario(scenario, resolve = resolveForDoor) {
|
|
|
630
630
|
/**
|
|
631
631
|
* — WHICH ENGINE COMMIT THIS RUN LOADED, read from the run's own artifacts.
|
|
632
632
|
*
|
|
633
|
-
*
|
|
633
|
+
* the testing handover requires the commit on every run, and until this existed it was reconstructed
|
|
634
634
|
* by joining a checkout's reflog against `startedAt`. That reconstruction expires with the reflog,
|
|
635
635
|
* dies with a re-clone, and is simply wrong for any run made while the checkout sat detached — it
|
|
636
636
|
* produced two near-miss wrong certifications in two days.
|
|
@@ -1354,7 +1354,7 @@ function evalAssertion(a, runDir) {
|
|
|
1354
1354
|
// ── — THIS IS A DELIVERY CONTRACT, AND A RUN THAT DID NOT DELIVER NEVER ENTERED IT ────────
|
|
1355
1355
|
//
|
|
1356
1356
|
// Owner ruling, 2026-08-22, verbatim: "clean up the failed runs. they owe the client nothing."
|
|
1357
|
-
// (
|
|
1357
|
+
// (Recorded here because the rule this line encodes is a product
|
|
1358
1358
|
// decision, not a harness preference.)
|
|
1359
1359
|
//
|
|
1360
1360
|
// Measured before the ruling: `sendPending` is carried by 25 of 25 delivered runs and 0 of 29
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
4
|
+
//
|
|
5
|
+
// EVERY GENERATED FILE IN THIS TREE IS CURRENT, or this says which one is not.
|
|
6
|
+
//
|
|
7
|
+
// node scripts/generated-files-are-current.mjs
|
|
8
|
+
//
|
|
9
|
+
// ── WHY THIS EXISTS, AND WHY IT DISCOVERS ITS OWN POPULATION ─────────────────────────────────────
|
|
10
|
+
//
|
|
11
|
+
// Each `scripts/mint-*.mjs` writes a file that is committed and derived, and each takes `--check` to
|
|
12
|
+
// say whether the committed copy still matches the tree. Until now CI ran exactly one of them. The
|
|
13
|
+
// other three could go stale on `main` and nothing here would say so — the failure surfaced instead
|
|
14
|
+
// in a private control that refuses to start on a stale fixture, which is the most expensive place
|
|
15
|
+
// to learn it and the furthest from whoever caused it.
|
|
16
|
+
//
|
|
17
|
+
// So this enumerates `scripts/mint-*.mjs` rather than naming them. A list would have to be extended
|
|
18
|
+
// by whoever adds the fifth minter, and the whole reason the fourth went unchecked is that nobody
|
|
19
|
+
// extended anything. A discovered population is covered by construction.
|
|
20
|
+
//
|
|
21
|
+
// AN EMPTY POPULATION IS A FAULT, NOT A PASS. If the glob matches nothing, this file has been moved
|
|
22
|
+
// or the naming convention has changed, and reporting "all current" over zero checks is exactly the
|
|
23
|
+
// shape of green this guard exists to refuse.
|
|
24
|
+
import { readdirSync } from "node:fs";
|
|
25
|
+
import { spawnSync, execFileSync } from "node:child_process";
|
|
26
|
+
import { join, dirname } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
|
|
29
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const ROOT = join(HERE, "..");
|
|
31
|
+
|
|
32
|
+
/** Every minter in `scripts/`, by filename, ordered. */
|
|
33
|
+
export function minters(dir = HERE) {
|
|
34
|
+
return readdirSync(dir).filter((f) => f.startsWith("mint-") && f.endsWith(".mjs")).sort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What the working tree looks like, so a minter that WROTE during `--check` can be caught writing.
|
|
39
|
+
*
|
|
40
|
+
* Returns null when the tree cannot be read — outside a checkout, say — and a null is carried as a
|
|
41
|
+
* stated limit rather than as a pass: the contract simply goes unchecked and the run says so.
|
|
42
|
+
*/
|
|
43
|
+
export function treeState(root = ROOT) {
|
|
44
|
+
try {
|
|
45
|
+
return execFileSync("git", ["-C", root, "status", "--porcelain"], { encoding: "utf8", maxBuffer: 1 << 28 });
|
|
46
|
+
} catch { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Run every minter's `--check` and sort the answers.
|
|
51
|
+
*
|
|
52
|
+
* `dir` and `root` are parameters so the arms can drive all three outcomes over throwaway minters.
|
|
53
|
+
* The alternative — dirtying a real generated file and restoring it — is a shared-file mutation, and
|
|
54
|
+
* the test runner runs files in parallel, so it would be a race that reddens somebody else's arm.
|
|
55
|
+
*/
|
|
56
|
+
export function checkAll({ dir = HERE, root = ROOT, log = console.log, readTree = () => treeState(root) } = {}) {
|
|
57
|
+
const found = minters(dir);
|
|
58
|
+
if (!found.length) return { found, stale: [], unreadable: [], wrote: [], empty: true };
|
|
59
|
+
|
|
60
|
+
const stale = [];
|
|
61
|
+
const unreadable = [];
|
|
62
|
+
const wrote = [];
|
|
63
|
+
for (const m of found) {
|
|
64
|
+
// ── `--check` IS A CONTRACT, AND NOTHING WAS VERIFYING IT ──────────────────────────────────────
|
|
65
|
+
//
|
|
66
|
+
// Every minter here is trusted to treat `--check` as "report, change nothing". A minter that
|
|
67
|
+
// ignores the flag and re-mints repairs the drift and then reports `current` — so the file is
|
|
68
|
+
// quietly fixed on whoever's machine ran it, the commit does not carry the repair, and this guard
|
|
69
|
+
// logs a clean line over a check that never happened. That is this file's own sentence one level
|
|
70
|
+
// in: a pass from a minter that did not look is not a pass either.
|
|
71
|
+
//
|
|
72
|
+
// The probe is the contract stated directly. `--check` must not write, so if the tree moved while
|
|
73
|
+
// one ran, it wrote. THE LIMIT, SAID RATHER THAN LEFT: this catches the harmful inert form, the
|
|
74
|
+
// one that silently repairs. A minter that ignores the flag and does nothing at all still reports
|
|
75
|
+
// `current`, and no probe from out here can tell that from a file that really is current.
|
|
76
|
+
const before = readTree();
|
|
77
|
+
const r = spawnSync(process.execPath, [join(dir, m), "--check"], { cwd: root, encoding: "utf8" });
|
|
78
|
+
const after = readTree();
|
|
79
|
+
const out = ((r.stdout || "") + (r.stderr || "")).trim();
|
|
80
|
+
if (before !== null && after !== null && before !== after) {
|
|
81
|
+
wrote.push({ m, out });
|
|
82
|
+
log(` WROTE ${m} (during --check)`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// 0 is current, 1 is stale, anything else is a minter that could not look — reported separately,
|
|
86
|
+
// because "I could not read the tree" and "the file is out of date" need different things done.
|
|
87
|
+
if (r.status === 0) { log(` current ${m}`); continue; }
|
|
88
|
+
if (r.status === 1) { stale.push({ m, out }); log(` STALE ${m}`); continue; }
|
|
89
|
+
unreadable.push({ m, out, code: r.status });
|
|
90
|
+
log(` ? ${m} (exit ${r.status})`);
|
|
91
|
+
}
|
|
92
|
+
return { found, stale, unreadable, wrote, empty: false, contractChecked: readTree() !== null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function main() {
|
|
96
|
+
const { found, stale, unreadable, wrote, empty, contractChecked } = checkAll();
|
|
97
|
+
if (empty) {
|
|
98
|
+
console.error("generated-files-are-current: no scripts/mint-*.mjs found. Either they moved or the "
|
|
99
|
+
+ "naming changed — and a pass over nothing is not a pass.");
|
|
100
|
+
process.exit(2);
|
|
101
|
+
}
|
|
102
|
+
console.log(`\ngenerated-files-are-current: checked ${found.length} minter(s)`
|
|
103
|
+
+ (contractChecked ? "" : "; the tree could not be read, so nothing verified that `--check` changed nothing"));
|
|
104
|
+
|
|
105
|
+
for (const { m, out } of [...wrote, ...stale, ...unreadable]) {
|
|
106
|
+
console.error(`\n──── ${m} ────\n${out}`);
|
|
107
|
+
}
|
|
108
|
+
if (wrote.length) {
|
|
109
|
+
console.error(`\n${wrote.length} minter(s) CHANGED THE TREE while running \`--check\`. \`--check\` `
|
|
110
|
+
+ `reports and changes nothing; one that re-mints repairs the drift on whoever ran it, leaves the `
|
|
111
|
+
+ `commit without the repair, and reports current over a check that did not happen. Fix the minter.`);
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
if (unreadable.length) {
|
|
115
|
+
console.error(`\n${unreadable.length} minter(s) could not look. That is not a pass; fix the minter first.`);
|
|
116
|
+
process.exit(2);
|
|
117
|
+
}
|
|
118
|
+
if (stale.length) {
|
|
119
|
+
console.error(`\n${stale.length} generated file(s) are out of date. Re-mint each one named above and `
|
|
120
|
+
+ `commit the result — a derived file that drifts is a check reporting on a tree that no longer exists.`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (import.meta.url === `file://${process.argv[1]}`) main();
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// ── THE ORACLE THIS DERIVATION IS CHECKED AGAINST ────────────────────────────────────────────────
|
|
22
22
|
//
|
|
23
23
|
// A derivation that is only ever compared against itself proves nothing, which is the shape this repo
|
|
24
|
-
// keeps paying for. So
|
|
24
|
+
// keeps paying for. So scripts/generated-files-are-current.mjs, in CI's guards job, holds this output to the
|
|
25
25
|
// THIRTEEN dead names Cart measured in the production install's own env file on 2026-09-05 — a
|
|
26
26
|
// population this script never saw and cannot influence. All thirteen must resolve to a replacement.
|
|
27
27
|
// If a future sweep breaks the suffix rule, that arm reds on real-world data rather than on a
|
|
@@ -82,7 +82,9 @@ function render(names) {
|
|
|
82
82
|
//
|
|
83
83
|
// node scripts/mint-names-in-force.mjs
|
|
84
84
|
//
|
|
85
|
-
//
|
|
85
|
+
// CI refuses a stale copy, from the guards job: scripts/generated-files-are-current.mjs runs every
|
|
86
|
+
// minter in scripts/ with --check. (This line used to name a test file that does not exist in this
|
|
87
|
+
// tree, so the only thing that ever caught this drifting was a private control refusing to start.)
|
|
86
88
|
|
|
87
89
|
export const NAMES_IN_FORCE = Object.freeze([
|
|
88
90
|
${names.map((n) => ` "${n}",`).join("\n")}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
+
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
4
|
+
//
|
|
5
|
+
// RE-MINT THE PUBLIC RESIDUE BACKLOG — the per-file, per-class count that may only go down.
|
|
6
|
+
//
|
|
7
|
+
// node scripts/mint-public-residue.mjs # report the delta, change nothing
|
|
8
|
+
// node scripts/mint-public-residue.mjs --check # ... and exit 1 if the file is out of date
|
|
9
|
+
// node scripts/mint-public-residue.mjs --apply # write it
|
|
10
|
+
//
|
|
11
|
+
// THE ONLY REASON TO RUN `--apply` IS THAT THE NUMBER WENT DOWN. Nothing here refuses to write a
|
|
12
|
+
// higher one — a table that could not record growth would be unable to describe a tree somebody
|
|
13
|
+
// widened a class over — but the floor arm in driver/test/the-public-residue-is-a-floor.test.mjs
|
|
14
|
+
// refuses the growth itself, and it reads the committed file rather than this script's output. So an
|
|
15
|
+
// author who mints upward has recorded the regression rather than absorbed it, and the suite says so
|
|
16
|
+
// on the next run. The two halves are deliberately not the same program.
|
|
17
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { join, dirname } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { CLASSES, censusOf, publishedOf } from "../shared/reference-guard-classes.mjs";
|
|
21
|
+
import { trackedFiles, skipReason } from "../shared/tracked-files.mjs";
|
|
22
|
+
import { isEntrypoint } from "../shared/is-entrypoint.mjs";
|
|
23
|
+
|
|
24
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
const FIXTURE = join(ROOT, "driver/test/fixtures/public-residue-backlog.json");
|
|
26
|
+
const GUARD = "public-residue-backlog";
|
|
27
|
+
|
|
28
|
+
/** The tree's current census, as the fixture records it. */
|
|
29
|
+
export function mint() {
|
|
30
|
+
const tracked = trackedFiles(GUARD, { root: ROOT });
|
|
31
|
+
if (tracked === null) return null;
|
|
32
|
+
// THE SAME POPULATION THE FLOOR READS, from the same helper. A mint over the index and a floor over
|
|
33
|
+
// HEAD would disagree under the overlay, and `--check` would report a difference that is only the
|
|
34
|
+
// two instruments asking different questions.
|
|
35
|
+
const p = publishedOf(tracked, ROOT);
|
|
36
|
+
if (p.error) { console.error(`mint-public-residue: ${p.error}`); process.exit(2); }
|
|
37
|
+
if (p.laid) console.log(`mint-public-residue: ${p.laid} tracked path(s) are not in HEAD — laid over this checkout, not published in it, and not counted`);
|
|
38
|
+
const c = censusOf(p.files, (f) => readFileSync(join(ROOT, f), "utf8"));
|
|
39
|
+
return { classes: CLASSES.map((x) => x.id), total: c.total, files: c.files };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Per-class totals, for a reader who wants to know WHICH number moved. */
|
|
43
|
+
export const byClass = (table) =>
|
|
44
|
+
CLASSES.map((c, i) => [c.id, Object.values(table.files).reduce((a, v) => a + v[i], 0)]);
|
|
45
|
+
|
|
46
|
+
function main() {
|
|
47
|
+
const apply = process.argv.includes("--apply");
|
|
48
|
+
const check = process.argv.includes("--check");
|
|
49
|
+
|
|
50
|
+
const now = mint();
|
|
51
|
+
// A COULD-NOT-LOOK EXITS 2, never 0. Outside a checkout there is no corpus, and a mint that wrote an
|
|
52
|
+
// empty table here would replace the whole backlog with nothing and call it a repair.
|
|
53
|
+
if (now === null) { console.error(`mint-public-residue: ${skipReason(GUARD)}`); process.exit(2); }
|
|
54
|
+
|
|
55
|
+
let was = null;
|
|
56
|
+
try { was = JSON.parse(readFileSync(FIXTURE, "utf8")); } catch { /* first mint */ }
|
|
57
|
+
|
|
58
|
+
console.log(`public residue: ${now.total} line(s) across ${Object.keys(now.files).length} file(s)`);
|
|
59
|
+
for (const [id, n] of byClass(now)) console.log(` ${String(n).padStart(5)} ${id}`);
|
|
60
|
+
|
|
61
|
+
const next = JSON.stringify(now, null, 2) + "\n";
|
|
62
|
+
const same = was && JSON.stringify(was, null, 2) + "\n" === next;
|
|
63
|
+
if (same) { console.log("the backlog is current"); return; }
|
|
64
|
+
|
|
65
|
+
if (was) {
|
|
66
|
+
const delta = now.total - was.total;
|
|
67
|
+
console.log(`\ntotal ${was.total} → ${now.total} (${delta >= 0 ? "+" : ""}${delta})`);
|
|
68
|
+
}
|
|
69
|
+
if (apply) { writeFileSync(FIXTURE, next); console.log("written"); return; }
|
|
70
|
+
console.log("\nre-run with --apply to write it");
|
|
71
|
+
if (check) process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isEntrypoint(import.meta.url)) main();
|