clearotron 0.3.3 → 0.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +1 -14
- package/bin/brandowner.mjs +5 -5
- package/bin/connect.mjs +4 -4
- package/bin/onboard.mjs +5 -7
- package/bin/start.mjs +20 -5
- package/build-info.json +2 -2
- package/driver/CHANGELOG.md +18 -0
- package/driver/company-bundle.mjs +11 -18
- package/driver/door-call-verdict.mjs +27 -0
- package/driver/driver.config.mjs +1 -2
- package/driver/package.json +1 -1
- package/driver/pipeline.mjs +14 -5
- package/driver/portal-mcp-client.mjs +1 -1
- package/driver/portal-request-origin.mjs +79 -0
- package/driver/portal-service.mjs +43 -15
- package/driver/profile-page.html +9 -13
- package/driver/profile-service.mjs +25 -13
- package/driver/profiles.mjs +17 -4
- package/driver/publish/index.mjs +13 -1
- package/driver/publish/render.mjs +13 -2
- package/driver/publish/search-depth.mjs +4 -2
- package/driver/run-economics.mjs +7 -18
- package/driver/stages.mjs +1 -1
- package/driver/suite-census.json +61 -31
- package/driver/tokens.mjs +18 -10
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/lib/audit.mjs +9 -2
- package/mcp-server/lib/http-handler.mjs +7 -3
- package/mcp-server/mint-token.mjs +8 -6
- package/mcp-server/package.json +1 -1
- package/mcp-server/server.mjs +10 -0
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-GBbbyQxc.js → index-DVtz44vH.js} +57 -8
- package/portal-ui/dist/index.html +1 -1
- package/portal-ui/package.json +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/scripts/freeze-example-run.mjs +1 -1
- package/scripts/live-surface-check.mjs +11 -2
- package/scripts/release-entry-catch-up.mjs +142 -0
- package/shared/client-door.mjs +15 -8
- package/shared/scope.mjs +25 -10
- package/shared/store-in-repo.mjs +38 -17
|
@@ -176,6 +176,22 @@ function bandsBandRows(deck, manifest) {
|
|
|
176
176
|
return out;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
// NO STORE PATH AND NO LOAD-LOG WORDING REACHES A BROWSER. The profile validator opens every sentence with
|
|
180
|
+
// the file it judged (`profiles/<key>.json: `), which is right in a terminal and in the load log and is a
|
|
181
|
+
// server path on the company page, so the door takes the prefix off. The three marketplace-entry refusals
|
|
182
|
+
// are then worded for the browser too: their load-log words ("the grid program domain-restricts to it",
|
|
183
|
+
// "inflates the floor") are engine vocabulary, and the staff profile editor already carries a sentence for
|
|
184
|
+
// each (profile-page.html's MAP), copied here unchanged. Every other sentence passes as the validator wrote it.
|
|
185
|
+
const BROWSER_WORDING = [
|
|
186
|
+
[/^platforms must not list "web"/, "Don’t list “web” — the general web is always searched. Remove it."],
|
|
187
|
+
[/^platforms entry .* must be a bare store DOMAIN/, "One of the marketplaces isn’t a website address — use domains like amazon.com, one per line."],
|
|
188
|
+
[/^platforms entry .* is duplicated/, "A marketplace is listed twice — remove the duplicate."],
|
|
189
|
+
];
|
|
190
|
+
export const forBrowser = (errors) => (errors ?? []).map((e) => {
|
|
191
|
+
const bare = String(e).replace(/^profiles\/[^\s:]*\.json: /, "");
|
|
192
|
+
return BROWSER_WORDING.find(([re]) => re.test(bare))?.[1] ?? bare;
|
|
193
|
+
});
|
|
194
|
+
|
|
179
195
|
/**
|
|
180
196
|
* A refusal, worded for the person in the browser.
|
|
181
197
|
*
|
|
@@ -200,18 +216,14 @@ export function browserRefusal(e) {
|
|
|
200
216
|
return { error: "refused", code: "domain_claimed", domain: d.domain, heldBy: d.heldBy,
|
|
201
217
|
message: `${d.domain} is already used by another company. An address can only belong to one, `
|
|
202
218
|
+ `because the engine uses it to tell them apart. Remove it here, or take it off the other company first.` };
|
|
203
|
-
case "no_marketplaces":
|
|
204
|
-
return { error: "refused", code: "no_marketplaces",
|
|
205
|
-
message: "This installation has no default marketplaces set up, so there is nothing to search. "
|
|
206
|
-
+ "Add at least one marketplace for this company, or ask an administrator to set the defaults." };
|
|
207
219
|
case "framework_missing":
|
|
208
220
|
return { error: "refused", code: "framework_missing",
|
|
209
221
|
message: "The risk framework this company was pointed at is not on this installation, so nothing "
|
|
210
222
|
+ "was created. Rating a company under a framework nobody chose is worse than refusing." };
|
|
211
223
|
case "invalid_bundle":
|
|
212
224
|
// The validator collects, so these are already per-field sentences written for a reader.
|
|
213
|
-
return { error: "refused", code: "invalid_bundle", errors: d.errors
|
|
214
|
-
message: (d.errors
|
|
225
|
+
return { error: "refused", code: "invalid_bundle", errors: forBrowser(d.errors),
|
|
226
|
+
message: forBrowser(d.errors).join(" ") || String(e.message) };
|
|
215
227
|
default:
|
|
216
228
|
return { error: "refused", message: String(e.message) };
|
|
217
229
|
}
|
|
@@ -439,13 +451,12 @@ export function makeProfileService({
|
|
|
439
451
|
try { assertProfileKey(wanted); }
|
|
440
452
|
catch (e) { return { status: 400, json: { error: "bad_key", message: String(e.message) } }; }
|
|
441
453
|
|
|
442
|
-
const existing = resolveExisting();
|
|
443
454
|
let framework, platforms, profile;
|
|
444
455
|
try {
|
|
445
456
|
// ALWAYS SET, NEVER ABSENT — the company's own when one is supplied, the house default named
|
|
446
457
|
// otherwise. This is the whole reason a create does not go through save.
|
|
447
458
|
framework = resolveFramework(isStr(body?.frameworkPath) ? body.frameworkPath : null);
|
|
448
|
-
platforms = resolvePlatforms(Array.isArray(body?.platforms) ? body.platforms : null
|
|
459
|
+
platforms = resolvePlatforms(Array.isArray(body?.platforms) ? body.platforms : null);
|
|
449
460
|
profile = buildProfile({
|
|
450
461
|
key: wanted, name,
|
|
451
462
|
domains: Array.isArray(body?.matchDomains) ? body.matchDomains : [],
|
|
@@ -511,7 +522,8 @@ export function makeProfileService({
|
|
|
511
522
|
// The receipt reads off THESE, so it can say which framework and how many marketplaces, and
|
|
512
523
|
// whether each was chosen or defaulted, rather than restating what was typed.
|
|
513
524
|
framework: { path: framework.path, defaulted: framework.source === "default" },
|
|
514
|
-
|
|
525
|
+
// Never defaulted any more: a company created without marketplaces has none (resolvePlatforms).
|
|
526
|
+
marketplaces: { count: platforms.platforms.length, defaulted: false },
|
|
515
527
|
...(commitError ? { commitError: `created and LIVE, but the git commit failed (${commitError}) — the audit line records the gap` } : {}),
|
|
516
528
|
} };
|
|
517
529
|
}
|
|
@@ -621,14 +633,14 @@ export function makeProfileService({
|
|
|
621
633
|
const v = validateProfileEdit(errKey, overlayBody, ctx, { sparse: true });
|
|
622
634
|
let keyError = null;
|
|
623
635
|
if (!exists) { try { assertProfileKey(project); } catch (e) { keyError = String(e.message); } }
|
|
624
|
-
const errors = keyError ? [keyError, ...v.errors] : v.errors;
|
|
636
|
+
const errors = forBrowser(keyError ? [keyError, ...v.errors] : v.errors);
|
|
625
637
|
return { status: 200, json: { ok: errors.length === 0, errors, isNew: !exists } };
|
|
626
638
|
}
|
|
627
639
|
|
|
628
640
|
// save — the validated AUTO-COMMIT write (sparse validators; customer-only keys 400 here)
|
|
629
641
|
if (!exists) { try { assertProfileKey(project); } catch (e) { return { status: 400, json: { error: String(e.message) } }; } }
|
|
630
642
|
const v = validateProfileEdit(errKey, overlayBody, ctx, { sparse: true });
|
|
631
|
-
if (!v.ok) return { status: 400, json: { error: "validation_failed", errors: v.errors } };
|
|
643
|
+
if (!v.ok) return { status: 400, json: { error: "validation_failed", errors: forBrowser(v.errors) } };
|
|
632
644
|
// The write lands FIRST (atomic temp+rename); a git failure after it must never hide the mutation
|
|
633
645
|
// behind a 500 — the overlay is LIVE the moment it is renamed (every loadProjects door reads
|
|
634
646
|
// force-fresh), so the response says written:true with the commit error named, and the audit line
|
|
@@ -670,7 +682,7 @@ export function makeProfileService({
|
|
|
670
682
|
// a brand-new key must also be a safe slug (it becomes a filename); surface that in the dry run
|
|
671
683
|
let keyError = null;
|
|
672
684
|
if (!exists) { try { assertProfileKey(key); } catch (e) { keyError = String(e.message); } }
|
|
673
|
-
const errors = keyError ? [keyError, ...v.errors] : v.errors;
|
|
685
|
+
const errors = forBrowser(keyError ? [keyError, ...v.errors] : v.errors);
|
|
674
686
|
return { status: 200, json: { ok: errors.length === 0, errors, isNew: !exists } };
|
|
675
687
|
}
|
|
676
688
|
|
|
@@ -684,7 +696,7 @@ export function makeProfileService({
|
|
|
684
696
|
preserveUncontrolled(profile, exists ? profiles.get(key) : null);
|
|
685
697
|
const effective = preserveCodeOwned(profile, exists ? profiles.get(key) : null);
|
|
686
698
|
const v = validateProfileEdit(key, effective, contextPack);
|
|
687
|
-
if (!v.ok) return { status: 400, json: { error: "validation_failed", errors: v.errors } };
|
|
699
|
+
if (!v.ok) return { status: 400, json: { error: "validation_failed", errors: forBrowser(v.errors) } };
|
|
688
700
|
|
|
689
701
|
const { files } = writeProfile({ profileDir, key, profile: effective, contextPack });
|
|
690
702
|
// The write is atomic and LIVE the instant it renames — the next run freezes THIS file. An
|
package/driver/profiles.mjs
CHANGED
|
@@ -175,13 +175,22 @@ export function derivedBatchSize(profile) {
|
|
|
175
175
|
return Math.max(1, Math.floor(gridCellBudget(profile) / derivedFloor(profile)));
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// THE TERMS ONE GRID CALL MAY CARRY when each term runs `cellsPerTerm` cells: the cell budget divided by the
|
|
179
|
+
// cells the grid actually runs, never more than the profile's own figure. The profile's figure assumes its
|
|
180
|
+
// own stores plus the web cell, so it is too large for a grid that runs more per term: a matter frame's
|
|
181
|
+
// channels, or a closure over a store the matter chose for a company that picked no marketplaces, whose
|
|
182
|
+
// figure is the whole budget in terms. One oversized call truncates (the 224-cell note above).
|
|
183
|
+
export function gridBatchFor(profile, cellsPerTerm) {
|
|
184
|
+
return Math.max(1, Math.min(profile?.batchSize ?? 14, Math.floor(gridCellBudget(profile) / Math.max(1, cellsPerTerm))));
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
// The per-entry platform rules as DATA rather than as throws, so every door applies the identical checks
|
|
179
188
|
// and phrases the failure in its own register: a profile load DIES (a broken profile bricks every run
|
|
180
189
|
// under it), a job CLARIFIES (one request, answerable by asking). Same rules either way — a domain that is
|
|
181
190
|
// a foot-gun in a profile is the same foot-gun arriving on a job.
|
|
182
191
|
//
|
|
183
|
-
// SHAPE stays with the caller deliberately: a profile REQUIRES a
|
|
184
|
-
// platforms are optional and additive. Only the per-entry vocabulary is shared.
|
|
192
|
+
// SHAPE stays with the caller deliberately: a profile REQUIRES a platforms array (possibly empty), while a
|
|
193
|
+
// job's platforms are optional and additive. Only the per-entry vocabulary is shared.
|
|
185
194
|
export function platformEntryErrors(list, { label = "platforms" } = {}) {
|
|
186
195
|
const errs = [];
|
|
187
196
|
const seen = new Set();
|
|
@@ -518,9 +527,13 @@ function validateProfileShape(key, p, { sparse = false } = {}) {
|
|
|
518
527
|
}
|
|
519
528
|
// platforms is REQUIRED on a whole profile but OPTIONAL on an overlay; the foot-gun guards below run whenever
|
|
520
529
|
// it is present (a project that re-states its marketplaces must state them just as carefully).
|
|
530
|
+
//
|
|
531
|
+
// AN EMPTY LIST IS VALID, by the owner's ruling of 2026-09-23: a company may pick no marketplaces, and a
|
|
532
|
+
// new one starts that way. What stays required is the ARRAY — an absent key is not "none", it is a
|
|
533
|
+
// profile nobody finished (derivedFloor reads absent as the historical default of six).
|
|
521
534
|
if (!sparse || p.platforms != null) {
|
|
522
|
-
if (!Array.isArray(p.platforms) || !p.platforms.
|
|
523
|
-
die("platforms must be
|
|
535
|
+
if (!Array.isArray(p.platforms) || !p.platforms.every((x) => typeof x === "string" && x.trim()))
|
|
536
|
+
die("platforms must be an array of store-domain strings");
|
|
524
537
|
// a one-character authoring slip here bricks every run under the profile (the floor counts entries
|
|
525
538
|
// verbatim while the grid dedupes) — reject the known foot-guns at load time. Shared with the job
|
|
526
539
|
// doors via platformEntryErrors so a domain rule can never drift between where it is stored and
|
package/driver/publish/index.mjs
CHANGED
|
@@ -1071,12 +1071,23 @@ export async function publishReport({ runId, codename, reportMd, auditMd, findin
|
|
|
1071
1071
|
// the copy. The register-only LEVEL is retired, so no new run reaches this true; a run that already
|
|
1072
1072
|
// did still must, or its workbook would claim a sweep that never ran.
|
|
1073
1073
|
const registerOnly = isRegisterOnly(searchPolicy);
|
|
1074
|
+
// A RUN WHOSE COMPANY PICKED NO MARKETPLACES (the owner's ruling of 2026-09-23). Marketplaces were still
|
|
1075
|
+
// covered by the general web search, and by any stores the engine chose for the matter, so the lines that
|
|
1076
|
+
// describe the common-law layer say that instead of naming a marketplace search nobody picked. Read from
|
|
1077
|
+
// the frozen profile, which is the list the run actually searched; absent (a legacy run) reads as picked.
|
|
1078
|
+
let noMarketplacesPicked = false;
|
|
1079
|
+
try {
|
|
1080
|
+
const frozenProfile = runDir ? JSON.parse(readFileSync(driverDir(runDir, 'profile.json'), 'utf8')) : null;
|
|
1081
|
+
noMarketplacesPicked = Array.isArray(frozenProfile?.platforms) && frozenProfile.platforms.length === 0;
|
|
1082
|
+
} catch { /* no frozen profile — a legacy run keeps its wording */ }
|
|
1074
1083
|
|
|
1075
1084
|
// Jurisdiction line for the Summary — searched register scope + the common-law surface layer.
|
|
1076
1085
|
const regScope = (searchedJurisdictions && searchedJurisdictions.length) ? searchedJurisdictions.join('/') : 'Worldwide';
|
|
1077
1086
|
const jurisdiction = registerOnly
|
|
1078
1087
|
? `${regScope} (register only — no common-law / marketplace search)`
|
|
1079
|
-
:
|
|
1088
|
+
: noMarketplacesPicked
|
|
1089
|
+
? `${regScope} (register) + common-law (Western web / social, plus any stores chosen for this matter)`
|
|
1090
|
+
: `${regScope} (register) + common-law (Western web / marketplace / social)`;
|
|
1080
1091
|
|
|
1081
1092
|
// Common-law URL-join: a grid hit whose candidate URL resolves to a finding's own link PROVES that search
|
|
1082
1093
|
// term became a finding (rescues an anchor term whose finding wears a different name). Best-effort; absent
|
|
@@ -1224,6 +1235,7 @@ export async function publishReport({ runId, codename, reportMd, auditMd, findin
|
|
|
1224
1235
|
caseLawText: rdText(join(dirname(reportMd), 'case-law-findings.md')),
|
|
1225
1236
|
registerPlan: rdJson(driverDir(runBase, 'register-plan.json')),
|
|
1226
1237
|
laneDepthVerdicts,
|
|
1238
|
+
noMarketplacesPicked,
|
|
1227
1239
|
});
|
|
1228
1240
|
writeRO('search-depth.json', JSON.stringify(searchDepth, null, 2));
|
|
1229
1241
|
} catch { /* the depth record is additive — a publish never fails for want of it */ }
|
|
@@ -70,6 +70,9 @@ let CASE_LAW_BY_ORD = new Map(); // E5: ordinal → grounded case-law profile
|
|
|
70
70
|
// is full-country only), so suppressing there would take case-law off the page with nothing left
|
|
71
71
|
// saying so. Absence of a state is not a statement that nothing was found.
|
|
72
72
|
let COURT_DECISIONS = null;
|
|
73
|
+
// The company picked no marketplaces (search-depth counts.sweep): the use-check's nothing-found line names the
|
|
74
|
+
// general web and the stores chosen for the matter instead of a marketplace search. Set per render.
|
|
75
|
+
let NO_MARKETPLACES_PICKED = false;
|
|
73
76
|
let ENFORCER_SIGNALS = new Map(); // E6: registration uri (lowercase) → {aggression, oppositions, owner}
|
|
74
77
|
// WP-receipts W2 — per-render provider record-link origin + label, resolved by publish from the run's
|
|
75
78
|
// OWN _driver/receipts.json provider (never the currently-configured provider — a re-published archive
|
|
@@ -552,6 +555,9 @@ const useEvidence = (m) => [USE_EVIDENCE_LABEL[m?._status], USE_SOURCE_LABEL[m?.
|
|
|
552
555
|
// runs carry the old value forever and a fourth spelling of it would have to be accepted everywhere.
|
|
553
556
|
const USE_CHECK_NO_RESULT = 'perplexity_research — no result';
|
|
554
557
|
const USE_CHECK_NO_RESULT_CITE = 'Nothing found in the marketplaces searched.';
|
|
558
|
+
// The same "searched, nothing found" line for a run whose company picked no marketplaces (the owner's
|
|
559
|
+
// ruling of 2026-09-23): there was no marketplace search to name, and the general web still ran.
|
|
560
|
+
const USE_CHECK_NO_RESULT_CITE_NO_MARKETPLACES = 'Nothing found in the general web search or in any store chosen for this matter.';
|
|
555
561
|
// — MATCHED ON NORMALISED PUNCTUATION, NOT ONE SPELLING. The constant itself does not
|
|
556
562
|
// move (archived runs carry it forever, the validators name it), but the SEAT emitted a hyphen where
|
|
557
563
|
// the doctrine writes an em dash, and exact equality let the raw tool name through to a delivered
|
|
@@ -1222,7 +1228,9 @@ function whatWasSearchedSection(opts, coverage = [], findings = [], recordsByUri
|
|
|
1222
1228
|
}
|
|
1223
1229
|
const sw = c.sweep || {};
|
|
1224
1230
|
if (sw.spellings) rows.push(['Spellings searched', String(sw.spellings)]);
|
|
1225
|
-
if (sw.checks) rows.push(['Marketplace and web',
|
|
1231
|
+
if (sw.checks) rows.push(['Marketplace and web', sw.noMarketplacesPicked
|
|
1232
|
+
? `${sw.checks.toLocaleString('en-GB')} checks: the general web, plus any stores chosen for this matter.`
|
|
1233
|
+
: `${sw.checks.toLocaleString('en-GB')} checks on ${sw.platforms} platforms`]);
|
|
1226
1234
|
if (sw.reputation) rows.push(['Reputation and meaning', `${sw.reputation.toLocaleString('en-GB')} checks`]);
|
|
1227
1235
|
rows.push(['Local-script spellings', c.localScriptSearched ? 'Searched' : 'Not searched']);
|
|
1228
1236
|
// HOW DEEP THE LOCAL-LANGUAGE INVESTIGATION WENT, which is a different question from the row above it.
|
|
@@ -1803,7 +1811,9 @@ function fullDetail(f, card, recordsByUri = new Map()) {
|
|
|
1803
1811
|
const useStatus = useEvidence(f.meters?.use);
|
|
1804
1812
|
// D7 — the code-owned "searched, nothing found" sentinel becomes client words HERE, by exact
|
|
1805
1813
|
// equality against the one constant. Any other value is a source string and rides through untouched.
|
|
1806
|
-
const useSrc = isUseCheckNoResult(f.use_check?.source)
|
|
1814
|
+
const useSrc = isUseCheckNoResult(f.use_check?.source)
|
|
1815
|
+
? (NO_MARKETPLACES_PICKED ? USE_CHECK_NO_RESULT_CITE_NO_MARKETPLACES : USE_CHECK_NO_RESULT_CITE)
|
|
1816
|
+
: f.use_check?.source;
|
|
1807
1817
|
// NO EVIDENCE TAG ON AN EMPTY RESULT. The line read "Use checked. Marketplace search run — no result
|
|
1808
1818
|
// found. Evidence: inferred", and "inferred" beside "no result" reads as a contradiction: it qualifies
|
|
1809
1819
|
// how a FINDING was established, and there is no finding here. Nothing was found, and that is the
|
|
@@ -2511,6 +2521,7 @@ export function renderHtml(parsed, findings = [], coverage = [], opts = {}) {
|
|
|
2511
2521
|
SCOPE_WORLDWIDE = opts.scopeBasis === 'worldwide' ? true : null; // the plan's scope_basis; null ⇒ fall back to the ledger-prose sniff
|
|
2512
2522
|
CASE_LAW_BY_ORD = opts.caseLawByOrdinal instanceof Map ? opts.caseLawByOrdinal : new Map(); // T7 (E5)
|
|
2513
2523
|
COURT_DECISIONS = opts.searchDepth?.counts?.courtDecisions ?? null; // gates the card's case-law strand
|
|
2524
|
+
NO_MARKETPLACES_PICKED = opts.searchDepth?.counts?.sweep?.noMarketplacesPicked === true;
|
|
2514
2525
|
ENFORCER_SIGNALS = new Map((Array.isArray(opts.enforcerSignals) ? opts.enforcerSignals : []).map((e) => [String(e.uri ?? '').toLowerCase(), e])); // T7 (E6)
|
|
2515
2526
|
RECORD_ORIGIN = opts.recordOrigin ?? null; // WP-receipts W2
|
|
2516
2527
|
// — `null` and `` mean DIFFERENT things and the render must not collapse them. `` is an
|
|
@@ -275,7 +275,7 @@ export function localScriptSearched(registerPlan) {
|
|
|
275
275
|
*
|
|
276
276
|
* @returns {{schemaVersion: number, cleared: object, counts: object}}
|
|
277
277
|
*/
|
|
278
|
-
export function searchDepthRecord({ auditMd = "", recordIndex = {}, recordFileNames = [], bandRecordIds = null, commonLawGrid = null, caseLawText = "", registerPlan = null, laneDepthVerdicts = null } = {}) {
|
|
278
|
+
export function searchDepthRecord({ auditMd = "", recordIndex = {}, recordFileNames = [], bandRecordIds = null, commonLawGrid = null, caseLawText = "", registerPlan = null, laneDepthVerdicts = null, noMarketplacesPicked = false } = {}) {
|
|
279
279
|
// `recordFileNames: null` travels all the way to the page — see recordsByCountry. The default stays `[]`
|
|
280
280
|
// because that is "the caller said nothing", not "the store is absent"; only the publish path knows the
|
|
281
281
|
// difference and it is the one producer.
|
|
@@ -295,7 +295,9 @@ export function searchDepthRecord({ auditMd = "", recordIndex = {}, recordFileNa
|
|
|
295
295
|
counts: {
|
|
296
296
|
recordsByCountry: recordsByCountry(recordFileNames),
|
|
297
297
|
recordsRead: recordFileNames === null ? null : recordFileNames.length,
|
|
298
|
-
|
|
298
|
+
// The company picked no marketplaces: the report says the general web ran plus any stores chosen for
|
|
299
|
+
// the matter. Stamped only when true, so every other run's record is unchanged.
|
|
300
|
+
sweep: { ...sweepCounts(commonLawGrid, auditMd), ...(noMarketplacesPicked ? { noMarketplacesPicked: true } : {}) },
|
|
299
301
|
localScriptSearched: localScriptSearched(registerPlan),
|
|
300
302
|
courtDecisions: courtDecisionsState(caseLawText),
|
|
301
303
|
// `localScriptSearched` above answers whether the spellings were searched; this answers how deep
|
package/driver/run-economics.mjs
CHANGED
|
@@ -106,7 +106,7 @@ import { runLog, note } from "./log.mjs";
|
|
|
106
106
|
import { writeRunStatus } from "./progress.mjs";
|
|
107
107
|
// tokens.mjs imports this module too (isCodeSide, stampRunEconomics). The cycle is safe because each side
|
|
108
108
|
// reads the other's bindings only inside functions, never while the module is loading.
|
|
109
|
-
import { isAttemptRow } from "./tokens.mjs";
|
|
109
|
+
import { isAttemptRow, modelKey } from "./tokens.mjs";
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* The provider's separately-priced token kinds, in the driver's own `usage` vocabulary (gateway.mjs /
|
|
@@ -185,23 +185,12 @@ function billingKeyOf(rec) {
|
|
|
185
185
|
// is rather than dragged into "unknown" beside genuinely unstamped legacy rows.
|
|
186
186
|
const engine = isCodeSide(rec) ? "code" : String(rec.engine ?? "unknown");
|
|
187
187
|
const authMode = isCodeSide(rec) ? "not-provider-billed" : String(rec.authMode ?? "unknown");
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
// model
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
// is modelKey's own, a non-empty string `modelUsed`, and not `modelUsed == null`: under that looser test
|
|
195
|
-
// a row stamped `modelUsed: ""` keyed its bucket as the empty string while the rollup keyed the same
|
|
196
|
-
// turn `<engine>/no-model-reported`. Two copies of one rule drifting apart is how the census and the
|
|
197
|
-
// rollup came to disagree about what an attempt is, so the tests hold these two copies to each other on
|
|
198
|
-
// the rows the engine writes. They still part on a row no writer produces: no model and no engine, or
|
|
199
|
-
// engine `anthropic-agent`. This key names the missing model there, while modelKey resolves the absent
|
|
200
|
-
// model through the catalog before it asks whether one exists, and buckets the row as `undefined`.
|
|
201
|
-
const stamped = typeof rec.modelUsed === "string" && rec.modelUsed;
|
|
202
|
-
const model = !stamped && typeof rec.model !== "string"
|
|
203
|
-
? `${typeof rec.engine === "string" && rec.engine ? rec.engine : "unknown"}/no-model-reported`
|
|
204
|
-
: String(rec.modelUsed ?? rec.model ?? "unknown");
|
|
188
|
+
// THE MODEL IS NAMED BY THE TOKEN ROLLUP'S OWN RULE (modelKey in tokens.mjs), not a copy of it. A copy
|
|
189
|
+
// lived here while tokens.mjs did not export the rule, and the two drifted: a native-language turn that
|
|
190
|
+
// named its model was billed under the raw served id while the rollup keyed it `anthropic/unstamped:<id>`,
|
|
191
|
+
// so one turn had two names in the same run's figures. A code-side row comes back as its own name
|
|
192
|
+
// (`code:<step>`), which says no model ran.
|
|
193
|
+
const model = modelKey(rec);
|
|
205
194
|
return { engine, authMode, model, key: `${engine}|${authMode}|${model}` };
|
|
206
195
|
}
|
|
207
196
|
|
package/driver/stages.mjs
CHANGED
|
@@ -2527,7 +2527,7 @@ export const STAGES = {
|
|
|
2527
2527
|
},
|
|
2528
2528
|
"adopt-or-override each placement by engaging its reason, and the `### Disagreement resolutions` rows (one per surfaced disagreement and per borderline:true, each ADOPTED/OVERRODE in writing)": {
|
|
2529
2529
|
class: "judgment", tokens: ["registerdigest_adjudication_invalid", "registerdigest_adjudication_incomplete"],
|
|
2530
|
-
why: "Answering the promotion question the other way, in writing, against a reason another stage authored. #850 keeps it J. The row's SUBJECT is handed over as data (the driver appends the PLACEMENT RULINGS TAIL block, pipeline.mjs
|
|
2530
|
+
why: "Answering the promotion question the other way, in writing, against a reason another stage authored. #850 keeps it J. The row's SUBJECT is handed over as data (the driver appends the PLACEMENT RULINGS TAIL block, digestDispatchExtra() in pipeline.mjs), so nothing here is a fetch.",
|
|
2531
2531
|
},
|
|
2532
2532
|
// ── REWRITTEN, NEVER DELETED (the ruling) — AND THE ROW THAT COST THIS CONVERSION A DESIGN ──
|
|
2533
2533
|
//
|
package/driver/suite-census.json
CHANGED
|
@@ -142,8 +142,8 @@
|
|
|
142
142
|
"todos": 0
|
|
143
143
|
},
|
|
144
144
|
"a-check-that-could-not-look-does-not-report-a-drift.test.mjs": {
|
|
145
|
-
"tests":
|
|
146
|
-
"asserts":
|
|
145
|
+
"tests": 13,
|
|
146
|
+
"asserts": 37,
|
|
147
147
|
"skips": 0,
|
|
148
148
|
"todos": 0
|
|
149
149
|
},
|
|
@@ -220,8 +220,14 @@
|
|
|
220
220
|
"todos": 0
|
|
221
221
|
},
|
|
222
222
|
"a-company-created-in-the-browser-is-a-whole-company.test.mjs": {
|
|
223
|
-
"tests":
|
|
224
|
-
"asserts":
|
|
223
|
+
"tests": 14,
|
|
224
|
+
"asserts": 75,
|
|
225
|
+
"skips": 0,
|
|
226
|
+
"todos": 0
|
|
227
|
+
},
|
|
228
|
+
"a-company-with-no-marketplaces-is-described-as-searched.test.mjs": {
|
|
229
|
+
"tests": 2,
|
|
230
|
+
"asserts": 4,
|
|
225
231
|
"skips": 0,
|
|
226
232
|
"todos": 0
|
|
227
233
|
},
|
|
@@ -1077,6 +1083,12 @@
|
|
|
1077
1083
|
"skips": 0,
|
|
1078
1084
|
"todos": 0
|
|
1079
1085
|
},
|
|
1086
|
+
"a-state-change-from-another-site-is-refused.test.mjs": {
|
|
1087
|
+
"tests": 15,
|
|
1088
|
+
"asserts": 70,
|
|
1089
|
+
"skips": 0,
|
|
1090
|
+
"todos": 0
|
|
1091
|
+
},
|
|
1080
1092
|
"a-stop-is-read-once-more-before-anything-is-published.test.mjs": {
|
|
1081
1093
|
"tests": 4,
|
|
1082
1094
|
"asserts": 12,
|
|
@@ -1197,6 +1209,12 @@
|
|
|
1197
1209
|
"skips": 2,
|
|
1198
1210
|
"todos": 0
|
|
1199
1211
|
},
|
|
1212
|
+
"a-version-npm-is-still-validating-gets-its-entry-once-served.test.mjs": {
|
|
1213
|
+
"tests": 14,
|
|
1214
|
+
"asserts": 47,
|
|
1215
|
+
"skips": 0,
|
|
1216
|
+
"todos": 0
|
|
1217
|
+
},
|
|
1200
1218
|
"a-visible-line-is-written-for-the-client.test.mjs": {
|
|
1201
1219
|
"tests": 11,
|
|
1202
1220
|
"asserts": 24,
|
|
@@ -1708,8 +1726,8 @@
|
|
|
1708
1726
|
"todos": 0
|
|
1709
1727
|
},
|
|
1710
1728
|
"client-door.test.mjs": {
|
|
1711
|
-
"tests":
|
|
1712
|
-
"asserts":
|
|
1729
|
+
"tests": 40,
|
|
1730
|
+
"asserts": 173,
|
|
1713
1731
|
"skips": 1,
|
|
1714
1732
|
"todos": 0
|
|
1715
1733
|
},
|
|
@@ -1725,6 +1743,12 @@
|
|
|
1725
1743
|
"skips": 0,
|
|
1726
1744
|
"todos": 0
|
|
1727
1745
|
},
|
|
1746
|
+
"closing-the-terminal-stops-what-start-started.test.mjs": {
|
|
1747
|
+
"tests": 2,
|
|
1748
|
+
"asserts": 6,
|
|
1749
|
+
"skips": 1,
|
|
1750
|
+
"todos": 0
|
|
1751
|
+
},
|
|
1728
1752
|
"cn-scope-honesty.test.mjs": {
|
|
1729
1753
|
"tests": 22,
|
|
1730
1754
|
"asserts": 96,
|
|
@@ -3442,8 +3466,8 @@
|
|
|
3442
3466
|
"todos": 0
|
|
3443
3467
|
},
|
|
3444
3468
|
"pipeline.mock.test.mjs": {
|
|
3445
|
-
"tests":
|
|
3446
|
-
"asserts":
|
|
3469
|
+
"tests": 101,
|
|
3470
|
+
"asserts": 842,
|
|
3447
3471
|
"skips": 0,
|
|
3448
3472
|
"todos": 0
|
|
3449
3473
|
},
|
|
@@ -3580,8 +3604,8 @@
|
|
|
3580
3604
|
"todos": 0
|
|
3581
3605
|
},
|
|
3582
3606
|
"portal-service.test.mjs": {
|
|
3583
|
-
"tests":
|
|
3584
|
-
"asserts":
|
|
3607
|
+
"tests": 132,
|
|
3608
|
+
"asserts": 715,
|
|
3585
3609
|
"skips": 1,
|
|
3586
3610
|
"todos": 0
|
|
3587
3611
|
},
|
|
@@ -3688,8 +3712,8 @@
|
|
|
3688
3712
|
"todos": 0
|
|
3689
3713
|
},
|
|
3690
3714
|
"profiles.test.mjs": {
|
|
3691
|
-
"tests":
|
|
3692
|
-
"asserts":
|
|
3715
|
+
"tests": 49,
|
|
3716
|
+
"asserts": 272,
|
|
3693
3717
|
"skips": 0,
|
|
3694
3718
|
"todos": 0
|
|
3695
3719
|
},
|
|
@@ -4078,8 +4102,8 @@
|
|
|
4078
4102
|
"todos": 0
|
|
4079
4103
|
},
|
|
4080
4104
|
"release-pipeline.test.mjs": {
|
|
4081
|
-
"tests":
|
|
4082
|
-
"asserts":
|
|
4105
|
+
"tests": 122,
|
|
4106
|
+
"asserts": 462,
|
|
4083
4107
|
"skips": 0,
|
|
4084
4108
|
"todos": 0
|
|
4085
4109
|
},
|
|
@@ -4102,8 +4126,8 @@
|
|
|
4102
4126
|
"todos": 0
|
|
4103
4127
|
},
|
|
4104
4128
|
"render.test.mjs": {
|
|
4105
|
-
"tests":
|
|
4106
|
-
"asserts":
|
|
4129
|
+
"tests": 163,
|
|
4130
|
+
"asserts": 681,
|
|
4107
4131
|
"skips": 0,
|
|
4108
4132
|
"todos": 0
|
|
4109
4133
|
},
|
|
@@ -4276,8 +4300,8 @@
|
|
|
4276
4300
|
"todos": 0
|
|
4277
4301
|
},
|
|
4278
4302
|
"run-economics.test.mjs": {
|
|
4279
|
-
"tests":
|
|
4280
|
-
"asserts":
|
|
4303
|
+
"tests": 26,
|
|
4304
|
+
"asserts": 161,
|
|
4281
4305
|
"skips": 0,
|
|
4282
4306
|
"todos": 0
|
|
4283
4307
|
},
|
|
@@ -4379,7 +4403,7 @@
|
|
|
4379
4403
|
},
|
|
4380
4404
|
"runner.knockout-e2e.test.mjs": {
|
|
4381
4405
|
"tests": 3,
|
|
4382
|
-
"asserts":
|
|
4406
|
+
"asserts": 118,
|
|
4383
4407
|
"skips": 0,
|
|
4384
4408
|
"todos": 0
|
|
4385
4409
|
},
|
|
@@ -5380,8 +5404,8 @@
|
|
|
5380
5404
|
"todos": 0
|
|
5381
5405
|
},
|
|
5382
5406
|
"the-people-page-can-change-and-remove-a-person.test.mjs": {
|
|
5383
|
-
"tests":
|
|
5384
|
-
"asserts":
|
|
5407
|
+
"tests": 17,
|
|
5408
|
+
"asserts": 85,
|
|
5385
5409
|
"skips": 0,
|
|
5386
5410
|
"todos": 0
|
|
5387
5411
|
},
|
|
@@ -5483,7 +5507,7 @@
|
|
|
5483
5507
|
},
|
|
5484
5508
|
"the-record-half-of-an-add-must-reach-git.test.mjs": {
|
|
5485
5509
|
"tests": 11,
|
|
5486
|
-
"asserts":
|
|
5510
|
+
"asserts": 40,
|
|
5487
5511
|
"skips": 0,
|
|
5488
5512
|
"todos": 0
|
|
5489
5513
|
},
|
|
@@ -6134,6 +6158,12 @@
|
|
|
6134
6158
|
"skips": 0,
|
|
6135
6159
|
"todos": 0
|
|
6136
6160
|
},
|
|
6161
|
+
"a-tool-the-listing-hides-cannot-be-called-by-name.test.mjs": {
|
|
6162
|
+
"tests": 4,
|
|
6163
|
+
"asserts": 10,
|
|
6164
|
+
"skips": 0,
|
|
6165
|
+
"todos": 0
|
|
6166
|
+
},
|
|
6137
6167
|
"account-audit-chain.test.mjs": {
|
|
6138
6168
|
"tests": 10,
|
|
6139
6169
|
"asserts": 48,
|
|
@@ -6267,8 +6297,8 @@
|
|
|
6267
6297
|
"todos": 0
|
|
6268
6298
|
},
|
|
6269
6299
|
"http-handler.test.mjs": {
|
|
6270
|
-
"tests":
|
|
6271
|
-
"asserts":
|
|
6300
|
+
"tests": 23,
|
|
6301
|
+
"asserts": 58,
|
|
6272
6302
|
"skips": 0,
|
|
6273
6303
|
"todos": 0
|
|
6274
6304
|
},
|
|
@@ -6316,7 +6346,7 @@
|
|
|
6316
6346
|
},
|
|
6317
6347
|
"ops-token.test.mjs": {
|
|
6318
6348
|
"tests": 8,
|
|
6319
|
-
"asserts":
|
|
6349
|
+
"asserts": 41,
|
|
6320
6350
|
"skips": 0,
|
|
6321
6351
|
"todos": 0
|
|
6322
6352
|
},
|
|
@@ -6345,8 +6375,8 @@
|
|
|
6345
6375
|
"todos": 0
|
|
6346
6376
|
},
|
|
6347
6377
|
"portal-trigger-wire.test.mjs": {
|
|
6348
|
-
"tests":
|
|
6349
|
-
"asserts":
|
|
6378
|
+
"tests": 2,
|
|
6379
|
+
"asserts": 11,
|
|
6350
6380
|
"skips": 0,
|
|
6351
6381
|
"todos": 0
|
|
6352
6382
|
},
|
|
@@ -6375,8 +6405,8 @@
|
|
|
6375
6405
|
"todos": 0
|
|
6376
6406
|
},
|
|
6377
6407
|
"scope.test.mjs": {
|
|
6378
|
-
"tests":
|
|
6379
|
-
"asserts":
|
|
6408
|
+
"tests": 30,
|
|
6409
|
+
"asserts": 121,
|
|
6380
6410
|
"skips": 0,
|
|
6381
6411
|
"todos": 0
|
|
6382
6412
|
},
|
|
@@ -6734,8 +6764,8 @@
|
|
|
6734
6764
|
"todos": 0
|
|
6735
6765
|
},
|
|
6736
6766
|
"profileFields.test.ts": {
|
|
6737
|
-
"tests":
|
|
6738
|
-
"asserts":
|
|
6767
|
+
"tests": 55,
|
|
6768
|
+
"asserts": 176,
|
|
6739
6769
|
"skips": 0,
|
|
6740
6770
|
"todos": 0
|
|
6741
6771
|
},
|
package/driver/tokens.mjs
CHANGED
|
@@ -68,27 +68,35 @@ function emptyAcc() {
|
|
|
68
68
|
* turn (`engine.resolveModelId ?? resolveModel`) and stamps the answer on the row as `modelUsed`. So
|
|
69
69
|
* this reads the stamp rather than re-deriving it — one resolution, at the point that knows.
|
|
70
70
|
*/
|
|
71
|
-
function modelKey(rec) {
|
|
71
|
+
export function modelKey(rec) {
|
|
72
72
|
if (typeof rec.modelUsed === "string" && rec.modelUsed) return rec.modelUsed;
|
|
73
73
|
|
|
74
74
|
// NO STAMP. Two different situations, and guessing the same way for both is what caused the bug.
|
|
75
75
|
const engine = typeof rec.engine === "string" ? rec.engine : "";
|
|
76
76
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
77
|
+
// A TURN THAT NAMED NO MODEL, a native-language row recording `modelActual: null` (see isAttemptRow),
|
|
78
|
+
// has no id at all. Its tokens still account, under a key that says the model is missing rather than
|
|
79
|
+
// one built from the absent field. Asked FIRST, so no row keys as `undefined` because the catalog was
|
|
80
|
+
// handed nothing to resolve.
|
|
81
|
+
if (typeof rec.model !== "string") return `${engine || "unknown"}/no-model-reported`;
|
|
82
|
+
|
|
83
|
+
// Rows predating the stamp are Anthropic — that was the only engine when they were written — and
|
|
84
|
+
// resolving keeps every historical rollup keyed exactly as it was.
|
|
85
|
+
//
|
|
86
|
+
// A NATIVE-LANGUAGE ROW IS STAMPED WITH ITS VENDOR, NOT AN ENGINE (jxBillingStamp in jx-lanes.mjs), and
|
|
87
|
+
// its `model` is the id that served the turn. An Anthropic one is normalised through the catalog like
|
|
88
|
+
// any served Claude id, so it keys as `anthropic/claude-haiku-4-5`, apart from a stage's
|
|
89
|
+
// `anthropic/claude-haiku` — the split ruled on and accepted with recording the tier. It used to fall
|
|
90
|
+
// to the branch below and key as `anthropic/unstamped:<id>`, a name for a missing stamp on a row that
|
|
91
|
+
// carries one. An OpenAI one keys as its served id, which is what an openai-agent stage row carries.
|
|
92
|
+
if (!engine || engine === "anthropic-agent" || engine === "anthropic") return resolveModel(rec.model);
|
|
93
|
+
if (engine === "openai") return rec.model;
|
|
81
94
|
|
|
82
95
|
// Any OTHER engine with no stamp. The tier is not a model id and this catalog is not that engine's,
|
|
83
96
|
// so there is no honest id to key under. Name the gap instead: the tokens still account (dropping
|
|
84
97
|
// them would break byModel summing to total, and an invisible gap is the failure this file already
|
|
85
98
|
// fixed once for byEngine), and the key says what is missing rather than asserting an Anthropic
|
|
86
99
|
// model produced them.
|
|
87
|
-
//
|
|
88
|
-
// A TURN THAT NAMED NO MODEL, a native-language row recording `modelActual: null` (see isAttemptRow),
|
|
89
|
-
// has no id at all. Its tokens still account, under a key that says the model is missing rather than
|
|
90
|
-
// one built from the absent field. Such a row always carries its vendor's stamp, so it reaches here.
|
|
91
|
-
if (typeof rec.model !== "string") return `${engine || "unknown"}/no-model-reported`;
|
|
92
100
|
return `${engine}/unstamped:${rec.model}`;
|
|
93
101
|
}
|
|
94
102
|
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/lib/audit.mjs
CHANGED
|
@@ -50,7 +50,7 @@ export function summarize(body) {
|
|
|
50
50
|
*/
|
|
51
51
|
export const UNNAMED_DOOR = "unnamed";
|
|
52
52
|
|
|
53
|
-
export function appendAudit({ email, sub, body, status, transport, door, path = DEFAULT_AUDIT_PATH }) {
|
|
53
|
+
export function appendAudit({ email, sub, kind = null, namesVerbs = null, body, status, transport, door, path = DEFAULT_AUDIT_PATH }) {
|
|
54
54
|
// `sub` = the inner-token PRINCIPAL (ops-token issuance, INSTALL.md §8) — distinguishes
|
|
55
55
|
// two automations sharing a transport identity. null for internal/user sessions without a sub claim.
|
|
56
56
|
//
|
|
@@ -69,7 +69,14 @@ export function appendAudit({ email, sub, body, status, transport, door, path =
|
|
|
69
69
|
// WRITTEN ONLY WHEN GIVEN, the same rule as `transport` and for the same reason: the existing log
|
|
70
70
|
// shape must not move for records that have no answer to this. A door that does not name itself is a
|
|
71
71
|
// record with no `door` key, not a record claiming to be from nowhere.
|
|
72
|
-
|
|
72
|
+
//
|
|
73
|
+
// `kind` is the principal's kind (ops, user, account, internal), and `namesVerbs` says, for an ops token
|
|
74
|
+
// only, whether it names its write verbs. An ops token that names none is being retired: re-issued with
|
|
75
|
+
// verbs, then refused once this log shows none in use. So the log must be able to count them. Written
|
|
76
|
+
// only when given, by the same rule as `transport`: a caller that never knew writes no claim.
|
|
77
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), email: email ?? null, sub: sub ?? null,
|
|
78
|
+
...(kind ? { kind } : {}), ...(typeof namesVerbs === "boolean" ? { namesVerbs } : {}),
|
|
79
|
+
...summarize(body), status: status ?? null, ...(transport ? { transport } : {}), door: door || UNNAMED_DOOR }) + "\n";
|
|
73
80
|
try { mkdirSync(dirname(path), { recursive: true }); appendFileSync(path, line); } catch { /* best-effort */ }
|
|
74
81
|
}
|
|
75
82
|
|