clearotron 0.2.4 → 0.3.0-beta.1
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 +13 -2
- package/CONTRIBUTING.md +1 -1
- package/INSTALL.md +62 -38
- package/bin/brandowner.mjs +18 -169
- package/bin/clearotron.mjs +3 -1
- package/bin/connect.mjs +28 -19
- package/bin/disconnect.mjs +3 -3
- package/bin/example.mjs +7 -7
- package/bin/framework-preflight.mjs +49 -0
- package/bin/grant.mjs +151 -93
- package/bin/onboard.mjs +220 -138
- package/bin/start.mjs +146 -137
- package/bin/stop.mjs +2 -2
- package/bin/update.mjs +1 -1
- package/build-info.json +2 -2
- package/docs/CLIENT-MCP.md +2 -2
- package/docs/E2E.md +12 -2
- package/docs/ONBOARDING.md +1 -1
- package/docs/PORTAL.md +14 -13
- package/docs/SECURITY.md +23 -24
- package/docs/architecture/04-configuration-reference.md +12 -5
- package/docs/architecture/05-config-governance.md +7 -7
- package/docs/architecture/07-quality-and-audit.md +1 -1
- package/docs/architecture/08-development-guide.md +5 -0
- package/docs/configuration.md +118 -0
- package/docs/decisions/0004-documentation-structure.md +2 -2
- package/docs/decisions/0006-what-the-public-repository-carries.md +2 -2
- package/driver/CHANGELOG.md +70 -0
- package/driver/ask-ledger.mjs +2 -2
- package/driver/cancel.mjs +27 -0
- package/driver/case-law-sources.mjs +3 -3
- package/driver/company-bundle.mjs +261 -0
- package/driver/compare.mjs +1 -1
- package/driver/compose-read.mjs +2 -2
- package/driver/config-inventory.mjs +2 -2
- package/driver/contract-audit.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/contract-vocabulary.mjs +1 -1
- package/driver/declination-call.mjs +1 -1
- package/driver/deliver-trigger.sh +3 -3
- package/driver/dev-portal.mjs +3 -1
- package/driver/digest-queue.mjs +1 -1
- package/driver/disposition-tool.mjs +1 -1
- package/driver/doc-constants.mjs +1 -1
- package/driver/drain-posture.mjs +2 -2
- package/driver/drainer-identity.mjs +1 -1
- package/driver/driver.config.mjs +44 -9
- package/driver/effective-scope.mjs +30 -1
- package/driver/effort-model.mjs +6 -6
- package/driver/engine/CONTRACT.md +2 -2
- package/driver/engine/anthropic-agent.mjs +11 -11
- package/driver/engine/jx-turn.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +29 -4
- package/driver/engine/mcp/recording-server.mjs +73 -1
- package/driver/engine/openai-agent.mjs +1 -1
- package/driver/engine/probe.mjs +28 -4
- package/driver/enqueue-schema.mjs +23 -3
- package/driver/findings-model.mjs +2 -2
- package/driver/flag-snapshot.mjs +2 -2
- package/driver/floor-duty.mjs +2 -2
- package/driver/frame-diff-model.mjs +1 -1
- package/driver/framework-preflight.mjs +143 -0
- package/driver/gateway.mjs +9 -1
- package/driver/hit-list.mjs +1 -1
- package/driver/jx-lanes.mjs +1 -1
- package/driver/jx.mjs +1 -1
- package/driver/knockout-assess-record.mjs +1 -1
- package/driver/knockout-review-record.mjs +435 -0
- package/driver/order-probe.mjs +1 -1
- package/driver/outbox-backoff.mjs +2 -2
- package/driver/owner-use-check.mjs +2 -2
- package/driver/package.json +1 -1
- package/driver/pipeline-knockout.mjs +105 -8
- package/driver/pipeline.mjs +81 -30
- package/driver/plain-register.mjs +77 -3
- package/driver/portal-access.mjs +141 -74
- package/driver/portal-config-view.mjs +59 -70
- package/driver/portal-report.mjs +4 -4
- package/driver/portal-service.mjs +348 -141
- package/driver/portal-upstream.mjs +105 -17
- package/driver/predelivery-lint.mjs +43 -18
- package/driver/product-rows.mjs +1 -1
- package/driver/products.mjs +1 -1
- package/driver/profile-page.html +30 -5
- package/driver/profile-service.mjs +197 -26
- package/driver/profiles.mjs +48 -1
- package/driver/publish/index.mjs +31 -13
- package/driver/publish/knockout.mjs +9 -5
- package/driver/publish/office-record-links.mjs +189 -0
- package/driver/publish/parse.mjs +3 -3
- package/driver/publish/publish-inputs.mjs +26 -0
- package/driver/publish/render-knockout.mjs +42 -42
- package/driver/publish/render.mjs +29 -4
- package/driver/publish/report-data.mjs +2 -2
- package/driver/publish/seed-pool.mjs +1 -1
- package/driver/publish/templates/report.css +8 -8
- package/driver/publish/xlsx.mjs +49 -7
- package/driver/queue-watch-verdict.mjs +2 -2
- package/driver/recipe-service.mjs +1 -1
- package/driver/record-carry.mjs +1 -1
- package/driver/reference-score.mjs +1 -1
- package/driver/reference-strip-signatures.mjs +1 -1
- package/driver/register-availability.mjs +4 -3
- package/driver/register-count.mjs +3 -3
- package/driver/register-records.mjs +1 -1
- package/driver/repair-composers.mjs +1 -1
- package/driver/repairs.mjs +3 -3
- package/driver/replay-archive.mjs +1 -1
- package/driver/report-card-record.mjs +1 -1
- package/driver/result-noun-fields.mjs +5 -0
- package/driver/roster-verdict.mjs +48 -5
- package/driver/run-activity.mjs +1 -1
- package/driver/run-requirements.mjs +18 -5
- package/driver/runner.mjs +24 -15
- package/driver/search-policy.mjs +8 -8
- package/driver/senior-rights.mjs +1 -1
- package/driver/skills/prelim-search/delivery-contract.md +1 -1
- package/driver/skills/prelim-search/risk-framework-triage.md +10 -7
- package/driver/stages-knockout.mjs +72 -6
- package/driver/stages.mjs +10 -10
- package/driver/suite-census.json +238 -70
- package/driver/synthesis-record.mjs +2 -2
- package/driver/systemd/clearotron-client-mcp.service +3 -3
- package/driver/systemd/clearotron-deploy.service +2 -2
- package/driver/systemd/clearotron-mcp-face.service +1 -1
- package/driver/systemd/clearotron-portal.service +3 -3
- package/driver/systemd/clearotron-worker.service +5 -5
- package/driver/systemd/install-census.mjs +1 -1
- package/driver/systemd/render-units.mjs +9 -9
- package/driver/terminal-clamp.mjs +1 -1
- package/driver/trigger-cap.mjs +18 -2
- package/driver/unit-inventory.mjs +8 -8
- package/driver/usage-ledger.mjs +5 -3
- package/driver/verify-knockout.mjs +7 -7
- package/driver/verify.mjs +5 -5
- package/driver/whatif-memo-run.mjs +1 -1
- package/driver/whatif-queue.mjs +3 -3
- package/driver/whatif-worker.mjs +2 -2
- package/examples/README.md +1 -1
- package/examples/grants.example.json +25 -24
- package/mcp-server/CHANGELOG.md +10 -0
- package/mcp-server/http-server.mjs +3 -3
- package/mcp-server/key-socket.mjs +1 -1
- package/mcp-server/lib/audit-view.mjs +3 -3
- package/mcp-server/lib/brief.mjs +3 -3
- package/mcp-server/lib/driver.mjs +1 -1
- package/mcp-server/lib/events.mjs +1 -1
- package/mcp-server/lib/http-handler.mjs +2 -2
- package/mcp-server/lib/instructions.mjs +2 -2
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/lib/ops.mjs +6 -3
- package/mcp-server/lib/options.mjs +15 -4
- package/mcp-server/lib/plan.mjs +7 -6
- package/mcp-server/lib/runs.mjs +10 -0
- package/mcp-server/lib/whatif.mjs +5 -5
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/README.md +1 -1
- package/mcp-server/remote/client-mcp-apikey.service +1 -1
- package/mcp-server/remote/client-mcp.service +2 -2
- package/mcp-server/remote/trademark-artifacts-http.service +1 -1
- package/mcp-server/server.mjs +38 -24
- package/package.json +2 -2
- package/portal-ui/dist/assets/{index-KFAHMgdT.js → index-CWTHP0sH.js} +3471 -1901
- package/portal-ui/dist/assets/{index-1ziUJX1E.css → index-KpytsmNH.css} +79 -26
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/lane-probe.mjs +9 -3
- package/providers/jx-subclass/lookup.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
- package/scripts/citation-drift-report.mjs +1 -1
- package/scripts/citation-line-check.mjs +2 -2
- package/scripts/drive-env-check.mjs +1 -1
- package/scripts/e2e.mjs +5 -5
- package/scripts/env-audit.mjs +13 -1
- package/scripts/headless-page.mjs +5 -5
- package/scripts/live-surface-check.mjs +26 -2
- package/scripts/mint-names-in-force.mjs +19 -5
- package/scripts/mint-reference-strip-backlog.mjs +1 -1
- package/scripts/mint-suite-census.mjs +37 -10
- package/scripts/pack-publishable.mjs +1 -1
- package/scripts/preinstall-node-check.mjs +1 -1
- package/scripts/release-await-cut.mjs +3 -3
- package/scripts/release-cut-decision.mjs +1 -1
- package/scripts/release-dist-tag.mjs +1 -1
- package/scripts/release-install-check.mjs +1 -1
- package/scripts/release-notes-lint.mjs +1 -1
- package/scripts/release-publish-guard.mjs +1 -1
- package/scripts/release-version-pr-checks.mjs +2 -2
- package/scripts/release-version.mjs +61 -5
- package/scripts/render-brand-banner.mjs +1 -1
- package/scripts/render-check.mjs +2 -2
- package/scripts/repo-writes.mjs +1 -1
- package/scripts/report-frame-check.mjs +1 -1
- package/scripts/report-screenshot.mjs +2 -2
- package/scripts/retire-bare-refs.mjs +1 -1
- package/scripts/revisit-render-check.mjs +1 -1
- package/scripts/score.mjs +1 -1
- package/scripts/strip-titles-and-attributions.mjs +389 -0
- package/scripts/strip-tracker-citations.mjs +122 -5
- package/scripts/test-run.mjs +4 -4
- package/scripts/third-party-notices.mjs +1 -1
- package/scripts/verify-publishable.mjs +1 -1
- package/shared/access-audience.mjs +2 -2
- package/shared/anon-overlay.mjs +1 -1
- package/shared/brand.mjs +15 -1
- package/shared/bundle-freshness.mjs +1 -1
- package/shared/bundle-rebuild.mjs +1 -1
- package/shared/checkout-move.mjs +2 -2
- package/shared/client-door.mjs +8 -8
- package/shared/connect-clients.mjs +7 -7
- package/shared/connector-signin-probe.mjs +1 -1
- package/shared/env-aliases.mjs +1 -1
- package/shared/env-local.mjs +5 -5
- package/shared/grants-edit.mjs +76 -0
- package/shared/install-auth.mjs +1 -1
- package/shared/listen.mjs +3 -3
- package/shared/mcp-challenge.mjs +1 -1
- package/shared/names-in-force.mjs +2 -1
- package/shared/onboarding-store.mjs +19 -2
- package/shared/reference-guard-classes.mjs +44 -2
- package/shared/register-selection.mjs +1 -1
- package/shared/scope.mjs +223 -56
- package/shared/secret-file.mjs +1 -1
- package/shared/server-units.mjs +1 -1
- package/shared/staff-domain.mjs +45 -78
- package/shared/summary-blocks.mjs +2 -2
- package/shared/systemd-failure.mjs +3 -3
- package/shared/tracked-files.mjs +1 -1
- package/shared/trigger-lane.mjs +1 -1
- package/shared/tty-style.mjs +1 -1
- package/shared/usage-block.mjs +1 -1
- package/shared/vacuous-pass.mjs +1 -1
- package/shared/verb-shim.mjs +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// ── WHY THIS IS DERIVED AND NOT WRITTEN BY HAND ──────────────────────────────────────────────────
|
|
8
8
|
//
|
|
9
|
-
// It is the replacement half of the retired-spelling check
|
|
9
|
+
// It is the replacement half of the retired-spelling check. The DETECTION half
|
|
10
10
|
// needs nothing from this file: no product code reads a `PRELIM_*` name any more, so a `PRELIM_*` that
|
|
11
11
|
// is set is dead whatever this list says. What the list decides is the second half of the sentence —
|
|
12
12
|
// which name to tell the operator to use instead — and that is where a hand list does damage. A hand
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
// --check exit 1 if the committed file disagrees with a fresh derivation, printing the difference
|
|
32
32
|
|
|
33
33
|
import { execFileSync } from "node:child_process";
|
|
34
|
+
import { publishedOf } from "../shared/reference-guard-classes.mjs"; // the published population, one helper for every minter
|
|
34
35
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
35
36
|
import { dirname, join } from "node:path";
|
|
36
37
|
import { fileURLToPath } from "node:url";
|
|
@@ -45,13 +46,26 @@ const OUT = join(REPO, "shared", "names-in-force.mjs");
|
|
|
45
46
|
* and a name only a test mentions is not a name this build reads. Including them would mint
|
|
46
47
|
* replacements for settings the product does not have. `.js` and `.ts` are in with `.mjs` because
|
|
47
48
|
* the providers' own `src` directories carry non-test `.js` — a `.mjs`-only derivation has produced
|
|
48
|
-
* wrong answers on this tree before
|
|
49
|
+
* wrong answers on this tree before.
|
|
49
50
|
*/
|
|
50
51
|
export function sourceFiles(root = REPO) {
|
|
51
|
-
|
|
52
|
+
// THE PUBLISHED POPULATION, NOT THE INDEX. `git ls-files` reads the index, and an overlay run stages
|
|
53
|
+
// the withheld corpus over a clone without committing it — so minting there counted withheld names
|
|
54
|
+
// into this public floor with nothing reporting it: the number is simply too high, and the next
|
|
55
|
+
// person to re-mint from a clean checkout is shown a shrink and told to record it. Same helper as the
|
|
56
|
+
// other three minters, because two spellings of this rule disagree under the overlay and `--check`
|
|
57
|
+
// then reports a difference that is only the two instruments asking different questions.
|
|
58
|
+
//
|
|
59
|
+
// Found by deriving the population of this guard rather than listing it: the check that holds the
|
|
60
|
+
// other three named them by hand and this file was not among them, so the guard passed over the
|
|
61
|
+
// fourth caller of the defect it exists to stop.
|
|
62
|
+
const all = execFileSync("git", ["-C", root, "ls-files", "*.mjs", "*.js", "*.ts"], {
|
|
52
63
|
encoding: "utf8", maxBuffer: 64 * 1024 * 1024,
|
|
53
64
|
}).split("\n").filter(Boolean);
|
|
54
|
-
|
|
65
|
+
const p = publishedOf(all, root);
|
|
66
|
+
if (p.error) { console.error(`mint-names-in-force: ${p.error}`); process.exit(2); }
|
|
67
|
+
if (p.laid) console.log(`mint-names-in-force: ${p.laid} tracked path(s) are not in HEAD — laid over this checkout, not published in it, and not counted`);
|
|
68
|
+
return p.files.filter((f) => !/(^|\/)(test|tests|bench)\//.test(f) && !/\.test\.(mjs|ts|js)$/.test(f));
|
|
55
69
|
}
|
|
56
70
|
|
|
57
71
|
const NAME_RE = /\bCLEAROTRON_[A-Z0-9_]+/g;
|
|
@@ -78,7 +92,7 @@ function render(names) {
|
|
|
78
92
|
//
|
|
79
93
|
// Every \`CLEAROTRON_*\` name this build reads. It exists so the retired-spelling check can name the
|
|
80
94
|
// replacement for an old \`PRELIM_*\` line rather than only saying the old one is dead — see
|
|
81
|
-
// shared/env-aliases.mjs
|
|
95
|
+
// shared/env-aliases.mjs. Re-mint with:
|
|
82
96
|
//
|
|
83
97
|
// node scripts/mint-names-in-force.mjs
|
|
84
98
|
//
|
|
@@ -3,7 +3,7 @@
|
|
|
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
|
// Mints `driver/test/fixtures/reference-strip-backlog.json` — the per-file floor under the repair of
|
|
6
|
-
// the sentences the reference strip broke
|
|
6
|
+
// the sentences the reference strip broke.
|
|
7
7
|
//
|
|
8
8
|
// Run it AFTER repairing lines, never to make a red arm green: the arm it feeds refuses any count that
|
|
9
9
|
// went UP, so re-minting is how a repair is recorded, not how a regression is absorbed.
|
|
@@ -41,6 +41,7 @@ import { CENSUS_WORKSPACES, CENSUS_ROOT_SCRIPTS, countTestSites, collectionFromM
|
|
|
41
41
|
import { providerTestFiles } from "./test-full.mjs";
|
|
42
42
|
import { withheldEntryFor, announceWithheldMode } from "../shared/withheld-paths-access.mjs"; // — withheld is a stated absence, not a loss.: the record does not ship, and without it every absence is a loss
|
|
43
43
|
import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — realpath both sides, or a symlinked invocation exits 0 silently
|
|
44
|
+
import { publishedOf } from "../shared/reference-guard-classes.mjs";
|
|
44
45
|
|
|
45
46
|
// — say which mode this run is in, so a relaxed check is never silent.
|
|
46
47
|
announceWithheldMode();
|
|
@@ -72,14 +73,44 @@ const README = [
|
|
|
72
73
|
"RE-STAMP DELIBERATELY: node scripts/mint-suite-census.mjs --apply",
|
|
73
74
|
];
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The tracked files matching `globs` that HEAD actually carries.
|
|
78
|
+
*
|
|
79
|
+
* `git ls-files` reads the INDEX. The private control stages a withheld corpus over a clone without
|
|
80
|
+
* committing it, so under that overlay the index holds files HEAD does not — and this census, minted
|
|
81
|
+
* on the published tree and checked against the overlaid one, reported every laid test file as an
|
|
82
|
+
* addition. The stamp was current and the check said stale, permanently, because the two runs were
|
|
83
|
+
* counting different populations rather than disagreeing about one.
|
|
84
|
+
*
|
|
85
|
+
* `publishedOf` is the discriminator the residue floor and the reference-strip backlog already use.
|
|
86
|
+
* One helper rather than three spellings: three drift, and the drift shows only under the overlay,
|
|
87
|
+
* which is the place nobody runs by accident.
|
|
88
|
+
*
|
|
89
|
+
* THE TWO CASES ARE NOT THE SAME and are not treated the same. A path in the index and absent from
|
|
90
|
+
* HEAD is LAID and is not counted. A path in HEAD and modified in the index is ordinary work in
|
|
91
|
+
* progress and is counted exactly as before — refusing that would make this refuse a normal edit.
|
|
92
|
+
* `publishedOf` filters on membership of HEAD, so a modified-but-committed file stays in.
|
|
93
|
+
*
|
|
94
|
+
* A tree with no HEAD cannot say what it published. That is a failure to look, and it exits rather
|
|
95
|
+
* than reading as "nothing is laid here", which is the permissive answer and the one that passes.
|
|
96
|
+
*/
|
|
97
|
+
function publishedFiles(root, globs) {
|
|
98
|
+
let all = [];
|
|
99
|
+
try {
|
|
100
|
+
all = execFileSync("git", ["-C", root, "ls-files", "--", ...globs], { encoding: "utf8" })
|
|
101
|
+
.split("\n").map((x) => x.trim()).filter(Boolean);
|
|
102
|
+
} catch { return []; }
|
|
103
|
+
if (!all.length) return [];
|
|
104
|
+
const p = publishedOf(all, root);
|
|
105
|
+
if (p.error) { console.error(`mint-suite-census: ${p.error}`); process.exit(2); }
|
|
106
|
+
if (p.laid) console.log(`mint-suite-census: ${p.laid} tracked path(s) are not in HEAD — laid over this checkout, not published in it, and not counted`);
|
|
107
|
+
return p.files.slice().sort();
|
|
108
|
+
}
|
|
109
|
+
|
|
75
110
|
export function buildCensus(root = ROOT) {
|
|
76
111
|
const out = { _README: README, workspaces: {}, rootScripts: {} };
|
|
77
112
|
for (const { ws, ext } of CENSUS_WORKSPACES) {
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
files = execFileSync("git", ["-C", root, "ls-files", "--", `${ws}/test/*.test.${ext}`], { encoding: "utf8" })
|
|
81
|
-
.split("\n").map((s) => s.trim()).filter(Boolean).sort();
|
|
82
|
-
} catch { files = []; }
|
|
113
|
+
const files = publishedFiles(root, [`${ws}/test/*.test.${ext}`]);
|
|
83
114
|
const perFile = {};
|
|
84
115
|
for (const f of files) {
|
|
85
116
|
const abs = join(root, f);
|
|
@@ -99,11 +130,7 @@ export function buildCensus(root = ROOT) {
|
|
|
99
130
|
// one: these globs span two shapes (`providers/_shared/test/` and `providers/<id>/test/`), and a
|
|
100
131
|
// stripped key would collide the moment two providers shipped a file of the same name.
|
|
101
132
|
for (const { script, globs } of CENSUS_ROOT_SCRIPTS) {
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
files = execFileSync("git", ["-C", root, "ls-files", "--", ...globs], { encoding: "utf8" })
|
|
105
|
-
.split("\n").map((x) => x.trim()).filter(Boolean).sort();
|
|
106
|
-
} catch { files = []; }
|
|
133
|
+
const files = publishedFiles(root, globs);
|
|
107
134
|
const perFile = {};
|
|
108
135
|
for (const f of files) {
|
|
109
136
|
const abs = join(root, f);
|
|
@@ -201,7 +201,7 @@ async function main() {
|
|
|
201
201
|
: " nothing stripped — no publish-only keys were present");
|
|
202
202
|
// Prove the repo manifest still has them, here, on every pack.
|
|
203
203
|
//
|
|
204
|
-
// ── WHICH KEYS THIS MAY REFUSE OVER, AND WHY IT IS NOT ALL OF THEM
|
|
204
|
+
// ── WHICH KEYS THIS MAY REFUSE OVER, AND WHY IT IS NOT ALL OF THEM ───────────────────────────────
|
|
205
205
|
//
|
|
206
206
|
// This asserted every entry of STRIP_KEYS was present in the repo manifest, and `private` has not
|
|
207
207
|
// been in it since publishing moved into CI — the STRIP_KEYS comment above says so in its own words.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
2
|
// Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
|
|
3
3
|
//
|
|
4
|
-
// The install refuses an unsupported Node BEFORE anything is written
|
|
4
|
+
// The install refuses an unsupported Node BEFORE anything is written.
|
|
5
5
|
//
|
|
6
6
|
// Three declarations already stated the requirement and none of them bound anyone: `engines.node` is a
|
|
7
7
|
// WARNING unless the person installing has set `engine-strict`, `.nvmrc` is read by nvm and nothing
|
|
@@ -55,7 +55,7 @@ import { cutDecision, versionAtHead, tagsHere } from "./release-cut-decision.mjs
|
|
|
55
55
|
* Default bound: twenty-five minutes at thirty-second steps. Both are arguments so an arm can drive
|
|
56
56
|
* the loop.
|
|
57
57
|
*
|
|
58
|
-
* TWENTY-FIVE, RAISED FROM FIFTEEN
|
|
58
|
+
* TWENTY-FIVE, RAISED FROM FIFTEEN, because this waits for the version pull
|
|
59
59
|
* request's OWN CI and that is what it must clear. Measured over the first three cuts, the wait held
|
|
60
60
|
* 552 s, 622 s and 686 s against a 900 s budget — rising every time, and the thing it waits on is the
|
|
61
61
|
* offline suite, which grows on purpose every time anybody adds an arm. The margin was one slow queue.
|
|
@@ -139,7 +139,7 @@ const git = (args) => execFileSync("git", args, { encoding: "utf8" });
|
|
|
139
139
|
* One read of `main`: the version it carries, whether that version is tagged, and WHICH COMMIT said so.
|
|
140
140
|
*
|
|
141
141
|
* THE COMMIT IS READ IN THE SAME PASS AS THE VERSION, and that is the whole point of this function
|
|
142
|
-
* existing rather than being three calls at the call site
|
|
142
|
+
* existing rather than being three calls at the call site. The job below used to
|
|
143
143
|
* check out `main` by name after this loop returned, so a commit landing in between — one that moves no
|
|
144
144
|
* version, an instrument fix with no note — was packed and published under a number whose changelog
|
|
145
145
|
* never described it. Nothing downstream could see it: the tip check compares VERSIONS, and the version
|
|
@@ -212,7 +212,7 @@ function main() {
|
|
|
212
212
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
213
213
|
now: () => Date.now() - started,
|
|
214
214
|
}).catch((e) => {
|
|
215
|
-
// ── A FAILURE TO LOOK IS NOT "NOT MERGED YET"
|
|
215
|
+
// ── A FAILURE TO LOOK IS NOT "NOT MERGED YET" ───────────────────────────────────────────────────
|
|
216
216
|
//
|
|
217
217
|
// Giving up quietly is the ordinary outcome of this loop and stays exit 0: checks that went red, a
|
|
218
218
|
// pull request somebody dismissed, a re-cut mid-flight. None of those is a release gone missing.
|
|
@@ -49,7 +49,7 @@ export function tagsHere({ run = (args) => execFileSync("git", args, { encoding:
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* WHICH COMMIT THE QUESTION IS ABOUT —
|
|
52
|
+
* WHICH COMMIT THE QUESTION IS ABOUT — measured on run 34050690448.
|
|
53
53
|
*
|
|
54
54
|
* `versionAtHead()` reads a COMMIT rather than the working tree, deliberately, and the header above says
|
|
55
55
|
* why at length. That is necessary and it was not sufficient: in the `version` job the changesets action
|
|
@@ -65,7 +65,7 @@ export function distTag(version, { preMode = false } = {}) {
|
|
|
65
65
|
throw new Error(`release-dist-tag: "${version}" is not a version this can read, so the channel it `
|
|
66
66
|
+ "belongs on cannot be derived. Publishing it would default to `latest`.");
|
|
67
67
|
}
|
|
68
|
-
// ── THE PRE-MODE OVERRIDE IS GONE, AND `preMode` NO LONGER DECIDES ANYTHING
|
|
68
|
+
// ── THE PRE-MODE OVERRIDE IS GONE, AND `preMode` NO LONGER DECIDES ANYTHING ──────────────────────
|
|
69
69
|
//
|
|
70
70
|
// It read `if (preMode) return STABLE`, putting every pre-release on `latest` under the owner's ruling
|
|
71
71
|
// of 2026-09-05. That was right while nothing installed the package and wrong once things did: nine
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// ── WHAT IT IS FOR, AND WHY IT IS NOT ANOTHER MANIFEST ASSERTION ────────────────────────────────────
|
|
9
9
|
//
|
|
10
|
-
// Five consecutive releases published a package npm refused to install
|
|
10
|
+
// Five consecutive releases published a package npm refused to install. Every
|
|
11
11
|
// check in the pipeline passed on every one of them, because each asked a question about the tarball
|
|
12
12
|
// and none asked the only question a visitor asks: does `npm install clearotron` work.
|
|
13
13
|
//
|
|
@@ -50,7 +50,7 @@ export const EMPTY_PHRASES = ["now correctly", "as expected", "properly"];
|
|
|
50
50
|
* A path whose first segment is one of these is a SOURCE-TREE path — `driver/test/…`, `scripts/…` — and
|
|
51
51
|
* means nothing to somebody who has never opened this repository. A path that starts at the reader's own
|
|
52
52
|
* home does not: `~/.config/clearotron/` is where THEIR settings are, and telling them is the note's job.
|
|
53
|
-
*
|
|
53
|
+
* Ruling 2026-09-05, narrowing the contract's flat ban on "file paths": what a reader types or
|
|
54
54
|
* opens is allowed; our tree is not.
|
|
55
55
|
*
|
|
56
56
|
* Derived from the tree rather than typed, so a directory added next month is covered without anybody
|
|
@@ -89,7 +89,7 @@ export function refusals({ workflow, rootPkg }) {
|
|
|
89
89
|
if (!/id-token:\s*write/.test(live)) add("the release workflow cannot request an OIDC token (`id-token: write` is gone)");
|
|
90
90
|
if (!/environment:\s*npm\b/.test(live)) add("the release workflow no longer runs in the `npm` environment the publisher is registered under");
|
|
91
91
|
|
|
92
|
-
// ── AND EVERY PUBLISHING JOB CARRIES BOTH, not merely the file somewhere
|
|
92
|
+
// ── AND EVERY PUBLISHING JOB CARRIES BOTH, not merely the file somewhere ────────────────────────────
|
|
93
93
|
//
|
|
94
94
|
// The two checks above ask whether the strings appear ANYWHERE. That was sufficient while one job
|
|
95
95
|
// published. It stopped being sufficient the moment a second publishing job was added: a workflow
|
|
@@ -134,7 +134,7 @@ export function checksVerdict({ checkRuns = [], workflowRuns = [], policy = null
|
|
|
134
134
|
/**
|
|
135
135
|
* This file's own window: 32 attempts at 15 s, or eight minutes.
|
|
136
136
|
*
|
|
137
|
-
* NAMED SO AN ARM CAN COUPLE THE JOB'S BUDGET TO IT
|
|
137
|
+
* NAMED SO AN ARM CAN COUPLE THE JOB'S BUDGET TO IT. The `version` job's
|
|
138
138
|
* `timeout-minutes` has to contain THIS wait, and the arm that claimed to check that compared the job
|
|
139
139
|
* against `release-await-cut.mjs`'s constant instead — a different wait, in a different job, which this
|
|
140
140
|
* job never runs. It held by luck and would have failed the moment that unrelated number was raised.
|
|
@@ -231,7 +231,7 @@ async function main() {
|
|
|
231
231
|
console.log(seen.reason);
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
234
|
-
// ── A PARKED RUN IS THE ORDINARY STATE OF A CUT, NOT A FAULT
|
|
234
|
+
// ── A PARKED RUN IS THE ORDINARY STATE OF A CUT, NOT A FAULT ─────────────────────────────────────
|
|
235
235
|
//
|
|
236
236
|
// This failed the job, and failing it was worse than the thing it reported. Measured 2026-09-06:
|
|
237
237
|
// a merge cut version pull request 59, its run parked awaiting approval as EVERY version branch's
|
|
@@ -54,7 +54,8 @@ export function groupVersion(root = ROOT) {
|
|
|
54
54
|
* fixed group means one note commonly lands in several files unchanged.
|
|
55
55
|
*/
|
|
56
56
|
export function assembleRoot(version, root = ROOT) {
|
|
57
|
-
const
|
|
57
|
+
const entries = [];
|
|
58
|
+
const seen = new Set();
|
|
58
59
|
for (const w of GROUP) {
|
|
59
60
|
const p = join(root, w, "CHANGELOG.md");
|
|
60
61
|
if (!existsSync(p)) continue;
|
|
@@ -68,17 +69,72 @@ export function assembleRoot(version, root = ROOT) {
|
|
|
68
69
|
const sections = text.split(/^## /m).slice(1);
|
|
69
70
|
const mine = sections.find((sec) => sec.split("\n", 1)[0].trim() === version);
|
|
70
71
|
if (!mine) continue;
|
|
71
|
-
for (const
|
|
72
|
-
const
|
|
72
|
+
for (const note of notesOf(mine)) {
|
|
73
|
+
const key = note.join("\n");
|
|
74
|
+
if (seen.has(key)) continue;
|
|
75
|
+
seen.add(key);
|
|
76
|
+
entries.push(...note);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { version, ...group(entries) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* One version section of a workspace changelog, as the notes it holds. Each note is the list of its
|
|
84
|
+
* entries in order, and each entry opens with the group it belongs under, unless none was named.
|
|
85
|
+
*
|
|
86
|
+
* EVERY PARAGRAPH IS AN ENTRY. Changesets writes a note's first line as a bullet and indents every later
|
|
87
|
+
* line beneath it. Reading only the bullets kept each note's first line and dropped the rest without a
|
|
88
|
+
* word: the 0.2.3 page reads "Five things a first-time reader could not act on." with none of the five
|
|
89
|
+
* under it, and a dry run of 0.3.0 put 8 of the 75 sentences in eight pending notes on the page
|
|
90
|
+
* (measured 2026-09-10). So each later paragraph is an entry of its own, and so is each list item.
|
|
91
|
+
*
|
|
92
|
+
* AN ENTRY THAT NAMES NO GROUP TAKES THE GROUP OF THE ENTRY BEFORE IT. A note opens with its group, and a
|
|
93
|
+
* paragraph that opens with another group starts that group's entries: `Fixed:` two paragraphs into a
|
|
94
|
+
* `New:` note is a fix, and so is the paragraph after it that explains the fix. A note whose first line
|
|
95
|
+
* names no group gives the rest of it none either, so the whole note is refused, not only its first line.
|
|
96
|
+
*/
|
|
97
|
+
export function notesOf(section) {
|
|
98
|
+
const notes = [];
|
|
99
|
+
let note = null, lines = null, current = null;
|
|
100
|
+
const close = () => {
|
|
101
|
+
if (!lines) return;
|
|
102
|
+
const text = lines.join(" ");
|
|
103
|
+
lines = null;
|
|
104
|
+
const own = /^(New|Fixed|For operators):\s/.exec(text)?.[1];
|
|
105
|
+
if (own) current = own;
|
|
106
|
+
note.push(own || !current ? text : `${current}: ${text}`);
|
|
107
|
+
};
|
|
108
|
+
for (const line of section.split("\n").slice(1)) {
|
|
109
|
+
const top = /^-\s+(.*\S)\s*$/.exec(line);
|
|
110
|
+
const item = /^\s+-\s+(.*\S)\s*$/.exec(line);
|
|
111
|
+
if (top) {
|
|
112
|
+
close();
|
|
113
|
+
note = [];
|
|
114
|
+
notes.push(note);
|
|
115
|
+
current = null;
|
|
73
116
|
// THE COMMIT SHA COMES OFF. Changesets' default generator prefixes every bullet with the commit
|
|
74
117
|
// that carried the note, so a squashed release writes the SAME seven characters at the head of
|
|
75
118
|
// every line — `- f7c1570:` seven times over, telling a reader nothing they can use. This is the
|
|
76
119
|
// file a customer opens to decide whether to upgrade; the provenance they can act on is the tag
|
|
77
120
|
// and the release page, both of which name that commit once.
|
|
78
|
-
|
|
121
|
+
lines = [top[1].replace(/^[0-9a-f]{7,40}: /, "")];
|
|
122
|
+
} else if (!note) {
|
|
123
|
+
continue; // the version heading, or anything before the first note
|
|
124
|
+
} else if (line.startsWith("#")) {
|
|
125
|
+
close(); // `### Patch Changes`: a new set of notes starts here
|
|
126
|
+
note = null;
|
|
127
|
+
} else if (!line.trim()) {
|
|
128
|
+
close(); // a blank line ends a paragraph
|
|
129
|
+
} else if (item) {
|
|
130
|
+
close();
|
|
131
|
+
lines = [item[1]];
|
|
132
|
+
} else {
|
|
133
|
+
(lines ??= []).push(line.trim()); // a paragraph's next line, or its first after a blank line
|
|
79
134
|
}
|
|
80
135
|
}
|
|
81
|
-
|
|
136
|
+
close();
|
|
137
|
+
return notes;
|
|
82
138
|
}
|
|
83
139
|
|
|
84
140
|
/**
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// retired it from every surface this product renders. Fronting the README with it would put a Swiss
|
|
15
15
|
// firm's logo on an open-source product — the exact thing that retirement was about.
|
|
16
16
|
//
|
|
17
|
-
// The wordmark renders LOWERCASE (
|
|
17
|
+
// The wordmark renders LOWERCASE (ruling 2026-08-21) while the name in prose does not, and it
|
|
18
18
|
// renders as SVG <text> in a GENERIC monospace stack: GitHub serves a README image sandboxed, so a
|
|
19
19
|
// webfont would not load and a named family the reader lacks would fall back to something unrelated.
|
|
20
20
|
|
package/scripts/render-check.mjs
CHANGED
|
@@ -216,7 +216,7 @@ const PROBE = `<script>
|
|
|
216
216
|
try { parent.postMessage({probeError:String(e && e.message || e)},'*'); } catch (e2) {}
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
// An error in here must not read as "no measurement" — that is the ambiguity
|
|
219
|
+
// An error in here must not read as "no measurement" — that is the ambiguity this was about.
|
|
220
220
|
window.addEventListener('error', function(ev){
|
|
221
221
|
try { parent.postMessage({probeError:String(ev && ev.message || ev)},'*'); } catch (e) {}
|
|
222
222
|
});
|
|
@@ -508,7 +508,7 @@ async function main() {
|
|
|
508
508
|
console.log(" Expect the sideways assertion to FAIL. A clean run here means the instrument is blind.\n");
|
|
509
509
|
}
|
|
510
510
|
let failures = 0;
|
|
511
|
-
// A COULD-NOT-LOOK IS NOT A FAILED MEASUREMENT
|
|
511
|
+
// A COULD-NOT-LOOK IS NOT A FAILED MEASUREMENT. Counted apart from `failures`
|
|
512
512
|
// because the two mean different things to whoever reads the exit code: 1 says the layout is wrong,
|
|
513
513
|
// 2 says nothing was measured. Merging them is how a starved runner sends a reader to look at CSS.
|
|
514
514
|
let unmeasured = 0;
|
package/scripts/repo-writes.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// ── why this exists ─────────────────────────────────────────────────────────────────────────────────
|
|
6
6
|
//
|
|
7
|
-
//
|
|
7
|
+
// `health-tells-the-truth-about-the-bundle` planted a future mtime on a REAL file in
|
|
8
8
|
// `portal-ui/src` to prove the staleness check fires, restored it afterwards, and went green. But
|
|
9
9
|
// `node --test` runs test FILES concurrently against one shared working tree, so for the seconds that
|
|
10
10
|
// plant was live, any other arm reading that path saw a tree from the future. The suite cannot
|
|
@@ -71,7 +71,7 @@ function main() {
|
|
|
71
71
|
"--virtual-time-budget=8000", "--dump-dom", `file://${join(work, "verify.html")}`,
|
|
72
72
|
], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 120000, env: browserEnv(work) });
|
|
73
73
|
|
|
74
|
-
// ── IS THIS OUR PAGE, OR CHROME'S ERROR PAGE?
|
|
74
|
+
// ── IS THIS OUR PAGE, OR CHROME'S ERROR PAGE? ───────────────────────────────────────────────────
|
|
75
75
|
//
|
|
76
76
|
// `--dump-dom` prints whatever chrome ended up showing, and a file it could not read produces the
|
|
77
77
|
// interstitial — which has a `<title>` of its own. This was saved from reporting a pass only by the
|
|
@@ -77,7 +77,7 @@ const cmd = (method, params = {}) => new Promise((r) => { const n = ++id; pendin
|
|
|
77
77
|
|
|
78
78
|
await cmd("Page.enable");
|
|
79
79
|
|
|
80
|
-
// ── IS THIS THE REPORT, OR CHROME'S OWN ERROR PAGE?
|
|
80
|
+
// ── IS THIS THE REPORT, OR CHROME'S OWN ERROR PAGE? ──────────────────────────────────────────────────
|
|
81
81
|
//
|
|
82
82
|
// Chrome is launched with the file URL as an ARGUMENT, so there is no navigation response to check and
|
|
83
83
|
// nothing here ever asked. When the file could not be read, Chrome showed `ERR_ACCESS_DENIED` — a page
|
|
@@ -126,7 +126,7 @@ if (typeof top !== "number") {
|
|
|
126
126
|
console.error(`report-screenshot: no element matched ${JSON.stringify(ANCHOR)} — nothing to anchor the frame to.`);
|
|
127
127
|
chrome.kill(); process.exit(1);
|
|
128
128
|
}
|
|
129
|
-
// ── CAN THIS BOX DRAW WHAT THE PAGE SAYS?
|
|
129
|
+
// ── CAN THIS BOX DRAW WHAT THE PAGE SAYS? ───────────────────────────────────────────────────────────
|
|
130
130
|
//
|
|
131
131
|
// The default demo product's report carries the mark's native-script renderings — ベンクリ, ベンコリ,
|
|
132
132
|
// ヴェンコリ — and they are load-bearing: the verdict sentence reads "A live Japanese class 9
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
-
// Retire bare `#NNN` tracker references from prose
|
|
4
|
+
// Retire bare `#NNN` tracker references from prose.
|
|
5
5
|
//
|
|
6
6
|
// node scripts/retire-bare-refs.mjs what would change, per file
|
|
7
7
|
// node scripts/retire-bare-refs.mjs --apply change it
|
|
@@ -146,7 +146,7 @@ const server = createServer((req, res) => {
|
|
|
146
146
|
hits.push({ epoch, route: routeKey(p, u.searchParams) })
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
if (p === '/portal/api/me') return json(res, { email: '
|
|
149
|
+
if (p === '/portal/api/me') return json(res, { email: 'manager@example-firm.com', permissions: { run: true, manage: true }, access: [{ kind: 'everything' }], accounts: '*', accountNames: {}, allowance: null })
|
|
150
150
|
if (p === '/portal/admin/roster') return json(res, { customers: [{ key: KEY, name: NAME }] })
|
|
151
151
|
if (p === '/portal/admin/families') return json(res, { of: {}, names: {} })
|
|
152
152
|
if (p === '/portal/api/runs') return json(res, { runs: RUNS() })
|
package/scripts/score.mjs
CHANGED
|
@@ -646,7 +646,7 @@ function print(id, ref, run, s, delta, refPath) {
|
|
|
646
646
|
for (const c of B.collisions)
|
|
647
647
|
console.log(` · ${c.owner}: reference "${c.entry}" is ${c.bucket}, surfaced "${c.noise}" is noise`);
|
|
648
648
|
}
|
|
649
|
-
// ── SAME PROPRIETOR, DIFFERENT MARKS — REPORTED, NEVER SUPPRESSING
|
|
649
|
+
// ── SAME PROPRIETOR, DIFFERENT MARKS — REPORTED, NEVER SUPPRESSING ───────────────────────────────
|
|
650
650
|
//
|
|
651
651
|
// These used to print under the line above, which tells the reader not to read the recall numbers.
|
|
652
652
|
// One large filer anywhere in a matter therefore suppressed a whole run's measurement, and did: R2's
|