clearotron 0.4.0 → 0.4.1-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/THIRD-PARTY-NOTICES.md +2 -2
- package/build-info.json +2 -2
- package/docs/SECURITY-OWASP.md +21 -13
- package/docs/SECURITY.md +20 -14
- package/docs/architecture/04-configuration-reference.md +3 -2
- package/docs/architecture/05-config-governance.md +1 -1
- package/docs/architecture/06-operations-runbook.md +1 -1
- package/docs/architecture/09-security-and-data.md +16 -11
- package/driver/CHANGELOG.md +18 -0
- package/driver/binding-layers.mjs +28 -6
- package/driver/citation-census.json +3 -3
- package/driver/common-law-coverage-status.mjs +52 -0
- package/driver/common-law-receipts.mjs +1 -1
- package/driver/connotation-search.mjs +5 -5
- package/driver/contract-dictation-registry.mjs +1 -1
- package/driver/contract-e3-backlog.mjs +1 -1
- package/driver/contract-vocabulary.mjs +18 -18
- package/driver/degraded-parts.mjs +10 -3
- package/driver/dispatch-record.mjs +19 -0
- package/driver/doubt-closure-tool.mjs +2 -2
- package/driver/driver.config.mjs +39 -6
- package/driver/engagement-receipt.mjs +1 -1
- package/driver/engine/CONTRACT.md +41 -1
- package/driver/engine/anthropic-agent.mjs +56 -4
- package/driver/engine/mcp/codex-config.mjs +1 -1
- package/driver/engine/mcp/coverage-server.mjs +1 -1
- package/driver/engine/mcp/gather-config.mjs +1 -1
- package/driver/engine/mcp/recording-server.mjs +31 -4
- package/driver/engine/mcp/stdio-server.mjs +45 -7
- package/driver/engine/openai-agent.mjs +100 -17
- package/driver/engine/session-record.mjs +223 -0
- package/driver/enqueue-schema.mjs +2 -2
- package/driver/findings-model.mjs +3 -3
- package/driver/form-neighbourhood.mjs +7 -7
- package/driver/frame-diff-model.mjs +3 -3
- package/driver/framework.mjs +22 -0
- package/driver/gateway.mjs +94 -12
- package/driver/named-band.mjs +2 -0
- package/driver/package.json +1 -1
- package/driver/phase0.mjs +13 -2
- package/driver/pipeline-knockout.mjs +1 -1
- package/driver/pipeline.mjs +46 -33
- package/driver/portal-families.mjs +3 -3
- package/driver/portal-service.mjs +1 -1
- package/driver/predelivery-lint.mjs +19 -19
- package/driver/publish/index.mjs +1 -1
- package/driver/publish/knockout.mjs +5 -4
- package/driver/publish/office-record-links.mjs +17 -2
- package/driver/publish/publish-inputs.mjs +1 -1
- package/driver/publish/render-knockout.mjs +17 -5
- package/driver/publish/render.mjs +4 -2
- package/driver/record-fold.mjs +177 -0
- package/driver/register-count.mjs +18 -1
- package/driver/register-plan.mjs +71 -17
- package/driver/register-records.mjs +7 -0
- package/driver/registry-fidelity.mjs +2 -2
- package/driver/repairs.mjs +1 -1
- package/driver/report-card-record.mjs +4 -4
- package/driver/scope-facts.mjs +11 -3
- package/driver/search-policy.mjs +20 -1
- package/driver/skills/clearance-common-law/SKILL.md +22 -22
- package/driver/skills/clearance-register/digest.md +3 -3
- package/driver/skills/clearance-register/register-recipes.md +11 -11
- package/driver/skills/clearance-register/status-rules.md +1 -1
- package/driver/skills/clearance-register/unit.md +1 -1
- package/driver/skills/clearance-search/firm-wide-reasoning.md +3 -2
- package/driver/skills/clearance-search/phase2-execution.md +6 -5
- package/driver/skills/clearance-search/synthesis-rules.md +4 -4
- package/driver/skills/clearance-search/worked-examples.md +39 -39
- package/driver/skills/clearance-variants/SKILL.md +16 -16
- package/driver/skills/frame-diff/SKILL.md +2 -2
- package/driver/stage-context.mjs +27 -1
- package/driver/stages.mjs +12 -12
- package/driver/suite-census.json +175 -31
- package/driver/synthesis-record.mjs +5 -0
- package/driver/unit-inventory.mjs +17 -28
- package/driver/verify.mjs +6 -6
- package/mcp-server/CHANGELOG.md +4 -0
- package/mcp-server/lib/evidence.mjs +8 -8
- package/mcp-server/lib/knockout.mjs +1 -1
- package/mcp-server/package.json +2 -2
- package/package.json +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/count.mjs +4 -1
- package/providers/_shared/enumerate.mjs +61 -22
- package/providers/_shared/execute-plan.mjs +2 -0
- package/providers/corsearch/src/capabilities.js +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
- package/providers/oauth-mcp-bridge/package.json +2 -2
- package/providers/signa/src/capabilities.js +17 -2
- package/providers/signa/src/core.js +234 -37
- package/scripts/band-shape-probe.mjs +2 -2
- package/scripts/deprecate-below.mjs +28 -4
- package/scripts/e2e-first-time.mjs +16 -1
- package/scripts/freeze-example-run.mjs +2 -2
- package/shared/scope.mjs +18 -2
|
@@ -80,7 +80,7 @@ export const VOCABULARY = [
|
|
|
80
80
|
{ token: "connotation_search_missing", stages: ["common-law"], site: "driver/verify.mjs" },
|
|
81
81
|
{ token: "receipts_short", stages: ["common-law"], site: "driver/verify.mjs" },
|
|
82
82
|
{ token: "half_path_unrecognized", stages: ["common-law-half"], site: "driver/verify.mjs:395" },
|
|
83
|
-
{ token: "no_coverage_status_row", stages: [...CL, "register-digest"], site: "driver/verify.mjs:452, 1416" },
|
|
83
|
+
{ token: "no_coverage_status_row", stages: [...CL, "register-digest"], site: "driver/verify.mjs:452 commonLawMeaningSeat, 1416" },
|
|
84
84
|
// B — `connotation_form_unparseable` and `connotation_form_untouched` are GONE: they
|
|
85
85
|
// were states only a hand-authored document could be in, and the form path is deleted (ruling
|
|
86
86
|
// 2026-08-17). Their subject matter is carried by the call tokens under the D1 family row below.
|
|
@@ -139,7 +139,7 @@ export const VOCABULARY = [
|
|
|
139
139
|
{ token: "coverage_no_status", stages: ["register-digest"], site: "driver/verify.mjs" },
|
|
140
140
|
{ token: "coverage_form_missing", stages: ["register-digest"], site: "driver/verify.mjs" },
|
|
141
141
|
{ token: "coverage_form_empty", stages: ["register-digest"], site: "driver/verify.mjs" },
|
|
142
|
-
{ token: "coverage_status_offenum", stages: ["register-digest"], site: "driver/verify.mjs
|
|
142
|
+
{ token: "coverage_status_offenum", stages: ["register-digest"], site: "driver/verify.mjs validators.registerFindings" },
|
|
143
143
|
{ token: "coverage_deferred_unaccounted", stages: ["register-digest"], site: "driver/verify.mjs coverageFormFail", family: "driver/register-plan.mjs PROVIDER_HARD_ERROR_PREFIX — token on a data row", dynamic: "D5" },
|
|
144
144
|
{ token: "coverage_clean_unexecuted", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs validatePlanFeasibility", dynamic: "D6" },
|
|
145
145
|
{ token: "coverage_clean_skipped", stages: ["register-digest"], site: "driver/verify.mjs, the matterContext validator", family: "driver/register-plan.mjs searchedJurisdictionsFromPlan", dynamic: "D6" },
|
|
@@ -150,7 +150,7 @@ export const VOCABULARY = [
|
|
|
150
150
|
{ token: "coverage_axis_", stages: ["register-digest"], site: "driver/verify.mjs", family: "driver/coverage-ledger.mjs", dynamic: "D4" },
|
|
151
151
|
{ token: "coverage_status_invalid", stages: ["register-digest"], site: "driver/verify.mjs", family: "driver/coverage-ledger.mjs", dynamic: "D4" },
|
|
152
152
|
{ token: "coverage_classes_invalid", stages: ["register-digest"], site: "driver/verify.mjs", family: "driver/coverage-ledger.mjs", dynamic: "D4" },
|
|
153
|
-
{ token: "plan_execution_unreadable", stages: ["register-digest", "narrative-refutation"], site: "driver/verify.mjs:1496, 1712" },
|
|
153
|
+
{ token: "plan_execution_unreadable", stages: ["register-digest", "narrative-refutation"], site: "driver/verify.mjs:1496 rowLabel, 1712" },
|
|
154
154
|
|
|
155
155
|
// ── matter-frame / clearance-variants / blind-frame / frame-diff ──────────────────────────────────────
|
|
156
156
|
{ token: "stagecontracts_invalid", stages: ["matter-frame", "clearance-variants", "placement-inquiry", "case-law"], site: "driver/verify.mjs:1539 coverageEntryList, 1170, 1366, 1677" },
|
|
@@ -197,7 +197,7 @@ export const VOCABULARY = [
|
|
|
197
197
|
// verify.mjs reaches the module, so a family that could grow a member no row covers must be readable.
|
|
198
198
|
{ token: "matterframe_", stages: ["matter-frame"], site: "driver/matter-frame-record.mjs acceptMatterFrame", family: "driver/matter-frame-record.mjs", dynamic: "D3" },
|
|
199
199
|
{ token: "framediff_model_missing", stages: ["frame-diff"], site: "driver/verify.mjs" },
|
|
200
|
-
{ token: "framediff_", stages: ["frame-diff"], site: "driver/verify.mjs:1224 → 742", family: "driver/frame-diff-model.mjs", dynamic: "D3" },
|
|
200
|
+
{ token: "framediff_", stages: ["frame-diff"], site: "driver/verify.mjs:1224 checkFindingsSibling → 742", family: "driver/frame-diff-model.mjs", dynamic: "D3" },
|
|
201
201
|
|
|
202
202
|
// ── register-unit ──────────────────────────────────────────────────────────────────────────────────
|
|
203
203
|
{ token: "declared_not_executed", stages: ["register-unit"], site: "driver/verify.mjs" },
|
|
@@ -206,17 +206,17 @@ export const VOCABULARY = [
|
|
|
206
206
|
{ token: "named_band_invalid", stages: ["register-unit"], site: "driver/verify.mjs" },
|
|
207
207
|
{ token: "named_band_collapsed", stages: ["register-unit"], site: "driver/verify.mjs" },
|
|
208
208
|
{ token: "band_block_unplanned", stages: ["register-unit"], site: "driver/verify.mjs" },
|
|
209
|
-
{ token: "named_band_", stages: ["register-unit"], site: "driver/verify.mjs
|
|
209
|
+
{ token: "named_band_", stages: ["register-unit"], site: "driver/verify.mjs validators.registerUnit", family: "driver/named-band.mjs", dynamic: "D3" },
|
|
210
210
|
|
|
211
211
|
// ── placement-inquiry ──────────────────────────────────────────────────────────────────────────────
|
|
212
|
-
{ token: "placementmodel_missing", stages: ["placement-inquiry"], site: "driver/verify.mjs:1352, 1379" },
|
|
212
|
+
{ token: "placementmodel_missing", stages: ["placement-inquiry"], site: "driver/verify.mjs:1352 connotationViolations, 1379" },
|
|
213
213
|
{ token: "placement_unjudged", stages: ["placement-inquiry"], site: "driver/verify.mjs placementAccountVerdict" },
|
|
214
214
|
{ token: "placement_register_unrendered", stages: ["placement-inquiry"], site: "driver/verify.mjs placementAccountVerdict" },
|
|
215
215
|
{ token: "placement_form_unreadable", stages: ["placement-inquiry"], site: "driver/verify.mjs placementAccountVerdict" },
|
|
216
|
-
{ token: "placement", stages: ["placement-inquiry"], site: "driver/verify.mjs:1352 → 742", family: "driver/placement-model.mjs (`placement_*` and `placements_*`)", dynamic: "D3" },
|
|
216
|
+
{ token: "placement", stages: ["placement-inquiry"], site: "driver/verify.mjs:1352 connotationViolations → 742", family: "driver/placement-model.mjs (`placement_*` and `placements_*`)", dynamic: "D3" },
|
|
217
217
|
|
|
218
218
|
// ── case-law / narrative-refutation ────────────────────────────────────────────────────────────────
|
|
219
|
-
{ token: "caselaw_ledger_missing", stages: ["case-law"], site: "driver/verify.mjs
|
|
219
|
+
{ token: "caselaw_ledger_missing", stages: ["case-law"], site: "driver/verify.mjs validators.caseLaw" },
|
|
220
220
|
{ token: "caselaw_ledger_unparseable", stages: ["case-law"], site: "driver/verify.mjs" },
|
|
221
221
|
{ token: "caselaw_ledger", stages: ["case-law"], site: "driver/verify.mjs:2425 caseLawLedgerFail", family: "driver/case-law-ledger.mjs caseLawLedgerFail (census reasons)", dynamic: "D8" },
|
|
222
222
|
{ token: "no_verdict_line", stages: ["narrative-refutation"], site: "driver/verify.mjs" },
|
|
@@ -234,8 +234,8 @@ export const VOCABULARY = [
|
|
|
234
234
|
/**
|
|
235
235
|
* WRAPPERS — the emitted failure string is not always the bare token.
|
|
236
236
|
*
|
|
237
|
-
* invalid_file:<rel(gradedArtifact)>:<validator reason> gateway.mjs
|
|
238
|
-
* max_tokens_no_output:<the whole string above> gateway.mjs
|
|
237
|
+
* invalid_file:<rel(gradedArtifact)>:<validator reason> runStageLadder() in gateway.mjs
|
|
238
|
+
* max_tokens_no_output:<the whole string above> runStageLadder() in gateway.mjs
|
|
239
239
|
*
|
|
240
240
|
* The file segment is variable and `max_tokens_no_output:` PREFIXES an otherwise ordinary token, so a
|
|
241
241
|
* matcher keyed on the bare token misses the emitted string and reports a false unattached token. These
|
|
@@ -281,7 +281,7 @@ export const ARM1_EXEMPTIONS = [
|
|
|
281
281
|
{
|
|
282
282
|
token: "profile_unparseable",
|
|
283
283
|
stages: ["common-law"],
|
|
284
|
-
reason: "The run's profile is config the driver loaded before the stage ran (verify.mjs:301). A corrupt profile is a config fault; no element of the common-law contract is about it.",
|
|
284
|
+
reason: "The run's profile is config the driver loaded before the stage ran (verify.mjs:301 COMMONLAW_UNAVAILABLE_RE). A corrupt profile is a config fault; no element of the common-law contract is about it.",
|
|
285
285
|
},
|
|
286
286
|
{
|
|
287
287
|
token: "framework_manifest_unreadable",
|
|
@@ -303,7 +303,7 @@ export const ARM1_EXEMPTIONS = [
|
|
|
303
303
|
{
|
|
304
304
|
token: "plan_execution_unreadable",
|
|
305
305
|
stages: ["register-digest", "narrative-refutation"],
|
|
306
|
-
reason: "The plan-execution receipt is written by the register tool lane, not by either stage (verify.mjs:1496, 1712). Unreadable is a tool/driver fault; both stages are only READERS of it.",
|
|
306
|
+
reason: "The plan-execution receipt is written by the register tool lane, not by either stage (verify.mjs:1496 rowLabel, 1712). Unreadable is a tool/driver fault; both stages are only READERS of it.",
|
|
307
307
|
},
|
|
308
308
|
{
|
|
309
309
|
token: "grid_spec_unreadable",
|
|
@@ -318,7 +318,7 @@ export const ARM1_EXEMPTIONS = [
|
|
|
318
318
|
{
|
|
319
319
|
token: "coverage_form_empty",
|
|
320
320
|
stages: ["register-digest"],
|
|
321
|
-
reason: "verify.mjs:1449, same artifact and same verdict in its own string: a form that parsed and carries no rows is \"(driver-written — this is a bug, not a model defect)\".",
|
|
321
|
+
reason: "verify.mjs:1449 connotationDispositionFail, same artifact and same verdict in its own string: a form that parsed and carries no rows is \"(driver-written — this is a bug, not a model defect)\".",
|
|
322
322
|
},
|
|
323
323
|
{
|
|
324
324
|
token: "half_path_unrecognized",
|
|
@@ -331,7 +331,7 @@ export const ARM1_EXEMPTIONS = [
|
|
|
331
331
|
* STAGE-UNREACHABLE VALIDATORS — not exemptions, a SCOPE statement.
|
|
332
332
|
*
|
|
333
333
|
* verify.mjs wires four validators no STAGES entry reaches: findings(1661), report(1719), audit(1728),
|
|
334
|
-
* clientSummary(1736). They are NOT dead code — replay-archive.mjs:128 and mcp-server/lib/coverage.mjs
|
|
334
|
+
* clientSummary(1736). They are NOT dead code — replay-archive.mjs:128 replayRun and check() in mcp-server/lib/coverage.mjs
|
|
335
335
|
* index validators[key] dynamically and the MCP server declares `audit` required — but no stage dispatches
|
|
336
336
|
* them, so their tokens are unaccountable BY CONSTRUCTION with respect to a stage partition. The
|
|
337
337
|
* client-summary STAGE was deleted 2026-08-01 (no line to cite: the stage is gone) and its validator outlived it.
|
|
@@ -343,7 +343,7 @@ export const STAGE_UNREACHABLE_VALIDATORS = [
|
|
|
343
343
|
{ validator: "findings", site: "driver/verify.mjs validators.findings", reason: "no STAGES entry names it; synthesis validates findings.json through validators.narrative's sibling checks instead" },
|
|
344
344
|
{ validator: "report", site: "driver/verify.mjs validators.report", reason: "report.md is ASSEMBLED by the driver (assembleReportMd); the validator survives as a post-assembly structural gate outside the stage table" },
|
|
345
345
|
{ validator: "audit", site: "driver/verify.mjs validators.audit", reason: "reached only through the MCP server's dynamic validators[key] lookup, which declares it required" },
|
|
346
|
-
{ validator: "clientSummary", site: "driver/verify.mjs
|
|
346
|
+
{ validator: "clientSummary", site: "driver/verify.mjs validators.clientSummary", reason: "the client-summary STAGE was deleted 2026-08-01 (no line to cite: the stage is gone); the validator was not" },
|
|
347
347
|
];
|
|
348
348
|
|
|
349
349
|
/**
|
|
@@ -423,7 +423,7 @@ export const TRIPWIRE_OUT_OF_SCOPE = [
|
|
|
423
423
|
{
|
|
424
424
|
prefix: "register_plan_",
|
|
425
425
|
site: "driver/register-plan.mjs",
|
|
426
|
-
reason: "parseRegisterPlan is called once from a validator (verify.mjs
|
|
426
|
+
reason: "parseRegisterPlan is called once from a validator (validators.registerFindings in verify.mjs) inside a try whose catch swallows it — `catch { /* no plan in reach — gate inactive */ }` in the same validator. No throw of this family becomes a stage failure token.",
|
|
427
427
|
},
|
|
428
428
|
];
|
|
429
429
|
|
|
@@ -526,12 +526,12 @@ export const INNER_CODES = Object.freeze([
|
|
|
526
526
|
// `form_damaged` is minted in connotation-search.mjs AND coverage-form.mjs and becomes a different
|
|
527
527
|
// token in each lane. The static census records the FIRST site it matched and no more, so its own
|
|
528
528
|
// citation names one of the three mints — which is why `mints` is a list, and why a ruling copied from
|
|
529
|
-
// the census output would have been half a ruling. verify.mjs:1075 already states the rule this row
|
|
529
|
+
// the census output would have been half a ruling. verify.mjs:1075 parseCorrectionKinds already states the rule this row
|
|
530
530
|
// records: `coverage_form_damaged`, never a bare `form_damaged`.
|
|
531
531
|
{ code: "form_damaged",
|
|
532
532
|
mints: ["driver/connotation-search.mjs:2004", "driver/connotation-search.mjs:2080", "driver/coverage-form.mjs:876"],
|
|
533
533
|
rollsUpTo: ["connotation_form_damaged", "coverage_form_damaged"],
|
|
534
|
-
why: "Minted in two lanes and namespaced per lane: verify.mjs:1019 for the meaning sweep, verify.mjs:1119 for the register digest. The namespacing is what keeps them apart — see verify.mjs:1075." },
|
|
534
|
+
why: "Minted in two lanes and namespaced per lane: verify.mjs:1019 parseCorrections for the meaning sweep, verify.mjs:1119 findReviewerCoherenceFlags for the register digest. The namespacing is what keeps them apart — see verify.mjs:1075 parseCorrectionKinds." },
|
|
535
535
|
]);
|
|
536
536
|
|
|
537
537
|
/**
|
|
@@ -69,7 +69,14 @@ const readJsonl = (p) => {
|
|
|
69
69
|
} catch { return []; }
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The stage's own last attempt row, or null when the stage wrote none.
|
|
74
|
+
*
|
|
75
|
+
* A PART IS DEGRADED WHEN ITS STAGE FAILED, and that is the row's `fail`, never its `ok`. The two said the
|
|
76
|
+
* same thing until the attempt row learned to record a session that reached an error result and was
|
|
77
|
+
* recovered (engine/session-record.mjs): such an attempt is not ok, and its stage still delivered. Whether
|
|
78
|
+
* that should reach a client's report is a ruling nobody has made, so this reads what it always meant.
|
|
79
|
+
*/
|
|
73
80
|
export function lastAttempt(runDir, stage) {
|
|
74
81
|
const rows = readJsonl(driverDir(runDir, `${stage}.jsonl`))
|
|
75
82
|
.filter((r) => Number.isFinite(Number(r?.attempt)) && typeof r.ok === "boolean");
|
|
@@ -93,7 +100,7 @@ function courtDecisions(runDir, log) {
|
|
|
93
100
|
if (lastEvent(log, ["case-law-decision"])?.run !== true) return null;
|
|
94
101
|
const last = lastAttempt(runDir, "case-law");
|
|
95
102
|
const part = { part: "court-decisions", name: PART_NAMES.courtDecisions };
|
|
96
|
-
if (last?.
|
|
103
|
+
if (last?.fail) return { ...part, reason: reasonFor(last.fail), cause: `the case-law stage ended failed: ${last.fail}` };
|
|
97
104
|
let text;
|
|
98
105
|
try { text = readFileSync(join(runDir, "case-law-findings.md"), "utf8"); } catch { text = null; }
|
|
99
106
|
if (text === null) return { ...part, reason: NOT_COMPLETED, cause: "case-law-findings.md is absent" };
|
|
@@ -119,7 +126,7 @@ const failedStep = (log, ok, failed) => {
|
|
|
119
126
|
};
|
|
120
127
|
const stageEnded = (runDir, stage) => {
|
|
121
128
|
const last = lastAttempt(runDir, stage);
|
|
122
|
-
return last?.
|
|
129
|
+
return last?.fail ? last : null;
|
|
123
130
|
};
|
|
124
131
|
|
|
125
132
|
// The register's own steps, each of which the run survives without: the band's shape (the floors and the
|
|
@@ -46,6 +46,25 @@ export function dispatchFileName(stage, attempt, repair = 0) {
|
|
|
46
46
|
return `${stage}.attempt${attempt}${repair ? `.repair${repair}` : ""}.${DISPATCH_SUFFIX}`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
export const STREAMS_DIR = "streams";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `_driver/streams/<label>.attempt<N>.jsonl` (or `…repair<M>…`): the raw stream of the session that
|
|
53
|
+
* dispatch started, named as its dispatch record is. The engines write it (engine/session-record.mjs,
|
|
54
|
+
* `streamSink`); this is the one place the name is made. Absolute, through `driverDir`, so a label's
|
|
55
|
+
* colon is written the way the platform can hold it.
|
|
56
|
+
*
|
|
57
|
+
* IN ITS OWN FOLDER, NEVER BESIDE THE JOURNALS. Every `*.jsonl` directly under `_driver/` is read as a
|
|
58
|
+
* stage's attempt journal by name: the token rollup sums its usage rows, the degraded-parts check reads
|
|
59
|
+
* its label off the file name, the integrity check leaves it out as append-only. A raw stream there
|
|
60
|
+
* would be counted as a stage and its usage summed twice. One folder down, no reader of `_driver/`
|
|
61
|
+
* lists it, and the integrity snapshot reads files only.
|
|
62
|
+
*/
|
|
63
|
+
export function streamFilePath(runDir, stage, attempt, repair = 0) {
|
|
64
|
+
if (!runDir) return null;
|
|
65
|
+
return join(driverDir(runDir), STREAMS_DIR, driverFileName(`${stage}.attempt${attempt}${repair ? `.repair${repair}` : ""}.jsonl`));
|
|
66
|
+
}
|
|
67
|
+
|
|
49
68
|
const shaOf = (buf) => createHash("sha256").update(buf).digest("hex").slice(0, 12);
|
|
50
69
|
|
|
51
70
|
// ── — WHAT THE STAGE WAS GRANTED, BESIDE THE SHA THAT PROVES WHAT IT WAS ASKED ────────────────
|
|
@@ -119,8 +119,8 @@ export function captureCall(runDir, seq, received, { now = () => new Date().toIS
|
|
|
119
119
|
//
|
|
120
120
|
// THAT PREMISE IS WHY THIS IS SAFE, SO CHECK IT BEFORE MOVING THE SPEC WRITE. Five of the six
|
|
121
121
|
// doubt-minting sites build an id from an ordinal over a FILTERED list
|
|
122
|
-
// (`presence-reconciliation.mjs:182` mints `doubt:presence:<sheet>:<n>` after three `continue` filters,
|
|
123
|
-
// with `status: "open"` hardcoded beside it); `doubt-ledger.mjs:232` is the exception and the template,
|
|
122
|
+
// (`presence-reconciliation.mjs:182 mintPresenceDoubts` mints `doubt:presence:<sheet>:<n>` after three `continue` filters,
|
|
123
|
+
// with `status: "open"` hardcoded beside it); `doubt-ledger.mjs:232 mintContradictionDoubts` is the exception and the template,
|
|
124
124
|
// putting the candidate name in the id and using the ordinal only as a tiebreaker. If the spec is ever
|
|
125
125
|
// minted per attempt instead of per stage, those ordinals can shift between attempts and this serve
|
|
126
126
|
// starts handing back a verdict about a different subject. Nothing else in this file would notice.
|
package/driver/driver.config.mjs
CHANGED
|
@@ -1337,6 +1337,10 @@ export const PROVIDERS = {
|
|
|
1337
1337
|
return { ok: true,
|
|
1338
1338
|
total: Number.isFinite(p.total_hits) ? p.total_hits : null,
|
|
1339
1339
|
...(p.total_approximate === true ? { approximate: true, floor: Number.isFinite(p.total_floor) ? p.total_floor : null } : {}),
|
|
1340
|
+
// The register's own notes on this answer, carried whole as the clearance sweep carries them —
|
|
1341
|
+
// `expanded_fallback` among them, which says the total counts records rather than marks. The
|
|
1342
|
+
// knockout listing dropped them, so a knockout run could not show what a clearance run shows.
|
|
1343
|
+
...(Array.isArray(p.warnings) && p.warnings.length ? { warnings: p.warnings } : {}),
|
|
1340
1344
|
records: (Array.isArray(p.results) ? p.results : []).map((row) => {
|
|
1341
1345
|
const rec = row?.raw && typeof row.raw === "object" ? core.normalizeRecord(row.raw, row.office || null) : null;
|
|
1342
1346
|
return {
|
|
@@ -1355,6 +1359,9 @@ export const PROVIDERS = {
|
|
|
1355
1359
|
filing_route: rec?.filingRoute ?? null,
|
|
1356
1360
|
// No page per record at Signa (hasPublicRecordUrl: false above), and none is made up here.
|
|
1357
1361
|
record_url: null,
|
|
1362
|
+
// The office's own page as the register gives it, for publish to fall back on where it cannot
|
|
1363
|
+
// address the office's page from the numbers above (office-record-links.mjs).
|
|
1364
|
+
office_url: rec?.officeUrl ?? null,
|
|
1358
1365
|
};
|
|
1359
1366
|
}) };
|
|
1360
1367
|
} catch (e) { return { ok: false, records: null, reason: `listRecords threw: ${e.message}` }; }
|
|
@@ -2079,12 +2086,31 @@ export const ENGINE_BINARIES = {
|
|
|
2079
2086
|
// and a pinned id was refused outright ("version 2.1.280 or newer is required"), measured 2026-09-22.
|
|
2080
2087
|
// A floor that only asks for a program that starts is a floor that passes a machine this engine
|
|
2081
2088
|
// cannot run the current generation on.
|
|
2082
|
-
|
|
2089
|
+
//
|
|
2090
|
+
// THE HAIKU TIER CROSSED LATER, when Claude Haiku 5.5 joined the generation. Measured 2026-10-09 by a
|
|
2091
|
+
// one-word `-p` turn per version on the test box's own sign-in, reading the model the turn reports in
|
|
2092
|
+
// `modelUsage`:
|
|
2093
|
+
//
|
|
2094
|
+
// version --model haiku
|
|
2095
|
+
// 2.1.285 claude-haiku-4-5-20251001
|
|
2096
|
+
// 2.1.287 claude-haiku-4-5-20251001
|
|
2097
|
+
// 2.1.288 claude-haiku-4-5-20251001
|
|
2098
|
+
// 2.1.289 claude-haiku-4-5-20251001
|
|
2099
|
+
// 2.1.290 claude-haiku-4-5-20251001
|
|
2100
|
+
// 2.1.292 claude-haiku-4-5-20251001
|
|
2101
|
+
// 2.1.293 claude-haiku-5-5
|
|
2102
|
+
// 2.1.294 claude-haiku-5-5
|
|
2103
|
+
// 2.1.295 claude-haiku-5-5
|
|
2104
|
+
//
|
|
2105
|
+
// 2.1.295 still serves claude-opus-5-5 and claude-sonnet-5-5. The floor moves to 2.1.293 for the reason
|
|
2106
|
+
// it moved to 2.1.284: a stage that asks for haiku is asking for the current haiku.
|
|
2107
|
+
package: "@anthropic-ai/claude-code", floor: "2.1.293",
|
|
2083
2108
|
// WHAT THE INSTALL TAKES ON DISK, in MB, which setup states before it asks to install. MEASURED, not
|
|
2084
2109
|
// declared by the vendor: the engines folder after a fresh install of this package into an empty
|
|
2085
|
-
// folder
|
|
2086
|
-
//
|
|
2087
|
-
|
|
2110
|
+
// folder. Re-measured when the floor moved to 2.1.293: 242 MB for a fresh install of 2.1.293 itself,
|
|
2111
|
+
// on npm 10.9.9, 2026-10-09. A later release can be larger or smaller, so setup says "about".
|
|
2112
|
+
// Re-measure when the floor moves.
|
|
2113
|
+
installMB: 242,
|
|
2088
2114
|
// The licence setup states wherever it tells a reader what they are about to install or use, as the
|
|
2089
2115
|
// vendor's package declares it: "SEE LICENSE IN README.md", Anthropic's own terms.
|
|
2090
2116
|
licence: "proprietary third-party software",
|
|
@@ -2110,8 +2136,15 @@ export const ENGINE_BINARIES = {
|
|
|
2110
2136
|
vendor: "OpenAI",
|
|
2111
2137
|
product: "Codex",
|
|
2112
2138
|
env: "CLEAROTRON_CODEX_PATH", fallback: "codex",
|
|
2113
|
-
|
|
2114
|
-
|
|
2139
|
+
// THE FLOOR IS THE OLDEST RELEASE THAT ACCEPTS THE JUDGMENT TIER'S MODEL on a ChatGPT sign-in, which
|
|
2140
|
+
// is how a subscription bills. Measured 2026-10-09 with a one-word `codex exec` turn per version on the
|
|
2141
|
+
// test box: `gpt-6.1-sol` is refused with a 400 on 0.156.1, 0.157.0 and 0.158.0 ("not supported when
|
|
2142
|
+
// using Codex with a ChatGPT account") and accepted on 0.159.0, 0.160.0, 0.161.0 and 0.162.0;
|
|
2143
|
+
// `gpt-6-luna` is accepted from 0.156.1. Below the floor every clearance stops at its first
|
|
2144
|
+
// judgment-tier stage (the tiers are in engine/openai-agent.mjs). The old floor, 0.154.0, was the
|
|
2145
|
+
// permissions feature's (engine/mcp/codex-config.mjs).
|
|
2146
|
+
package: "@openai/codex", floor: "0.159.0",
|
|
2147
|
+
installMB: 424, // re-measured the same way as Claude's, above: a fresh install of 0.159.0 itself, 2026-10-09
|
|
2115
2148
|
licence: "third-party software under the Apache-2.0 licence", // the package's own "license" field
|
|
2116
2149
|
label: "OpenAI — each stage runs as a headless `codex exec` turn",
|
|
2117
2150
|
module: "engine/openai-agent.mjs", adapter: "openaiAgentEngine",
|
|
@@ -37,7 +37,7 @@ export function isFinding(heading, block) {
|
|
|
37
37
|
|
|
38
38
|
// Distinctive word anchors (length ≥4, non-stopword) from the owner / element / goods / sector /
|
|
39
39
|
// jurisdiction / mark vocabulary; multi-word anchors contribute their significant words (so a finding
|
|
40
|
-
// mentioning "
|
|
40
|
+
// mentioning "Varnholt" anchors against owner "Varnholt Advokatpartnerselskab").
|
|
41
41
|
function anchorTerms(anchors = {}) {
|
|
42
42
|
const raw = [
|
|
43
43
|
...(anchors.owners ?? []), ...(anchors.dominantElements ?? []), ...(anchors.goodsServices ?? []),
|
|
@@ -36,7 +36,9 @@ ladder consumes it without knowing which engine produced it ([gateway.mjs](../ga
|
|
|
36
36
|
laneWaitMs: number,
|
|
37
37
|
json: object | null, // synthesized envelope in the classifier's shape; every downstream
|
|
38
38
|
// classifier (payloadText, status, embedded-fallback, timeout,
|
|
39
|
-
// lane-wedge) reads this and works unchanged across engines
|
|
39
|
+
// lane-wedge) reads this and works unchanged across engines.
|
|
40
|
+
// It also carries how the session ended: terminalReason,
|
|
41
|
+
// apiErrorStatus and errorText (see `session` below)
|
|
40
42
|
usage: Usage | null, // canonical shape below; null = no tokens accounted (e.g. stall)
|
|
41
43
|
sessionRef: string | null, // opaque resume handle (claude session_id | codex thread_id)
|
|
42
44
|
modelWire: string | null, // MODEL GAUGE — the served model id this turn observed (§3);
|
|
@@ -57,9 +59,47 @@ ladder consumes it without knowing which engine produced it ([gateway.mjs](../ga
|
|
|
57
59
|
// opened; [] = recorded "read nothing"; key absent = engine cannot
|
|
58
60
|
// observe reads (openai-agent) → gateway journals `reads: null`
|
|
59
61
|
readsTruncated: boolean | absent, // true = the path cap dropped at least one further distinct file
|
|
62
|
+
toolCallsByName: object | null, // the turn's tool calls by tool name; an MCP call is
|
|
63
|
+
// `mcp__<server>__<tool>` on both engines; null = no session ran
|
|
64
|
+
session: Session | null, // what the session went through (below); null = no session ran
|
|
65
|
+
stream: { file, present, bytes, reason? } | null,
|
|
66
|
+
// the raw stream, written to the file the caller named
|
|
67
|
+
// (the gateway names _driver/streams/<label>.attempt<N>.jsonl)
|
|
60
68
|
}
|
|
61
69
|
```
|
|
62
70
|
|
|
71
|
+
### `session` — what the session went through
|
|
72
|
+
|
|
73
|
+
Both engines used to keep only the LAST result of a session, so a session the vendor's classifier cut
|
|
74
|
+
off mid-answer, or refused and a restart recovered, ended in a successful result and was recorded as
|
|
75
|
+
clean. `session` keeps every event of that kind, as the program wrote it, with milliseconds since the
|
|
76
|
+
spawn ([engine/session-record.mjs](session-record.mjs)):
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Session { interrupted, starts, results: [{atMs, index, subtype, isError, stopReason, terminalReason, origin}],
|
|
80
|
+
classifierCuts: [{atMs, synthetic}], refusals: [{atMs, category, model}], refusalStops }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
It carries kinds, codes and times, never the program's words. The retry ladder still reads the last
|
|
84
|
+
result alone; the gateway journals the attempt as `ok: false` when any result carried `is_error`, while
|
|
85
|
+
`fail` — what every reader of "did the stage fail" reads — is unchanged. Codex states no cut or refusal
|
|
86
|
+
on its stream, so on that engine `results` lists every `turn.completed`, `turn.failed` and `error`.
|
|
87
|
+
|
|
88
|
+
How the session ended rides the envelope, and the gateway writes it on every attempt and repair row,
|
|
89
|
+
null where the engine said nothing:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
terminalReason the kind of ending as the program states it (claude `terminal_reason`); null on codex
|
|
93
|
+
apiErrorStatus the vendor's status when it gave one (claude `api_error_status`); null on codex
|
|
94
|
+
errorText the program's own words for the error, only when the session ended in one, cut to
|
|
95
|
+
300 characters: claude's result text when that result has `is_error`; codex's
|
|
96
|
+
`turn.failed` message, or its stream error when the turn never completed
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
These are the one place the record holds the program's words: an expired sign-in otherwise reads as
|
|
100
|
+
`stop_sequence` and nothing else. `stopReason` stays verbatim beside them, and none of the three moves a
|
|
101
|
+
verdict.
|
|
102
|
+
|
|
63
103
|
`runStage()` folds that into what the pipeline consumes — `{ok, json, attempts, text, sessionKey, …}` —
|
|
64
104
|
and journals usage/wall/status on the way past.
|
|
65
105
|
|
|
@@ -26,6 +26,7 @@ import { authorityTrees } from "../authority-trees.mjs";
|
|
|
26
26
|
import { recordEngineChild, clearEngineChild } from "./child-record.mjs"; //
|
|
27
27
|
import { engineEnv } from "./engine-env.mjs"; // — the program's environment, by list (see spawnEnv)
|
|
28
28
|
import { engineSpawn, spawnsDetached, killWindowsTreeNow } from "./engine-spawn.mjs"; // one answer to how a turn starts and ends, on every platform
|
|
29
|
+
import { newSessionRecord, noteClaudeEvent, sessionSummary, countTool, streamSink, claudeEnding } from "./session-record.mjs"; // what the session went through, beside how it ended
|
|
29
30
|
|
|
30
31
|
// Read per-call (not module-level) so tests can drive a short stall timeout / a mock binary.
|
|
31
32
|
// ONE place knows how to find the program (driver.config.mjs resolveEngineProgram): the explicit setting,
|
|
@@ -514,6 +515,9 @@ export function synthesizeEnvelope({ resultEvent, usage, killed }) {
|
|
|
514
515
|
status: ok ? "ok" : (killed ? "timeout" : "error"),
|
|
515
516
|
result: { meta: { agentMeta: { usage } }, payloads: [{ text }] },
|
|
516
517
|
summary: r?.subtype, runId: r?.session_id, stopReason: r?.stop_reason,
|
|
518
|
+
// …and beside the stop reason, what ended the session and, on an error, the program's own words for it
|
|
519
|
+
// (session-record.mjs, claudeEnding). Recorded only: `ok` above reads exactly what it read before.
|
|
520
|
+
...claudeEnding(r),
|
|
517
521
|
};
|
|
518
522
|
}
|
|
519
523
|
|
|
@@ -524,7 +528,7 @@ export const anthropicAgentEngine = {
|
|
|
524
528
|
// doctrine tree, the profile store and `<runDir>/_driver/` at the moment of the write. That is a real
|
|
525
529
|
// boundary and this engine has it.
|
|
526
530
|
writeBoundary: "enforced",
|
|
527
|
-
async runTurn({ message, model, thinking, timeoutSec, resumeRef, mcpConfig, allowedTools, cwd, skillsDir, skillsGrantRoots, profilesDir, resolveSkill, runDir, seatWrites = null, grantDispatch = null, stallSec, progressFiles } = {}) {
|
|
531
|
+
async runTurn({ message, model, thinking, timeoutSec, resumeRef, mcpConfig, allowedTools, cwd, skillsDir, skillsGrantRoots, profilesDir, resolveSkill, runDir, seatWrites = null, grantDispatch = null, stallSec, progressFiles, streamFile = null } = {}) {
|
|
528
532
|
if (!message) throw new Error("anthropic-agent.runTurn: message is required");
|
|
529
533
|
const { args, input, grantNote } = buildClaudeArgs({ message, model, thinking, resumeRef, mcpConfig, allowedTools, cwd, skillsDir, skillsGrantRoots, profilesDir, resolveSkill, runDir, seatWrites, grantDispatch });
|
|
530
534
|
// the cross-check speaks. Driver stderr is what an operator reads, and it is where the other
|
|
@@ -690,6 +694,24 @@ export const anthropicAgentEngine = {
|
|
|
690
694
|
// ("a completed tool result = the agent loop advanced", below). The gap between them is the turn
|
|
691
695
|
// waiting on tools rather than generating.
|
|
692
696
|
let toolCalls = 0;
|
|
697
|
+
// …AND THE SAME CALLS BY TOOL NAME, so a tool server's own log can be set against what the model
|
|
698
|
+
// asked for. A name is the program's identifier for a tool (`Read`, `mcp__band__band_lookup`), never
|
|
699
|
+
// an input, so it carries no client text. The values sum to `toolCalls` by construction: one fold
|
|
700
|
+
// per tool_use block, at the one site that counts them.
|
|
701
|
+
const toolCallsByName = Object.create(null);
|
|
702
|
+
// …AND WHICH OF THEM A TOOL SERVER ANSWERED WITH AN ERROR. A refused call the model then re-made used to
|
|
703
|
+
// leave the stage row reading ok on one attempt, as though nothing had happened. Each ask's id names its
|
|
704
|
+
// tool, and its result says whether it came back as an error; nothing else is kept.
|
|
705
|
+
const toolNameById = new Map();
|
|
706
|
+
const toolErrorIds = new Set();
|
|
707
|
+
// WHAT THE SESSION WENT THROUGH, beside how it ended: every result event, every classifier cut,
|
|
708
|
+
// every refusal and every restart, each with when it arrived. See session-record.mjs; the retry
|
|
709
|
+
// policy below still reads the LAST result alone, so this records and decides nothing.
|
|
710
|
+
const session = newSessionRecord();
|
|
711
|
+
// The raw stream, one file per session beside its dispatch (the caller names it). Opened before the
|
|
712
|
+
// first byte can arrive; closed in settle(). A sink that could not open is a null writer whose
|
|
713
|
+
// close() says why.
|
|
714
|
+
const sink = streamSink(streamFile);
|
|
693
715
|
// HOW MANY OF THOSE ASKED FOR A COMMAND TOOL. The command tools are removed from every stage
|
|
694
716
|
// (COMMAND_TOOLS), so the number a test round expects here is zero, and a count is what proves it: a
|
|
695
717
|
// flag in the argv shows what was asked of the program, this shows what the model did. Still a count
|
|
@@ -814,6 +836,7 @@ export const anthropicAgentEngine = {
|
|
|
814
836
|
let ev;
|
|
815
837
|
try { ev = JSON.parse(line); } catch { return; /* partial/non-json line */ }
|
|
816
838
|
sawAnyEvent = true;
|
|
839
|
+
noteClaudeEvent(session, ev, Date.now() - t0);
|
|
817
840
|
if (ev.type === "result") resultEvent = ev;
|
|
818
841
|
else if (ev.type === "rate_limit_event") rateLimitEvent = ev;
|
|
819
842
|
else if (ev.type === "system" && ev.subtype === "init") {
|
|
@@ -839,7 +862,8 @@ export const anthropicAgentEngine = {
|
|
|
839
862
|
// classification and no recovery park, or the turn never settles with its watchdog already gone.
|
|
840
863
|
// Same belt-and-braces as the thinking gauge's `?.some?.()` on the line above.
|
|
841
864
|
for (const b of Array.isArray(ev.message?.content) ? ev.message.content : []) {
|
|
842
|
-
if (b?.type === "tool_use") toolCalls++; // #1111 — every tool, not only Read;
|
|
865
|
+
if (b?.type === "tool_use") { toolCalls++; countTool(toolCallsByName, b?.name); } // #1111 — every tool, not only Read; the count and its split by name
|
|
866
|
+
if (b?.type === "tool_use" && b?.id != null) toolNameById.set(String(b.id), String(b?.name ?? "?"));
|
|
843
867
|
if (b?.type === "tool_use" && COMMAND_TOOLS.includes(b?.name)) commandToolCalls++;
|
|
844
868
|
if (b?.type === "tool_use" && FILE_WRITE_TOOLS.includes(b?.name) && b?.id != null) writeAsks.add(String(b.id));
|
|
845
869
|
if (b?.type === "tool_use" && b?.name === "Read" && typeof b?.input?.file_path === "string") {
|
|
@@ -873,8 +897,10 @@ export const anthropicAgentEngine = {
|
|
|
873
897
|
progress();
|
|
874
898
|
}
|
|
875
899
|
else if (ev.type === "user") {
|
|
876
|
-
for (const b of Array.isArray(ev.message?.content) ? ev.message.content : [])
|
|
900
|
+
for (const b of Array.isArray(ev.message?.content) ? ev.message.content : []) {
|
|
877
901
|
if (b?.type === "tool_result" && b?.is_error === true && writeAsks.has(String(b?.tool_use_id))) writesFailed++;
|
|
902
|
+
if (b?.type === "tool_result" && b?.is_error === true && b?.tool_use_id != null) toolErrorIds.add(String(b.tool_use_id));
|
|
903
|
+
}
|
|
878
904
|
// the result of the ask above. Only counted when an ask is outstanding, so a `user`
|
|
879
905
|
// event with no preceding tool_use adds nothing rather than charging the turn for a gap it
|
|
880
906
|
// never spent waiting.
|
|
@@ -1052,6 +1078,7 @@ export const anthropicAgentEngine = {
|
|
|
1052
1078
|
chunkGapMs = prevChunkAt === null ? 0 : Date.now() - prevChunkAt;
|
|
1053
1079
|
prevChunkAt = Date.now();
|
|
1054
1080
|
lastMove = Date.now(); // ANY streamed byte = liveness → resets the stall clock
|
|
1081
|
+
sink.write(d); // the raw stream, byte for byte, before anything parses it
|
|
1055
1082
|
buf += d.toString();
|
|
1056
1083
|
let nl;
|
|
1057
1084
|
while ((nl = buf.indexOf("\n")) >= 0) { parseLine(buf.slice(0, nl)); buf = buf.slice(nl + 1); }
|
|
@@ -1074,7 +1101,7 @@ export const anthropicAgentEngine = {
|
|
|
1074
1101
|
if (stderr.length > maxBuffer) { stderr = stderr.slice(0, maxBuffer); overflowKill(); }
|
|
1075
1102
|
});
|
|
1076
1103
|
// error + close can BOTH fire for a single failure (Node) → settle EXACTLY once (no double clearInterval/resolve).
|
|
1077
|
-
child.on("error", (e) => { if (settled) return; settled = true; clearInterval(watchdog); resolve(errResult(t0, e, resumeRef)); });
|
|
1104
|
+
child.on("error", (e) => { if (settled) return; settled = true; clearInterval(watchdog); sink.close(); resolve(errResult(t0, e, resumeRef)); });
|
|
1078
1105
|
child.on("close", (code) => settle(code));
|
|
1079
1106
|
function settle(code) {
|
|
1080
1107
|
// the turn is over; the record must not outlive it, and is cleared only if
|
|
@@ -1087,6 +1114,7 @@ export const anthropicAgentEngine = {
|
|
|
1087
1114
|
try { child.stdout.destroy(); } catch { /* stream gone */ }
|
|
1088
1115
|
try { child.stderr.destroy(); } catch { /* stream gone */ }
|
|
1089
1116
|
if (!overflow) { parseLine(buf); } // B1: flush the final, un-terminated line (the result event) — but NEVER the truncated overflow tail
|
|
1117
|
+
const stream = sink.close(); // after the flush: every byte the program wrote is in it, or the record says why not
|
|
1090
1118
|
buf = "";
|
|
1091
1119
|
// ONE SAMPLE OF THE CLOCK, and both quantities below are derived from it. Two `Date.now()` calls
|
|
1092
1120
|
// a few milliseconds apart are enough to break the identity on the very turn it matters for: on a
|
|
@@ -1176,11 +1204,18 @@ export const anthropicAgentEngine = {
|
|
|
1176
1204
|
// from this list is unreliable and the consumer must downgrade it to "not observed").
|
|
1177
1205
|
reads: [...reads], readsTruncated,
|
|
1178
1206
|
toolCalls, toolWaitMs: settledToolWaitMs, // #1111 — two integers, no content
|
|
1207
|
+
// The same calls by tool name (values sum to toolCalls), and what the session went through: every
|
|
1208
|
+
// result, cut, refusal and restart (session-record.mjs). `stream` is the raw stream's file and size,
|
|
1209
|
+
// or why it was not kept. Recording only — `failed` above still reads the last result alone.
|
|
1210
|
+
toolCallsByName: { ...toolCallsByName }, session: sessionSummary(session), stream,
|
|
1179
1211
|
// The two counts a test round reads per stage: calls to a command tool, and calls the program
|
|
1180
1212
|
// refused. The refusals are the result event's own `permission_denials`, counted; null when the
|
|
1181
1213
|
// turn settled with no result event, so "not reported" never reads as "none refused".
|
|
1182
1214
|
commandToolCalls, writesFailed,
|
|
1183
1215
|
toolCallsRefused: Array.isArray(resultEvent?.permission_denials) ? resultEvent.permission_denials.length : null,
|
|
1216
|
+
// The tool-server calls that came back as an error, by tool name: the program sent them and the
|
|
1217
|
+
// server refused or failed them. A call the program itself refused is counted above and not here.
|
|
1218
|
+
toolCallsErroredByName: toolServerErrors(toolErrorIds, toolNameById, resultEvent?.permission_denials),
|
|
1184
1219
|
// ms from spawn to the child's FIRST byte, or null when it never spoke. RECORDING
|
|
1185
1220
|
// ONLY: nothing branches on it. A recording field is protected by a test or by nothing, so
|
|
1186
1221
|
// engine.anthropic.test.mjs pins it — deleting it must red something.
|
|
@@ -1253,9 +1288,26 @@ function errResult(t0, e, resumeRef) {
|
|
|
1253
1288
|
// modelWire: null for the opposite reason — no turn ran, so the wire said nothing about a model, and
|
|
1254
1289
|
// the record must say UNKNOWN rather than inherit the alias that was asked for.
|
|
1255
1290
|
json: null, usage: null, reads: [], readsTruncated: false, modelWire: null, providerWire: null, sessionRef: resumeRef ?? null,
|
|
1291
|
+
// No session ran, so there is nothing to have been interrupted and no stream: null, never an empty record.
|
|
1292
|
+
toolCallsByName: null, toolCallsErroredByName: null, session: null, stream: null,
|
|
1256
1293
|
};
|
|
1257
1294
|
}
|
|
1258
1295
|
|
|
1296
|
+
/**
|
|
1297
|
+
* The tool-server calls of one turn that came back as an error, by tool name: every `mcp__` ask whose
|
|
1298
|
+
* result carried `is_error`, less the asks the program refused itself (`permission_denials`, which
|
|
1299
|
+
* `toolCallsRefused` counts). An OBJECT is a measurement (`{}`: none). PURE.
|
|
1300
|
+
*/
|
|
1301
|
+
export function toolServerErrors(errorIds, nameById, denials) {
|
|
1302
|
+
const denied = new Set((Array.isArray(denials) ? denials : []).map((d) => String(d?.tool_use_id ?? "")).filter(Boolean));
|
|
1303
|
+
const byName = Object.create(null);
|
|
1304
|
+
for (const id of errorIds ?? []) {
|
|
1305
|
+
const name = nameById?.get(id);
|
|
1306
|
+
if (typeof name === "string" && name.startsWith("mcp__") && !denied.has(id)) countTool(byName, name);
|
|
1307
|
+
}
|
|
1308
|
+
return { ...byName };
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1259
1311
|
/**
|
|
1260
1312
|
* The program's own word for which provider served a turn, from the result event's per-model usage:
|
|
1261
1313
|
* `modelUsage[<model>].provider`, "firstParty" on Anthropic's own API and "foundry" on Azure Foundry
|
|
@@ -218,7 +218,7 @@ export function commandEnvToml(withheld = []) {
|
|
|
218
218
|
// ── the stage's permission profile: what its shell commands may read and write ──────────────────────
|
|
219
219
|
// codex's `workspace-write` sandbox confines where a command WRITES and lets it read the whole disk, the
|
|
220
220
|
// install's settings files and the program's own sign-in included. A permission profile confines reading
|
|
221
|
-
// too (the vendor's Permissions guide, a beta feature since 0.138.0; the floor
|
|
221
|
+
// too (the vendor's Permissions guide, a beta feature since 0.138.0; the floor was 0.154.0 when this was written and is 0.159.0 now, for the judgment tier's model — driver.config.mjs): a stage's
|
|
222
222
|
// commands read the platform's own paths, the instruction trees and the temp folders, and write the run
|
|
223
223
|
// folder, their working folder and the temp folders. Nothing else is readable, so a page a stage fetched
|
|
224
224
|
// cannot get a command to print the account's files. The temp folders stay writable because
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// wired per run by the driver; there is no parameter for it and this tool never guesses one
|
|
20
20
|
// (recording-server.mjs's record_doubt_closure records why the parameter must not exist). The form name comes off the run's own
|
|
21
21
|
// era stamp, the row ids are the driver's, and every other identifier is computed. Read at CALL TIME,
|
|
22
|
-
// never captured at module load (stdio-server.mjs:29-33 states why for exactly this variable).
|
|
22
|
+
// never captured at module load (stdio-server.mjs:29-33 toolLogPath states why for exactly this variable).
|
|
23
23
|
import { serve } from "./stdio-server.mjs";
|
|
24
24
|
import { recordCoverage } from "../../coverage-tool.mjs";
|
|
25
25
|
import { MAX_ROWS_PER_CALL } from "../../coverage-call.mjs";
|
|
@@ -107,7 +107,7 @@ export const REGISTER_SERVERS = {
|
|
|
107
107
|
// public page, and this provider publishes neither. Read 2026-08-22 across providers/signa/src: no
|
|
108
108
|
// vienna or figurative field anywhere in the core, the normaliser carries `imageAvailable`
|
|
109
109
|
// (`rec.has_media`) and nothing else about the mark's figure, `hasPublicRecordUrl: false` and
|
|
110
|
-
// `resolved_link: null`
|
|
110
|
+
// `resolved_link: null` (no page per record of its own). Serving it would return a
|
|
111
111
|
// bare boolean under a name that promises comparable figurative data. NOT A PROBE: this is a read of
|
|
112
112
|
// the code and the vendor doc in this repo, and whether the live API has an image endpoint nobody
|
|
113
113
|
// wired is UNTESTED from here — euipo's `markImage` looked equally absent until `GET
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// ── WHAT THIS CATEGORY IS FOR, AND WHY IT IS NOT A RETRIEVAL GRANT ──────────────────────────────────
|
|
6
6
|
//
|
|
7
7
|
// The two-box model conflates "may reach the outside world" with "may hand back structure"
|
|
8
|
-
// (gather-config.mjs:246-268). A stage converted to a typed return needs the second and not the first.
|
|
8
|
+
// (gather-config.mjs:246-268 RECORDING). A stage converted to a typed return needs the second and not the first.
|
|
9
9
|
// This server carries ONLY the second: it writes the calling stage's own artifact into the calling
|
|
10
10
|
// stage's own run directory, and it dials nothing.
|
|
11
11
|
//
|
|
@@ -55,7 +55,7 @@ import { PROVIDERS, REGISTER_PROVIDER } from "../../driver.config.mjs";
|
|
|
55
55
|
// already written against them; this conversion is the consumption their headers said was still owed.
|
|
56
56
|
import { recordClosures } from "../../doubt-closure-tool.mjs";
|
|
57
57
|
import { MAX_CLOSURES_PER_CALL, CLOSURE_KINDS } from "../../doubt-closure-call.mjs";
|
|
58
|
-
import { readFileSync } from "node:fs";
|
|
58
|
+
import { readFileSync, existsSync } from "node:fs"; import { FROZEN_METHOD_FILE } from "../../framework-method.mjs";
|
|
59
59
|
import { join } from "node:path";
|
|
60
60
|
import { driverDir } from "../../../shared/driver-dir.mjs"; //
|
|
61
61
|
import { VARIANT_CATEGORIES } from "../../variant-manifest-model.mjs";
|
|
@@ -398,7 +398,7 @@ serve({
|
|
|
398
398
|
item: {
|
|
399
399
|
type: "string",
|
|
400
400
|
description:
|
|
401
|
-
"For a FIRING variant directive this must be a mark-shaped search term (
|
|
401
|
+
"For a FIRING variant directive this must be a mark-shaped search term (WAVA, CORAL " +
|
|
402
402
|
"MAGIC) or carry a remedy. A label — a parenthetical, an enumeration, more than about " +
|
|
403
403
|
"four words — dispatches as a nil search that reads CLEAN, and is refused here.",
|
|
404
404
|
},
|
|
@@ -1134,6 +1134,9 @@ serve({
|
|
|
1134
1134
|
handler: record_synthesis,
|
|
1135
1135
|
}, {
|
|
1136
1136
|
name: "record_knockout_assess",
|
|
1137
|
+
// The framework's `inputs` fields are offered only on a run whose framework states a method: a session
|
|
1138
|
+
// offered a field it may not use can fill it, and the check that refuses it runs after the whole step.
|
|
1139
|
+
schemaNow: (schema) => knockoutAssessSchemaFor(schema, process.env.CLEAROTRON_BAND_RUN_DIR),
|
|
1137
1140
|
description:
|
|
1138
1141
|
"Hand back THIS CHUNK's rated assessment as VALUES. The driver serializes knockout-assess-<n>.json, " +
|
|
1139
1142
|
"so you never format JSON and never write the file. WHICH CHUNK IS NOT YOURS TO SAY — the driver " +
|
|
@@ -1392,7 +1395,7 @@ serve({
|
|
|
1392
1395
|
},
|
|
1393
1396
|
batch: {
|
|
1394
1397
|
type: "object",
|
|
1395
|
-
required: ["productContext", "inUseAs",
|
|
1398
|
+
required: ["productContext", "inUseAs"], // places are per name (ruling 570): the batch list is optional, as in the acceptor and the manual
|
|
1396
1399
|
properties: {
|
|
1397
1400
|
productContext: { type: "string", description: "One sentence: what the batch is for. Every mark's contextFraming is read against it." },
|
|
1398
1401
|
inUseAs: { type: "string", description: "The kinds of use off the register that could conflict in this client's field, as one phrase completing \"Is this name already in use as …?\". The places the batch is searched on include the sites where these uses are listed." },
|
|
@@ -1439,3 +1442,27 @@ serve({
|
|
|
1439
1442
|
handler: record_knockout_frame,
|
|
1440
1443
|
}],
|
|
1441
1444
|
});
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* The knockout save tool's schema for the run this server serves. The framework's `inputs` fields are
|
|
1448
|
+
* offered only where the run's framework states a method, which is when its frozen copy
|
|
1449
|
+
* (`_driver/framework-method.json`) exists: a run with no method has none, since the mint removes it. A
|
|
1450
|
+
* session offered a field it may not use can fill it, and the check that refuses it runs only after the
|
|
1451
|
+
* whole step. With no run to read, the schema is the one declared. Read when the tools are listed.
|
|
1452
|
+
*/
|
|
1453
|
+
export function knockoutAssessSchemaFor(schema, runDir) {
|
|
1454
|
+
if (!runDir || existsSync(driverDir(String(runDir), FROZEN_METHOD_FILE))) return schema;
|
|
1455
|
+
const strip = (s) => {
|
|
1456
|
+
if (Array.isArray(s)) return s.map(strip);
|
|
1457
|
+
if (!s || typeof s !== "object") return s;
|
|
1458
|
+
const out = {};
|
|
1459
|
+
for (const [k, v] of Object.entries(s)) {
|
|
1460
|
+
if (k === "properties" && v && typeof v === "object" && !Array.isArray(v)) {
|
|
1461
|
+
out.properties = Object.fromEntries(Object.entries(v).filter(([name]) => name !== "inputs").map(([name, sub]) => [name, strip(sub)]));
|
|
1462
|
+
} else if (k === "required" && Array.isArray(v)) out.required = v.filter((name) => name !== "inputs");
|
|
1463
|
+
else out[k] = strip(v);
|
|
1464
|
+
}
|
|
1465
|
+
return out;
|
|
1466
|
+
};
|
|
1467
|
+
return strip(schema);
|
|
1468
|
+
}
|