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
|
@@ -188,7 +188,7 @@ html.anon-on [data-anon='mark']:not(.anon-clear) {
|
|
|
188
188
|
max-width: var(--screen-measure);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/* ── THE WIDTH VOCABULARY
|
|
191
|
+
/* ── THE WIDTH VOCABULARY ─────────────────────────────────────────────────────────────────────────
|
|
192
192
|
Owner, on his own screens: "we INCONSISTENTLY use screen width … consistency is important."
|
|
193
193
|
Measured: seventeen inline maxWidth literals across seven screens, in nine different values — each
|
|
194
194
|
its author's guess. Width is now a zone-type decision made HERE, once:
|
|
@@ -437,7 +437,7 @@ table.data td {
|
|
|
437
437
|
vertical-align: top;
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
/*
|
|
440
|
+
/* THE GRID IS DECLARED, NOT EMERGENT.
|
|
441
441
|
Without `table-layout: fixed` a table's columns are sized by their content, so every cell in every row
|
|
442
442
|
is a vote on where the columns sit. That is why opening a row on Clearances could re-flow the parent
|
|
443
443
|
table and truncate the `Updated` header to `Upd`, and why the alignment of an expanded read was
|
|
@@ -449,7 +449,7 @@ table.data.fixed {
|
|
|
449
449
|
table-layout: fixed;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
/*
|
|
452
|
+
/* THE FIXED TABLE HAS NO FLOOR OF ITS OWN.
|
|
453
453
|
`table.data` sets `min-width: 760px` so a content-sized table cannot collapse into unreadable slivers.
|
|
454
454
|
Under fixed layout that floor is not a safety net, it is the thing that overflows: the columns are
|
|
455
455
|
already declared as percentages of the container, so the table has a shape at any width, and a floor
|
|
@@ -460,7 +460,7 @@ table.data.fixed {
|
|
|
460
460
|
min-width: 0;
|
|
461
461
|
}
|
|
462
462
|
|
|
463
|
-
/*
|
|
463
|
+
/* THE HEADER MUST BE ALLOWED TO WRAP, or the font decides whether the table scrolls.
|
|
464
464
|
`table.data th` sets `white-space: nowrap`, which is right for a content-sized table and wrong here:
|
|
465
465
|
under fixed layout the cells can shrink (overflow-wrap: anywhere) but the header cannot, so the
|
|
466
466
|
letterspaced caps alone set an unshrinkable floor. When the brand webfont is unavailable — no network,
|
|
@@ -509,7 +509,7 @@ table.data tr.read-row > td {
|
|
|
509
509
|
background: var(--surface-sunken);
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
-
/*
|
|
512
|
+
/* the whole read row opens its report.
|
|
513
513
|
The affordance is on the thing you can click, across the WHOLE row, which is the point: the only way
|
|
514
514
|
in used to be a text button at the far right edge, furthest from the run title the reader has just
|
|
515
515
|
read, on a row with `cursor: auto` and no hover state at all. People concluded there was no report.
|
|
@@ -548,7 +548,7 @@ table.data tr.row {
|
|
|
548
548
|
cursor: pointer;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
/*
|
|
551
|
+
/* the row disclosure is a real button.
|
|
552
552
|
It was a bare span with aria-hidden, inside a <tr onClick>: silent to a screen reader AND with no
|
|
553
553
|
keyboard path to open a row at all. The button carries the name and the state; these rules make it
|
|
554
554
|
look exactly as the span did, so the fix is behavioural rather than visual.
|
|
@@ -577,7 +577,7 @@ button.twisty:focus-visible {
|
|
|
577
577
|
outline-offset: 1px;
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
-
/*
|
|
580
|
+
/* the grouping toggle. In the toolbar, beside the other view controls, so its state is visible
|
|
581
581
|
WITHOUT opening a menu: a toggle whose state you have to go looking for is a toggle that explains
|
|
582
582
|
nothing about why the sort looked wrong. */
|
|
583
583
|
.group-toggle {
|
|
@@ -595,7 +595,7 @@ table.data tr.row:hover td {
|
|
|
595
595
|
background: var(--surface-sunken);
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
-
/*
|
|
598
|
+
/* A SECTION HEADER, not a decorative divider.
|
|
599
599
|
Brand owner is the organising principle of the whole list, and a reader had to work it out: the name
|
|
600
600
|
was set in the smallest, lowest-contrast, most letterspaced type on the page, which reads as a rule
|
|
601
601
|
BETWEEN rows rather than as "everything below this belongs to Foxglade Interactive".
|
|
@@ -747,7 +747,7 @@ table.data tr.group-head ~ tr.row > td:first-child {
|
|
|
747
747
|
border-left-color: var(--border-strong);
|
|
748
748
|
}
|
|
749
749
|
|
|
750
|
-
/*
|
|
750
|
+
/* A TRANSIENT SURFACE, which this codebase did not have one of.
|
|
751
751
|
The selection bar used `.notice`. That treatment is not a one-off: it is what the error and empty
|
|
752
752
|
states on the same screen use — which is exactly why it was wrong here. A notice is a persistent
|
|
753
753
|
thing you READ; a selection bar is a transient thing you ACT on, and it appears and vanishes as you
|
|
@@ -773,7 +773,7 @@ table.data tr.group-head ~ tr.row > td:first-child {
|
|
|
773
773
|
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18);
|
|
774
774
|
}
|
|
775
775
|
|
|
776
|
-
/*
|
|
776
|
+
/* THE CHECKBOX, in the palette, in every state.
|
|
777
777
|
It rendered in the browser default blue: the only blue on an otherwise warm and maroon page, and the
|
|
778
778
|
single clearest sign that a control had been dropped in and never dressed.
|
|
779
779
|
`accent-color` handles unchecked and checked in one property. Focus and disabled are separate because
|
|
@@ -965,11 +965,11 @@ input.filter {
|
|
|
965
965
|
font-style: italic;
|
|
966
966
|
}
|
|
967
967
|
|
|
968
|
-
/*
|
|
968
|
+
/* three labelled rungs where there used to be one line. The requirement is that a
|
|
969
969
|
reader can tell the legal read from the consequences AT A GLANCE, so the label is its own strong
|
|
970
970
|
column rather than a lead-in sentence: three sentences run together in the old single-line cell would
|
|
971
971
|
be more text and no more legible, which is the failure this replaces. */
|
|
972
|
-
/* ONE grid for ALL rungs, not one per row
|
|
972
|
+
/* ONE grid for ALL rungs, not one per row: each row being its own grid sized
|
|
973
973
|
its label column with max-content PER ROW, so the text column started at a different x on every
|
|
974
974
|
line and a long qualifier ("(one or more)") deformed its own row's grid — the owner's screenshot.
|
|
975
975
|
The rows become display:contents so every label shares a single column, sized to the longest label
|
|
@@ -998,7 +998,7 @@ input.filter {
|
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
1000
|
.footer-demo-note {
|
|
1001
|
-
max-width: 420px; /*
|
|
1001
|
+
max-width: 420px; /* the note's width is the note's own, not its call site's */
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
1004
1004
|
/* The line beside the primary action: what happens next when it is available, and why it is not when
|
|
@@ -1168,7 +1168,7 @@ input.filter {
|
|
|
1168
1168
|
max-height: 230px;
|
|
1169
1169
|
}
|
|
1170
1170
|
|
|
1171
|
-
/* ──
|
|
1171
|
+
/* ── a territory the wired register does not reach ────────────────────────
|
|
1172
1172
|
Shown and selectable, never absent: an absent entry teaches a reader nothing, because they cannot
|
|
1173
1173
|
tell an unsupported territory from one they mistyped. The marker is a statement, not a warning — the
|
|
1174
1174
|
choice is legitimate and the report discloses it. */
|
|
@@ -1257,6 +1257,18 @@ input.filter {
|
|
|
1257
1257
|
line-height: 1.5;
|
|
1258
1258
|
}
|
|
1259
1259
|
|
|
1260
|
+
/* The house account, in the pick panel: first in the list and marked as the one you can always run
|
|
1261
|
+
under. The wash rather than a chip or a badge — it is not a warning about the entry, it is the entry
|
|
1262
|
+
a reader should land on when they have no reason to pick another. */
|
|
1263
|
+
.pick-generic {
|
|
1264
|
+
background: var(--accent-wash);
|
|
1265
|
+
border-color: var(--accent-ink);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.pick-generic .entry-title {
|
|
1269
|
+
color: var(--text-accent);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1260
1272
|
.start-pill {
|
|
1261
1273
|
padding: 8px 14px;
|
|
1262
1274
|
border: 1px solid var(--border-strong);
|
|
@@ -1312,7 +1324,7 @@ input.filter {
|
|
|
1312
1324
|
|
|
1313
1325
|
/* The column the wizard's steps live in. 720px, the design's measure for a form — NOT the footer's,
|
|
1314
1326
|
which spans the screen. */
|
|
1315
|
-
/* ──
|
|
1327
|
+
/* ── §B — THE MEASURE MOVES OFF THE COLUMN AND ONTO ITS BLOCKS ────────────────────────────────────
|
|
1316
1328
|
The owner: the New clearance page "is bound to an artificially narrow width; the table side-scrolls
|
|
1317
1329
|
or wraps and looks worse than its content deserves. The top panel already fits the screen width."
|
|
1318
1330
|
720px is the right measure for a COLUMN OF FIELDS and the wrong one for a five-column comparison. So
|
|
@@ -1340,7 +1352,7 @@ input.filter {
|
|
|
1340
1352
|
max-width: none;
|
|
1341
1353
|
}
|
|
1342
1354
|
|
|
1343
|
-
/* ──
|
|
1355
|
+
/* ── §B — "what this search includes", in or out at a glance ─────────────────────────────────────────
|
|
1344
1356
|
The list read as three sentences of equal weight, and whether an axis ran at all was buried in the
|
|
1345
1357
|
clause after the dash. The tick and the cross carry the answer; the colour is a reinforcement, never
|
|
1346
1358
|
the only signal, because a marker distinguishable by hue alone is not a marker for every reader. */
|
|
@@ -1371,7 +1383,7 @@ input.filter {
|
|
|
1371
1383
|
.carries-out .carries-mark { color: var(--text-faint); }
|
|
1372
1384
|
.carries-out { color: var(--text-faint); }
|
|
1373
1385
|
|
|
1374
|
-
/*
|
|
1386
|
+
/* THE SELECTOR'S DEPTH ICONS ARE GONE, by the owner's ruling of
|
|
1375
1387
|
2026-09-03: "in the summary things rescale and work great. in product selector … we can remove
|
|
1376
1388
|
these next to the product description, tiny fixed icons. barely visible." The ladder they drew was
|
|
1377
1389
|
correct and unreadable at the size the row gave it. The FINAL SUMMARY's effort bars stay exactly as
|
|
@@ -1442,7 +1454,7 @@ input.filter {
|
|
|
1442
1454
|
background: var(--accent-wash);
|
|
1443
1455
|
}
|
|
1444
1456
|
|
|
1445
|
-
/* ──
|
|
1457
|
+
/* ── A ROW THAT CANNOT BE PICKED HAS TO LOOK LIKE ONE ─────────────────────
|
|
1446
1458
|
The class was applied by the component and NO RULE EXISTED for it, so a disabled row rendered
|
|
1447
1459
|
identically to a live one: same border, same surface, same hover. That is exactly what the owner
|
|
1448
1460
|
reported — "it doesnt appear disabled, no message etc - but i cant select it."
|
|
@@ -1481,7 +1493,7 @@ input.filter {
|
|
|
1481
1493
|
color: var(--text-muted);
|
|
1482
1494
|
}
|
|
1483
1495
|
|
|
1484
|
-
/* ──
|
|
1496
|
+
/* ── a lane this search needs and this deployment does not have ───────────
|
|
1485
1497
|
Louder than the coverage note beside it: coverage narrows what a report covers, this removes a whole
|
|
1486
1498
|
section of the reasoning. Still not a refusal — the search runs and the report states the gap. */
|
|
1487
1499
|
.pick-row-capability {
|
|
@@ -1837,7 +1849,7 @@ input.filter {
|
|
|
1837
1849
|
border-bottom: 1px solid var(--border-hairline);
|
|
1838
1850
|
}
|
|
1839
1851
|
|
|
1840
|
-
/* ──
|
|
1852
|
+
/* ── the two trades, side by side at the press ────────────────────────────
|
|
1841
1853
|
Each option is a BLOCK a reader can read before clicking, not a button with a word on it: the whole
|
|
1842
1854
|
point of the question is that the two cost different things, and a row of verbs would hide that.
|
|
1843
1855
|
The safe one is first and plain; "Stop now" is marked but not alarmed — the owner asked for it
|
|
@@ -2025,7 +2037,7 @@ input.filter {
|
|
|
2025
2037
|
white-space: nowrap; cursor: pointer;
|
|
2026
2038
|
}
|
|
2027
2039
|
.home2-stop:hover, .home2-cancel:hover, .home2-ack:hover { border-color: var(--border-strong); color: var(--text-body); }
|
|
2028
|
-
/*
|
|
2040
|
+
/* the honest face of a Stop this deployment cannot serve. NOT home2-stop: the
|
|
2029
2041
|
browser check counts that class as a Stop that can act. */
|
|
2030
2042
|
.home2-stop-unavailable {
|
|
2031
2043
|
font-size: 12px;
|
|
@@ -2037,7 +2049,7 @@ input.filter {
|
|
|
2037
2049
|
cursor: not-allowed;
|
|
2038
2050
|
}
|
|
2039
2051
|
|
|
2040
|
-
/*
|
|
2052
|
+
/* the sentence that replaces the Stop button while a stop is in flight. */
|
|
2041
2053
|
.home2-stopping {
|
|
2042
2054
|
font-size: 12px;
|
|
2043
2055
|
color: var(--text-muted);
|
|
@@ -2045,7 +2057,7 @@ input.filter {
|
|
|
2045
2057
|
}
|
|
2046
2058
|
.home2-stop:disabled, .home2-cancel:disabled, .home2-ack:disabled { opacity: 0.55; cursor: default; }
|
|
2047
2059
|
|
|
2048
|
-
/* ──
|
|
2060
|
+
/* ── acknowledged, behind a count ──────────────────────────────────────────────────────────
|
|
2049
2061
|
Quiet on purpose. These are runs the reader has already dealt with, and the whole point of the
|
|
2050
2062
|
feature is that they stop competing with live work for attention — a panel styled like the queue
|
|
2051
2063
|
would put them back in the fight it just took them out of. */
|
|
@@ -2158,7 +2170,7 @@ input.filter {
|
|
|
2158
2170
|
.home2-qowner, .home2-qdepth { display: none; }
|
|
2159
2171
|
}
|
|
2160
2172
|
|
|
2161
|
-
/* ──
|
|
2173
|
+
/* ── THE USE-YOUR-AI PAGE ─────────────────────────────────────────────────
|
|
2162
2174
|
The owner walked this page against the standing test — "I am a lawyer, only slightly technical" —
|
|
2163
2175
|
and failed it. Four of the seven defects he listed were layout, and one of them was one word: the
|
|
2164
2176
|
buttons carried `className="btn"`, and there is no `.btn` in this file. They rendered as bare
|
|
@@ -2170,7 +2182,7 @@ input.filter {
|
|
|
2170
2182
|
the wrong one for a page of controls: the buttons stacked one per row down a 640px column with two
|
|
2171
2183
|
thirds of the frame empty beside them. The text keeps a readable measure (`--screen-measure`, the
|
|
2172
2184
|
vocabulary's own, not a ninth invented number) and the buttons take the frame and wrap. No new width
|
|
2173
|
-
is declared here, deliberately —
|
|
2185
|
+
is declared here, deliberately — the width vocabulary rules that a new one needs its own reason, and
|
|
2174
2186
|
this page did not need one, it needed to stop using the narrowest one for everything. */
|
|
2175
2187
|
.ai-screen h2 {
|
|
2176
2188
|
font-size: 17px;
|
|
@@ -2190,7 +2202,7 @@ input.filter {
|
|
|
2190
2202
|
|
|
2191
2203
|
.ai-connect-head { margin-top: 28px; }
|
|
2192
2204
|
|
|
2193
|
-
/* ── ONE QUESTION, WHEN THE DEPLOYMENT LEAVES IT OPEN
|
|
2205
|
+
/* ── ONE QUESTION, WHEN THE DEPLOYMENT LEAVES IT OPEN ─────────────────────────── */
|
|
2194
2206
|
.ai-where { margin: 0 0 14px; max-width: var(--screen-measure); }
|
|
2195
2207
|
|
|
2196
2208
|
.ai-where-q { margin: 0 0 8px; font-size: 15px; color: var(--text-strong); }
|
|
@@ -2333,3 +2345,44 @@ input.filter {
|
|
|
2333
2345
|
.ai-help-body p { margin: 0 0 10px; }
|
|
2334
2346
|
|
|
2335
2347
|
.ai-help-doc { font-size: 13px; }
|
|
2348
|
+
|
|
2349
|
+
/* ── Give someone access: the points on the tree a new person can be given ─────────────────────────────
|
|
2350
|
+
One row per point. A BUTTON, because choosing a point is an action and a keyboard has to reach it; the
|
|
2351
|
+
resets below are what make a button read as a row. `.sub` indents a company under the organisation
|
|
2352
|
+
it sits in. A row covered by a wider choice above it — everything, or its whole organisation — is
|
|
2353
|
+
disabled and drawn on, never unticked on its own. */
|
|
2354
|
+
.attach-row {
|
|
2355
|
+
display: flex;
|
|
2356
|
+
align-items: center;
|
|
2357
|
+
gap: 11px;
|
|
2358
|
+
width: 100%;
|
|
2359
|
+
padding: 9px 12px;
|
|
2360
|
+
border: 1px solid var(--border-hairline);
|
|
2361
|
+
border-radius: 8px;
|
|
2362
|
+
background: var(--surface-raised);
|
|
2363
|
+
color: inherit;
|
|
2364
|
+
font: inherit;
|
|
2365
|
+
font-size: 13.5px;
|
|
2366
|
+
text-align: left;
|
|
2367
|
+
cursor: pointer;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
.attach-row.on {
|
|
2371
|
+
border-color: var(--accent);
|
|
2372
|
+
background: var(--accent-wash);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
.attach-row.sub {
|
|
2376
|
+
width: auto;
|
|
2377
|
+
margin-left: 26px;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
.attach-row:disabled {
|
|
2381
|
+
cursor: default;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
.attach-row .sub-t {
|
|
2385
|
+
margin-left: auto;
|
|
2386
|
+
color: var(--text-faint);
|
|
2387
|
+
font-size: 12px;
|
|
2388
|
+
}
|
|
@@ -49,8 +49,8 @@
|
|
|
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-
|
|
53
|
-
<link rel="stylesheet" crossorigin href="/portal/assets/index-
|
|
52
|
+
<script type="module" crossorigin src="/portal/assets/index-CWTHP0sH.js"></script>
|
|
53
|
+
<link rel="stylesheet" crossorigin href="/portal/assets/index-KpytsmNH.css">
|
|
54
54
|
</head>
|
|
55
55
|
<body>
|
|
56
56
|
<div id="root"></div>
|
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.
|
|
5
|
+
"version": "0.3.0-beta.1",
|
|
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": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
//
|
|
28
28
|
// `proven` · `failed` (with the provider's own reason) · `cannot-prove` (with why).
|
|
29
29
|
//
|
|
30
|
-
// Reporting a provider that CANNOT be probed as `failed` is the could-not-look defect that
|
|
31
|
-
//
|
|
30
|
+
// Reporting a provider that CANNOT be probed as `failed` is the could-not-look defect that two
|
|
31
|
+
// separate filings closed this week — a check that could not run reported as a check that
|
|
32
32
|
// found something wrong. The count kernel beside this file sets the precedent: it returns `total: null`
|
|
33
33
|
// with a reason rather than a zero it did not count.
|
|
34
34
|
//
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
// call — so it gets no arm in CI, and ships with a green that means nothing. Which is precisely the
|
|
43
43
|
// class of instrument this issue exists to complain about.
|
|
44
44
|
|
|
45
|
+
import { join } from "node:path";
|
|
46
|
+
import { pathToFileURL } from "node:url";
|
|
47
|
+
|
|
45
48
|
// The marks are DATA, overridable, and each says why it was chosen. A control that stops returning hits
|
|
46
49
|
// turns this probe into a permanent false alarm, so the reason has to survive for whoever re-picks it.
|
|
47
50
|
export const DEFAULT_CONTROLS = Object.freeze({
|
|
@@ -79,7 +82,10 @@ export function probeSpend(capabilities) {
|
|
|
79
82
|
export async function loadProviderCapabilities(repoRoot, providerId, importer = (u) => import(u)) {
|
|
80
83
|
if (!providerId) return null;
|
|
81
84
|
try {
|
|
82
|
-
|
|
85
|
+
// pathToFileURL, not a hand-built `file://${repoRoot}/…`: a URL reads everything after a # as a
|
|
86
|
+
// fragment and decodes a literal %xx, so under such a folder this read the wrong file and the lane's
|
|
87
|
+
// cost came back UNKNOWN. On Windows the hand-built form held only by the URL parser's leniency.
|
|
88
|
+
const m = await importer(pathToFileURL(join(repoRoot, "providers", providerId, "src", "capabilities.js")).href);
|
|
83
89
|
return m?.CAPABILITIES ?? m?.default ?? null;
|
|
84
90
|
} catch { return null; }
|
|
85
91
|
}
|
|
@@ -41,7 +41,7 @@ export function subclassesFor(db, { country, term, niceClass }) {
|
|
|
41
41
|
//
|
|
42
42
|
// ── — THE 13TH EDITION GOVERNS THE CODE. CNIPA'S OWN WORDING STILL FINDS THE GOOD. ──────────
|
|
43
43
|
//
|
|
44
|
-
//
|
|
44
|
+
// Ruling, 2026-08-20: "13th edition. done." — option 2, accepting that the 12th's assignments
|
|
45
45
|
// lose authority WHEREVER THE EDITIONS DISAGREE. That last clause is the whole design: disagreement is
|
|
46
46
|
// the case being decided, and it is not the same case as "the 13th has never heard of this good".
|
|
47
47
|
//
|
|
@@ -7,7 +7,7 @@ Wants=network-online.target
|
|
|
7
7
|
[Service]
|
|
8
8
|
# Reads the OAuth cache at ~/.config/clawdi/oauth-mcp/courtlistener.json (no ~/.env needed).
|
|
9
9
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
10
|
-
# ── RESOLVED AT INSTALL (
|
|
10
|
+
# ── RESOLVED AT INSTALL (option B) ───────────────────────────────────────────────────────────
|
|
11
11
|
# `@CLEAROTRON_CHECKOUT_DIR@` is substituted by render-units.mjs when it writes the resolved copy.
|
|
12
12
|
# This unit deliberately loads no EnvironmentFile — one would let ~/.env override the PATH set above —
|
|
13
13
|
# so it cannot use the `${CLEAROTRON_CHECKOUT_DIR}` form the lane services use.
|
|
@@ -193,7 +193,7 @@ if (isEntrypoint(import.meta.url)) {
|
|
|
193
193
|
const rows = driftRows(citationsIntoChanged(corpus, byBase, changed, (pth) => trackedSet.has(pth)), changed);
|
|
194
194
|
|
|
195
195
|
if (argv.includes("--json")) { console.log(JSON.stringify({ range, changed: [...changed.keys()], rows }, null, 2)); process.exit(0); }
|
|
196
|
-
console.log(`citation-drift-report
|
|
196
|
+
console.log(`citation-drift-report: ${range} (base ${base.slice(0, 8)})`);
|
|
197
197
|
console.log(` ${changed.size} modified file(s); ${rows.length} citation(s) affected`);
|
|
198
198
|
if (!rows.length) {
|
|
199
199
|
console.log(" ✓ no citation on the base points below a net insertion in a file this change touches.");
|
|
@@ -232,7 +232,7 @@ export function decidableHits(citations, lineCountOf, ships = sourceCrossesTheCu
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
|
-
* A FLOOR UNDER THE POPULATION, RELATIVE TO THIS TREE —
|
|
235
|
+
* A FLOOR UNDER THE POPULATION, RELATIVE TO THIS TREE — ruling 2026-09-02, his words: "keep the
|
|
236
236
|
* verifier, kill the magic."
|
|
237
237
|
*
|
|
238
238
|
* The reason for a floor is unchanged and it is a good one: if the extractor breaks, every count goes to
|
|
@@ -633,7 +633,7 @@ const CALLS = /^\s*\(/;
|
|
|
633
633
|
const OWN_NUMBER = /^\s*(?:at\s+\d|:\d)/;
|
|
634
634
|
|
|
635
635
|
/** The symbol-shaped tokens adjoining a citation, in BOTH grammars: `cite SYM` and `SYM (cite)`. PURE. */
|
|
636
|
-
// ── THE RATCHET: A NEWLY ADDED CITATION CARRIES A SYMBOL
|
|
636
|
+
// ── THE RATCHET: A NEWLY ADDED CITATION CARRIES A SYMBOL ────────────────────────────────────────────
|
|
637
637
|
//
|
|
638
638
|
// The blindness above is not closeable on the existing corpus. Of the line citations in this tree only a
|
|
639
639
|
// minority name a symbol beside the number, and the rest are checked for EXISTENCE and nothing more — a
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
//
|
|
9
9
|
// A test that drives a real command and expects that command's `.env` to configure it gets the BUILT-IN
|
|
10
10
|
// DEFAULTS instead, silently, including default PORTS. Two independent mechanisms produce it and a drive
|
|
11
|
-
// has to defeat both
|
|
11
|
+
// has to defeat both:
|
|
12
12
|
//
|
|
13
13
|
// 1. `scripts/test-run.mjs` sets `CLEAROTRON_NO_ENV_FILE=1` for every child of the suite. Correct on
|
|
14
14
|
// its own terms — no test should be configured by a file on the developer's box.
|
package/scripts/e2e.mjs
CHANGED
|
@@ -1353,7 +1353,7 @@ function evalAssertion(a, runDir) {
|
|
|
1353
1353
|
// carries no runId, and this assertion is reading that exact status.json.
|
|
1354
1354
|
// ── — THIS IS A DELIVERY CONTRACT, AND A RUN THAT DID NOT DELIVER NEVER ENTERED IT ────────
|
|
1355
1355
|
//
|
|
1356
|
-
//
|
|
1356
|
+
// 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
|
//
|
|
@@ -1373,7 +1373,7 @@ function evalAssertion(a, runDir) {
|
|
|
1373
1373
|
//
|
|
1374
1374
|
// ── 2026-08-24, AND THIS SCOPE SURVIVED IT ────────────────────────────────────────────────────
|
|
1375
1375
|
//
|
|
1376
|
-
// A second
|
|
1376
|
+
// A second ruling that day ordered failed runs' notification packets onto the same re-drop
|
|
1377
1377
|
// cover as delivered ones, and `driver/outbox-backoff.mjs`'s `owedANotification` now says so. That
|
|
1378
1378
|
// is NOT a reversal of the line below and the two must not be read as one rule: what a failed run
|
|
1379
1379
|
// owes the CLIENT is still nothing, which is this assertion's subject; what it owes the REQUESTER
|
|
@@ -1502,7 +1502,7 @@ function evalAssertion(a, runDir) {
|
|
|
1502
1502
|
//
|
|
1503
1503
|
// Three states, not two: "cannot tell" is reported as itself rather than as breakage, because
|
|
1504
1504
|
// systemctl --user needs XDG_RUNTIME_DIR and a plain `sudo -u <user> node …` does not set it.
|
|
1505
|
-
// ── IT ASKS ABOUT THE WORKER FIRST, BECAUSE THE WORKER IS THE DRAIN
|
|
1505
|
+
// ── IT ASKS ABOUT THE WORKER FIRST, BECAUSE THE WORKER IS THE DRAIN ────────────────────────────────
|
|
1506
1506
|
//
|
|
1507
1507
|
// This function knew only about `prelim-driver.timer` and `prelim-driver.path` — the two RETIRED units.
|
|
1508
1508
|
// On a deployment drained by `clearotron-worker.service` it therefore found neither, concluded nothing
|
|
@@ -1908,7 +1908,7 @@ async function cmdRun(id) {
|
|
|
1908
1908
|
console.log(`\n${queuedIds.length} job(s) queued across ${doors.length} door(s)${
|
|
1909
1909
|
queuedIds.some((x) => !x) ? ` — ${queuedIds.filter((x) => !x).length} did not report an id` : ""
|
|
1910
1910
|
}. The QUEUE is the source of truth for what is in flight; watch it, not this list.`);
|
|
1911
|
-
// THE COMMAND IS PRINTED ONLY WHEN NO DRAINER HOLDS THIS QUEUE
|
|
1911
|
+
// THE COMMAND IS PRINTED ONLY WHEN NO DRAINER HOLDS THIS QUEUE, and the unit it
|
|
1912
1912
|
// names is the one this deployment actually uses. It used to be printed on the strength of two RETIRED
|
|
1913
1913
|
// units being inactive, which on a worker-drained box meant telling the operator to start a second
|
|
1914
1914
|
// drainer beside a running one. Suggesting a second drainer is the failure here, not the absence of a
|
|
@@ -2200,7 +2200,7 @@ function queueOutcomes(ref, queueDir = QUEUE_DIR, knownRuns = []) {
|
|
|
2200
2200
|
return out;
|
|
2201
2201
|
}
|
|
2202
2202
|
|
|
2203
|
-
// ──
|
|
2203
|
+
// ── HAS THIS ROUND FINISHED, AND IS THAT KNOWN? ───────────────────────────────────────────────────────────────────
|
|
2204
2204
|
//
|
|
2205
2205
|
// THE ONLY PLACE SETTLEDNESS IS DECIDED. Both the launch pre-flight and `report`'s stamp call this, so
|
|
2206
2206
|
// they can never disagree about whether the previous round is done — which is the disagreement that
|
package/scripts/env-audit.mjs
CHANGED
|
@@ -244,7 +244,19 @@ export const SYSTEM_OWNED = new Set([
|
|
|
244
244
|
// pushed back is right to have made this an explicit decision rather than an omission: they ARE read
|
|
245
245
|
// by product code, and the only honest answers were a row or this list.
|
|
246
246
|
"NO_COLOR", "FORCE_COLOR",
|
|
247
|
-
// ──
|
|
247
|
+
// ── WINDOWS SUBSYSTEM FOR LINUX ─────────────────────────────────────────────────────────────────
|
|
248
|
+
//
|
|
249
|
+
// Set by WSL itself on every session inside it, which is what makes them worth reading: `isWsl()` in
|
|
250
|
+
// bin/onboard.mjs tests both, because under WSL a Windows binary on PATH is not the engine and the
|
|
251
|
+
// installer has to say so. Nobody configures a Clearotron install by setting them, and an
|
|
252
|
+
// .env.example row would invite somebody to try — the same argument NO_COLOR gets above, and TERM
|
|
253
|
+
// before it.
|
|
254
|
+
//
|
|
255
|
+
// The alternative was a governance row, and it would have been a false claim about a document: the
|
|
256
|
+
// row would describe a decision this product does not make. Excluded rather than documented, which
|
|
257
|
+
// is what this list is for.
|
|
258
|
+
"WSL_DISTRO_NAME", "WSL_INTEROP",
|
|
259
|
+
// ── THE GITHUB ACTIONS RUNTIME ──────────────────────────────────────────────────────────────────
|
|
248
260
|
//
|
|
249
261
|
// `CI` above is already here for exactly this reason; these two arrived with the release scripts and
|
|
250
262
|
// want the same answer. GitHub sets both INSIDE a workflow run — `GITHUB_OUTPUT` is the step-output
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
// headless-page.mjs — did the browser open the page we asked for, or something of its own?
|
|
5
5
|
//
|
|
6
|
-
// (and, since
|
|
6
|
+
// (and, since criteria 3-4, whether this box can draw what that page says)
|
|
7
7
|
//
|
|
8
|
-
// ── WHY THIS EXISTS
|
|
8
|
+
// ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────────────
|
|
9
9
|
//
|
|
10
10
|
// Seven scripts drive headless Chrome and none of them asked. They cannot ask the obvious way: Chrome is
|
|
11
11
|
// launched with the file URL as a COMMAND-LINE ARGUMENT, so there is no navigation call whose response
|
|
@@ -80,7 +80,7 @@ export function pageVerdict({ href = "", expected = "", marker = null, markerNam
|
|
|
80
80
|
+ "captured is Chrome's interstitial, not the artefact — and that page carries an `<h1>`, a "
|
|
81
81
|
+ "`<title>` and a body, so a content check alone reads it as a success." };
|
|
82
82
|
}
|
|
83
|
-
// ── THE BROWSER'S START PAGE IS NOT A WRONG DOCUMENT
|
|
83
|
+
// ── THE BROWSER'S START PAGE IS NOT A WRONG DOCUMENT ────────────────────────────────────────────
|
|
84
84
|
//
|
|
85
85
|
// `about:blank` is what Chrome shows before it has navigated anywhere. Reaching the check below, it
|
|
86
86
|
// compares unequal to the expected URL and was reported as `wrong-document` — "a redirect, a stale tab
|
|
@@ -131,7 +131,7 @@ export function pageVerdict({ href = "", expected = "", marker = null, markerNam
|
|
|
131
131
|
export async function assertPageLoaded(evaluate, { expected, marker = null, markerName = "the page's own content", what = "this page", errorText = null,
|
|
132
132
|
graceMs = NAVIGATION_GRACE_MS, pollMs = NAVIGATION_POLL_MS,
|
|
133
133
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms)), now = () => Date.now() } = {}) {
|
|
134
|
-
// ── WAIT FOR THE BROWSER TO LEAVE ITS START PAGE, THEN JUDGE
|
|
134
|
+
// ── WAIT FOR THE BROWSER TO LEAVE ITS START PAGE, THEN JUDGE ─────────────────────────────────────
|
|
135
135
|
//
|
|
136
136
|
// Three of these scripts launch Chrome with the URL as an argument and get no response to wait on, so
|
|
137
137
|
// the first read of `location.href` can land before the browser has moved. On an idle box it never
|
|
@@ -204,7 +204,7 @@ export function chromeErrorPage(dom = "") {
|
|
|
204
204
|
|| /jstcache=|<body[^>]*\bid="?neterror"?/.test(t);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// ── CAN THIS BOX DRAW WHAT THE PAGE SAYS? (
|
|
207
|
+
// ── CAN THIS BOX DRAW WHAT THE PAGE SAYS? (criteria 3 and 4) ─────────────────────────────────────────
|
|
208
208
|
//
|
|
209
209
|
// The default demo product is a full-country search, and its report carries the mark's native-script
|
|
210
210
|
// renderings — ベンクリ, ベンコリ, ヴェンコリ. They are LOAD-BEARING: the verdict sentence reads "A live
|
|
@@ -85,6 +85,7 @@ import { mcpToolCall } from "../driver/portal-mcp-client.mjs";
|
|
|
85
85
|
import { readFlagSnapshot, builtFor, isStale, postureDelta, PRODUCTION_POSTURE } from "../driver/flag-snapshot.mjs";
|
|
86
86
|
import { policyFor, productAvailability, ORDERABLE_PRODUCTS } from "../driver/search-policy.mjs";
|
|
87
87
|
import { rosterVerdict } from "../driver/roster-verdict.mjs";
|
|
88
|
+
import { triggerCapGap, triggerCapWarning } from "../driver/trigger-cap.mjs"; // the portal's own sentence about a key's gap
|
|
88
89
|
import { bundledDemoKeys } from "../driver/bundled-demos.mjs";
|
|
89
90
|
import { unitsActiveVerdict } from "../driver/unit-state-verdict.mjs";
|
|
90
91
|
import { deploymentBox } from "../shared/deployment-box.mjs"; // — extracted; one allowlist, two readers
|
|
@@ -517,10 +518,33 @@ try {
|
|
|
517
518
|
try { bundledDemos = bundledDemoKeys({ profilesDir: BUNDLED_PROFILES_DIR }); }
|
|
518
519
|
catch (e) { fail("roster resolves", `the bundled roster could not be derived, so #83 cannot be ruled out: ${e.message}`); }
|
|
519
520
|
|
|
521
|
+
// THE DOOR ANSWERED FOR THIS KEY, so the comparison is made within this key's cap. Its claims are read
|
|
522
|
+
// the way doctor and the brand-owner command read them. A key that cannot be read leaves the verdict
|
|
523
|
+
// unable to tell a narrowing from a fault, and it says so rather than guessing either way.
|
|
524
|
+
let caller = { readable: false, accounts: null };
|
|
525
|
+
try { caller = (await import("../driver/portal-service.mjs")).opsTokenPosture(OPS_TOKEN); }
|
|
526
|
+
catch { /* stays unreadable — the verdict reports what it could not compare */ }
|
|
520
527
|
if (bundledDemos) {
|
|
521
|
-
const { state, message } = rosterVerdict({ keys, onDisk, bundledDemos,
|
|
528
|
+
const { state, message } = rosterVerdict({ keys, onDisk, bundledDemos, caller,
|
|
529
|
+
expectDemos: process.env.CLEAROTRON_E2E_EXPECT_DEMO_ROSTER === "1" });
|
|
522
530
|
({ pass, fail, skip })[state]("roster resolves", message);
|
|
523
531
|
}
|
|
532
|
+
// A COMPANY IN THE STORE THAT THIS KEY CANNOT START is its own finding, not a roster disagreement. A
|
|
533
|
+
// warning, not a failure: the portal re-takes its credential at the start of every call, so a Start for
|
|
534
|
+
// it is refused only when that re-mint fails. The sentence is the portal's own.
|
|
535
|
+
if (onDisk) {
|
|
536
|
+
if (!caller.readable)
|
|
537
|
+
skip("trigger key covers the roster", OPS_TOKEN
|
|
538
|
+
? "the key's claims could not be read, so its cap was NOT compared with the store"
|
|
539
|
+
: "no trigger key was found, so there is no cap to compare — see \"trigger lane wired\"");
|
|
540
|
+
else {
|
|
541
|
+
const gap = triggerCapGap({ accounts: caller.accounts, roster: onDisk });
|
|
542
|
+
if (gap.uncovered.length) record("trigger key covers the roster", "warn", triggerCapWarning(gap));
|
|
543
|
+
else pass("trigger key covers the roster", gap.capped
|
|
544
|
+
? `every company in the store (${onDisk.length}) is within the key's cap`
|
|
545
|
+
: "the key carries no account cap, so it covers every company");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
524
548
|
} catch (e) {
|
|
525
549
|
fail("roster resolves", `list_profiles failed: ${e.message}`);
|
|
526
550
|
}
|
|
@@ -562,7 +586,7 @@ try {
|
|
|
562
586
|
fail("ops-MCP reachable", `${MCP_URL}: ${e.message}`);
|
|
563
587
|
}
|
|
564
588
|
|
|
565
|
-
// ── 3b. THE TRIGGER LANE, AS ITS OWN SURFACE
|
|
589
|
+
// ── 3b. THE TRIGGER LANE, AS ITS OWN SURFACE ─────────────────────────────────────────────────────────
|
|
566
590
|
//
|
|
567
591
|
// The lane the portal's Start button calls is `PORTAL_MCP_URL` + `PORTAL_OPS_TOKEN`, and NOTHING on
|
|
568
592
|
// this box exercised it. On 2026-09-02 the owner could not start a run: every surface here was green,
|