onto-mcp 0.4.12 → 0.4.13
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/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +137 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +169 -8
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +575 -24
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +75 -3
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +19 -8
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +282 -29
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +135 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +188 -47
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +35 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +17 -0
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +83 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +18 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +14 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +304 -18
- package/dist/core-runtime/reconstruct/maturation-validation.js +1231 -53
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +107 -3
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +27 -0
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +87 -3
- package/dist/core-runtime/reconstruct/post-seed-validation.js +118 -5
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +117 -2
- package/dist/core-runtime/reconstruct/record.js +42 -0
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +16 -0
- package/dist/core-runtime/reconstruct/run-control-validation.js +1011 -34
- package/dist/core-runtime/reconstruct/run.js +5198 -536
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +54 -3
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +105 -2
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +41 -0
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +71 -183
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +37 -1
- package/dist/core-runtime/reconstruct/terminal-validation.js +132 -2
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +217 -5
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +12 -5
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +18 -3
- package/settings.example.json +77 -16
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `onto seats` — print a read-only inventory of every LLM model seat the
|
|
3
|
+
* runtime can dispatch, resolved against the settings.json chain for the
|
|
4
|
+
* current project. Discovery only: it writes nothing and changes no behaviour.
|
|
5
|
+
*/
|
|
6
|
+
import { resolveSettingsChain } from "../discovery/settings-chain.js";
|
|
7
|
+
import { collectSeatInventory, renderSeatInventoryTable, } from "../discovery/seat-inventory.js";
|
|
8
|
+
export async function runSeats(argv) {
|
|
9
|
+
const asJson = argv.includes("--json");
|
|
10
|
+
const settings = await resolveSettingsChain(process.cwd(), process.cwd());
|
|
11
|
+
const rows = collectSeatInventory(settings);
|
|
12
|
+
if (asJson) {
|
|
13
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
console.log(renderSeatInventoryTable(rows));
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
@@ -129,8 +129,11 @@ export function parseRuntimeIssueStanceSchemaContext(rawPacketText) {
|
|
|
129
129
|
const lensId = requireStringValue(finding.lens_id, `Runtime Issue Stance Input Projection.finding_summaries[${index}].lens_id`);
|
|
130
130
|
if (lensId !== requestedLensId)
|
|
131
131
|
continue;
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
// Issue-strict parity with the on-disk validator (issue-artifact-runtime
|
|
133
|
+
// buildStanceEvidenceRefs): the lens's RAW finding evidence refs are a
|
|
134
|
+
// per-lens union, but finding-ledger#findingId anchors are registered ONLY
|
|
135
|
+
// per issue (surface_finding_ids loop below) — a cross-issue anchor that
|
|
136
|
+
// passed submit here used to be rejected at the on-disk re-validation.
|
|
134
137
|
for (const evidenceRef of stringArray(finding.evidence_refs, `Runtime Issue Stance Input Projection.finding_summaries[${index}].evidence_refs`)) {
|
|
135
138
|
requestedLensFindingRefs.add(evidenceRef);
|
|
136
139
|
}
|
|
@@ -173,6 +176,10 @@ export function parseRuntimeIssueStanceSchemaContext(rawPacketText) {
|
|
|
173
176
|
for (const findingId of stringArray(issue.surface_finding_ids, `Runtime Issue Stance Input Projection.issues[${index}].surface_finding_ids`)) {
|
|
174
177
|
addPromptRefVariants({ refs, artifactRef: findingLedgerRef, anchor: findingId });
|
|
175
178
|
for (const relationId of relationRefsByFindingId.get(findingId) ?? []) {
|
|
179
|
+
// Bare relation id parity with the on-disk validator, which registers
|
|
180
|
+
// the raw id for graph-endpoint relations — a bare `rel-N` submit used
|
|
181
|
+
// to be rejected here but accepted on disk (live-observed class).
|
|
182
|
+
refs.add(relationId);
|
|
176
183
|
addPromptRefVariants({
|
|
177
184
|
refs,
|
|
178
185
|
artifactRef: relationGraphRef,
|
|
@@ -189,6 +196,7 @@ export function parseRuntimeIssueStanceSchemaContext(rawPacketText) {
|
|
|
189
196
|
});
|
|
190
197
|
}
|
|
191
198
|
for (const relationId of dependencyRelationRefsByIssueId.get(issueId) ?? []) {
|
|
199
|
+
refs.add(relationId); // bare-id parity with the on-disk dependency loop
|
|
192
200
|
addPromptRefVariants({
|
|
193
201
|
refs,
|
|
194
202
|
artifactRef: relationGraphRef,
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded unit resubmit — pure decision/projection logic (no I/O, no LLM
|
|
3
|
+
* calls). Unit-agnostic: the error-spec construction and packet projection are
|
|
4
|
+
* shared across review units; per-unit failure classifiers select which
|
|
5
|
+
* validation rejections are correctable.
|
|
6
|
+
*
|
|
7
|
+
* When `review.execution.retry.resubmit.enabled` is on, a unit whose structured
|
|
8
|
+
* submit was rejected by a deterministic evidence-ref whitelist is re-requested
|
|
9
|
+
* through the unit's existing retry budget with an error spec injected into its
|
|
10
|
+
* prompt packet, instead of retrying blind.
|
|
11
|
+
*
|
|
12
|
+
* Wired units (§4-6a, §4-2c/2-A):
|
|
13
|
+
* - issue-stance: `issue_evidence_refs` whitelist. Exhaustion demotes the unit
|
|
14
|
+
* to complete-with-failure (degradation-summary, disclosed non-blockingly)
|
|
15
|
+
* rather than halting — unless the same validation class fails a strict
|
|
16
|
+
* majority of stance units (structural defect → whole-run halt with
|
|
17
|
+
* `halt_reason = correlated_validation: …`). The demotion/correlated
|
|
18
|
+
* machinery is stance-only and reads the terminal outcome; the rare-poison
|
|
19
|
+
* stance gate therefore retries only correctable validation failures and does
|
|
20
|
+
* not reinterpret a later infra terminal failure as validation.
|
|
21
|
+
* - deliberation-response: `allowed_evidence_refs` whitelist. Exhaustion reuses
|
|
22
|
+
* deliberation's existing non-halting degrade (unavailable-completion), so it
|
|
23
|
+
* needs no demotion/correlated machinery. Only the submit-time rejection is
|
|
24
|
+
* correctable; on-disk rejections run post-pool and degrade.
|
|
25
|
+
* - synthesis-response: `allowed_source_refs` whitelist (§4-2c/2-A). Its
|
|
26
|
+
* rejection message always carries `source_refs_used` → substring-classified
|
|
27
|
+
* output_contract, so its in-loop resubmit depends on the structural retry
|
|
28
|
+
* gate. Exhaustion reuses synthesis's existing non-halting degrade
|
|
29
|
+
* (completeUnavailableSynthesisResponseUnit); no demotion/correlated
|
|
30
|
+
* machinery.
|
|
31
|
+
*
|
|
32
|
+
* This module owns the deterministic parts: failure classification, error
|
|
33
|
+
* spec construction, idempotent packet projection, and the correlated
|
|
34
|
+
* escalation decision. File I/O and dispatch stay in
|
|
35
|
+
* run-review-prompt-execution.
|
|
36
|
+
*
|
|
37
|
+
* Design: development-records/design/20260704-review-unit-resubmit-and-limit-breaker-design.md (설계 A),
|
|
38
|
+
* development-records/design/20260707-s4-6a-deliberation-resubmit-design.md (§4-6a 확대).
|
|
39
|
+
*/
|
|
40
|
+
/** Marker delimiting the runtime-owned error spec section inside a packet.
|
|
41
|
+
* Replacement is idempotent: a later resubmit replaces the section instead of
|
|
42
|
+
* stacking a second copy. */
|
|
43
|
+
export const RESUBMIT_ERROR_SPEC_BEGIN = "<!-- onto:resubmit-error-spec:begin -->";
|
|
44
|
+
export const RESUBMIT_ERROR_SPEC_END = "<!-- onto:resubmit-error-spec:end -->";
|
|
45
|
+
/** halt_reason prefix for the correlated-failure whole-run halt (F-A2). */
|
|
46
|
+
export const CORRELATED_VALIDATION_HALT_REASON = "correlated_validation";
|
|
47
|
+
/** Submit-time validator throw site
|
|
48
|
+
* (structured-output-tools `normalizeIssueStanceResponseSubmitArgs`). */
|
|
49
|
+
const SUBMIT_UNSUPPORTED_REF_PATTERN = /submit_issue_stance_response\.stances\[(\d+)\]\.evidence_refs contains unsupported ref for (\S+): (.*)$/m;
|
|
50
|
+
/** On-disk per-lens validator throw site
|
|
51
|
+
* (issue-artifact-runtime `validateIssueStanceResponseObject`). */
|
|
52
|
+
const ON_DISK_UNSUPPORTED_REF_PATTERN = /issue-stance response for issue (\S+) and lens \S+ references unsupported evidence: (.*)$/m;
|
|
53
|
+
/**
|
|
54
|
+
* Classify a unit failure message as an issue-stance unsupported-evidence-ref
|
|
55
|
+
* validation rejection. Anchors on BOTH runtime-owned throw sites: the
|
|
56
|
+
* submit-time whitelist (per-issue union set) and the stricter on-disk
|
|
57
|
+
* per-lens re-validation — either rejection is the same failure class for
|
|
58
|
+
* promotion purposes. Message text is not guaranteed to survive the worker
|
|
59
|
+
* adapters' stderr (see readFrozenUnsupportedRefViolation for the structural
|
|
60
|
+
* freeze-file source); this classifier is the in-process/message half.
|
|
61
|
+
* Returns null for every other failure class so infra failures (timeout,
|
|
62
|
+
* transport, empty output) keep their current semantics.
|
|
63
|
+
*/
|
|
64
|
+
export function classifyUnsupportedEvidenceRefFailure(message) {
|
|
65
|
+
const submitMatch = SUBMIT_UNSUPPORTED_REF_PATTERN.exec(message);
|
|
66
|
+
if (submitMatch) {
|
|
67
|
+
const stanceIndex = Number.parseInt(submitMatch[1], 10);
|
|
68
|
+
const issueId = submitMatch[2];
|
|
69
|
+
const evidenceRef = submitMatch[3].trim();
|
|
70
|
+
if (!Number.isFinite(stanceIndex) || issueId.length === 0)
|
|
71
|
+
return null;
|
|
72
|
+
return { stanceIndex, issueId, evidenceRef };
|
|
73
|
+
}
|
|
74
|
+
const onDiskMatch = ON_DISK_UNSUPPORTED_REF_PATTERN.exec(message);
|
|
75
|
+
if (onDiskMatch) {
|
|
76
|
+
const issueId = onDiskMatch[1];
|
|
77
|
+
const evidenceRef = onDiskMatch[2].trim();
|
|
78
|
+
if (issueId.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return { stanceIndex: null, issueId, evidenceRef };
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
export function isUnsupportedEvidenceRefFailureMessage(message) {
|
|
85
|
+
return (typeof message === "string" &&
|
|
86
|
+
classifyUnsupportedEvidenceRefFailure(message) !== null);
|
|
87
|
+
}
|
|
88
|
+
/** Submit-time throw site (structured-output-tools `assertAllowedRefs` via
|
|
89
|
+
* `normalizeIssueDeliberationResponseSubmitArgs`). This is the only deliberation
|
|
90
|
+
* evidence_refs rejection reachable by resubmit: the executor throws it during
|
|
91
|
+
* the dispatch (retryable) and freezes it as the salvage input. The on-disk
|
|
92
|
+
* validator (`validateIssueDeliberationResponseObject`) runs post-pool and is
|
|
93
|
+
* caught into a non-halting degrade — it never re-enters the retry loop — so it
|
|
94
|
+
* is deliberately not anchored here. */
|
|
95
|
+
const SUBMIT_DELIBERATION_UNSUPPORTED_REF_PATTERN = /submit_issue_deliberation_response\.evidence_refs contains unsupported ref: (.*)$/m;
|
|
96
|
+
/**
|
|
97
|
+
* Classify a unit failure message as a submit-time deliberation-response
|
|
98
|
+
* unsupported-evidence-ref rejection. Returns null for every other class so
|
|
99
|
+
* infra failures — and on-disk rejections, which degrade rather than resubmit —
|
|
100
|
+
* keep their current semantics.
|
|
101
|
+
*/
|
|
102
|
+
export function classifyDeliberationUnsupportedEvidenceRefFailure(message) {
|
|
103
|
+
const submitMatch = SUBMIT_DELIBERATION_UNSUPPORTED_REF_PATTERN.exec(message);
|
|
104
|
+
if (submitMatch) {
|
|
105
|
+
const evidenceRef = submitMatch[1].trim();
|
|
106
|
+
if (evidenceRef.length === 0)
|
|
107
|
+
return null;
|
|
108
|
+
return { evidenceRef };
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
/** Submit-time throw sites in `normalizeIssueSynthesisResponseSubmitArgs`
|
|
113
|
+
* (structured-output-tools): `assertAllowedRefs` for a bad ref, and the
|
|
114
|
+
* "must include at least one" guard for an all-invalid set. The empty
|
|
115
|
+
* allowed-set rejection ("cannot validate … because allowed_source_refs is
|
|
116
|
+
* empty") is deliberately NOT matched — an empty allowed set is a runtime/context
|
|
117
|
+
* condition a resubmit cannot correct, so it stays non-retryable. */
|
|
118
|
+
const SUBMIT_SYNTHESIS_UNSUPPORTED_REF_PATTERN = /submit_issue_synthesis_response\.source_refs_used contains unsupported ref: (.*)$/m;
|
|
119
|
+
const SUBMIT_SYNTHESIS_MISSING_ALLOWED_REF_PATTERN = /submit_issue_synthesis_response\.source_refs_used must include at least one allowed source ref\./;
|
|
120
|
+
/**
|
|
121
|
+
* Classify a unit failure message as a submit-time synthesis-response
|
|
122
|
+
* unsupported/missing source-ref rejection. Returns null for every other class
|
|
123
|
+
* (including the non-correctable empty-allowed-set rejection) so infra failures
|
|
124
|
+
* keep their current semantics.
|
|
125
|
+
*/
|
|
126
|
+
export function classifySynthesisUnsupportedSourceRefFailure(message) {
|
|
127
|
+
const unsupported = SUBMIT_SYNTHESIS_UNSUPPORTED_REF_PATTERN.exec(message);
|
|
128
|
+
if (unsupported) {
|
|
129
|
+
const sourceRef = unsupported[1].trim();
|
|
130
|
+
if (sourceRef.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
return { sourceRef };
|
|
133
|
+
}
|
|
134
|
+
if (SUBMIT_SYNTHESIS_MISSING_ALLOWED_REF_PATTERN.test(message)) {
|
|
135
|
+
return { sourceRef: null };
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Neutralize the runtime-owned section markers inside a value before it is
|
|
141
|
+
* interpolated into the spec. The rejected `evidence_ref` is model-controlled;
|
|
142
|
+
* a value literally containing an END (or BEGIN) marker would land as a
|
|
143
|
+
* premature delimiter inside the spec and fool the idempotent strip
|
|
144
|
+
* (`indexOf(END, begin)` matches the injected marker first), orphaning spec
|
|
145
|
+
* fragments and breaking the at-most-one-spec invariant across resubmit rounds.
|
|
146
|
+
* Marker-free values (every normal ref) are returned unchanged, so this is a
|
|
147
|
+
* no-op for the common path. Applied to every dynamic interpolation for
|
|
148
|
+
* defense-in-depth even though only the ref is model-controlled today.
|
|
149
|
+
*/
|
|
150
|
+
function neutralizeSpecMarkers(value) {
|
|
151
|
+
return value
|
|
152
|
+
.split(RESUBMIT_ERROR_SPEC_BEGIN)
|
|
153
|
+
.join("(spec marker removed)")
|
|
154
|
+
.split(RESUBMIT_ERROR_SPEC_END)
|
|
155
|
+
.join("(spec marker removed)");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Render the bounded error spec for a resubmit attempt. Contains only the
|
|
159
|
+
* violation and the allowed set for the offending unit — never the failed
|
|
160
|
+
* output itself (design: no full-output retransmission; the model rebuilds
|
|
161
|
+
* the complete payload from the unchanged packet body). Unit-agnostic: the
|
|
162
|
+
* `unit` descriptor selects the submit tool, rejected-unit label, allowed-set
|
|
163
|
+
* header, and closing instruction; stance is the default and unchanged.
|
|
164
|
+
*/
|
|
165
|
+
export function buildResubmitErrorSpec(args) {
|
|
166
|
+
const { violation } = args;
|
|
167
|
+
const evidenceRef = neutralizeSpecMarkers(violation.evidenceRef);
|
|
168
|
+
const issueId = neutralizeSpecMarkers(violation.issueId);
|
|
169
|
+
// Field-name vocabulary differs by unit: stance/deliberation reject
|
|
170
|
+
// `evidence_refs`, synthesis rejects `source_refs_used`. Defaulting to the
|
|
171
|
+
// evidence_refs vocabulary keeps stance/deliberation output byte-identical.
|
|
172
|
+
const refFieldPlural = args.unit?.kind === "synthesis" ? "source_refs_used" : "evidence_refs";
|
|
173
|
+
const refFieldSingular = args.unit?.kind === "synthesis" ? "source ref" : "evidence_ref";
|
|
174
|
+
const allowedBlock = args.allowedEvidenceRefs.length > 0
|
|
175
|
+
? args.allowedEvidenceRefs
|
|
176
|
+
.map((ref) => `- ${neutralizeSpecMarkers(ref)}`)
|
|
177
|
+
.join("\n")
|
|
178
|
+
: `- (none — omit ${refFieldPlural} entries you cannot support)`;
|
|
179
|
+
const spec = args.unit?.kind === "deliberation"
|
|
180
|
+
? {
|
|
181
|
+
submitTool: "submit_issue_deliberation_response",
|
|
182
|
+
rejectedLine: `- rejected: deliberation for issue_id: ${issueId}, lens_id: ${neutralizeSpecMarkers(args.unit.lensId)}`,
|
|
183
|
+
allowedHeader: "- allowed evidence_refs:",
|
|
184
|
+
closing: [
|
|
185
|
+
"Every evidence_ref must come from the allowed set in the schema",
|
|
186
|
+
"context above. Resubmit the full deliberation response, not only the",
|
|
187
|
+
"rejected entry.",
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
: args.unit?.kind === "synthesis"
|
|
191
|
+
? {
|
|
192
|
+
submitTool: "submit_issue_synthesis_response",
|
|
193
|
+
rejectedLine: `- rejected: synthesis response for issue_id: ${issueId}`,
|
|
194
|
+
allowedHeader: "- allowed source_refs_used:",
|
|
195
|
+
closing: [
|
|
196
|
+
"Every source ref must come from the allowed set in the schema",
|
|
197
|
+
"context above. Resubmit the full synthesis response, not only the",
|
|
198
|
+
"rejected entry.",
|
|
199
|
+
],
|
|
200
|
+
}
|
|
201
|
+
: {
|
|
202
|
+
submitTool: "submit_issue_stance_response",
|
|
203
|
+
rejectedLine: `- rejected stance: ${violation.stanceIndex !== null
|
|
204
|
+
? `stances[${violation.stanceIndex}] (issue_id: ${issueId})`
|
|
205
|
+
: `stance for issue_id: ${issueId}`}`,
|
|
206
|
+
allowedHeader: `- allowed evidence_refs for ${issueId}:`,
|
|
207
|
+
closing: [
|
|
208
|
+
"Every stance's evidence_refs must come from that issue's allowed set in",
|
|
209
|
+
"the schema context above. Resubmit the full stances array, not only the",
|
|
210
|
+
"rejected entry.",
|
|
211
|
+
],
|
|
212
|
+
};
|
|
213
|
+
// A specific offending ref → name it; an empty ref (synthesis "must include at
|
|
214
|
+
// least one" rejection) → instruct citing from the allowed set instead.
|
|
215
|
+
const refLine = evidenceRef.length > 0
|
|
216
|
+
? `- unsupported ${refFieldSingular}: ${evidenceRef}`
|
|
217
|
+
: `- you cited no allowed ${refFieldSingular}; include at least one from the allowed set below`;
|
|
218
|
+
return [
|
|
219
|
+
RESUBMIT_ERROR_SPEC_BEGIN,
|
|
220
|
+
"",
|
|
221
|
+
`## Resubmit required: ${refFieldPlural} validation rejected (attempt ${args.resubmitAttempt})`,
|
|
222
|
+
"",
|
|
223
|
+
`Your previous ${spec.submitTool} call was rejected by`,
|
|
224
|
+
"deterministic validation. Do not apologize or explain; call the submit",
|
|
225
|
+
"tool again with a complete corrected payload.",
|
|
226
|
+
"",
|
|
227
|
+
spec.rejectedLine,
|
|
228
|
+
refLine,
|
|
229
|
+
spec.allowedHeader,
|
|
230
|
+
allowedBlock,
|
|
231
|
+
"",
|
|
232
|
+
...spec.closing,
|
|
233
|
+
"",
|
|
234
|
+
RESUBMIT_ERROR_SPEC_END,
|
|
235
|
+
].join("\n");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Project the error spec into a packet idempotently: strips a previous
|
|
239
|
+
* runtime-owned spec section (if any) before appending the new one, so the
|
|
240
|
+
* packet carries at most one spec regardless of how many resubmit rounds ran.
|
|
241
|
+
*/
|
|
242
|
+
export function applyResubmitErrorSpecToPacket(packetText, errorSpec) {
|
|
243
|
+
const stripped = stripResubmitErrorSpec(packetText);
|
|
244
|
+
return `${stripped.trimEnd()}\n\n${errorSpec.trimEnd()}\n`;
|
|
245
|
+
}
|
|
246
|
+
function stripResubmitErrorSpec(packetText) {
|
|
247
|
+
const begin = packetText.indexOf(RESUBMIT_ERROR_SPEC_BEGIN);
|
|
248
|
+
if (begin === -1)
|
|
249
|
+
return packetText;
|
|
250
|
+
const end = packetText.indexOf(RESUBMIT_ERROR_SPEC_END, begin);
|
|
251
|
+
if (end === -1)
|
|
252
|
+
return packetText.slice(0, begin);
|
|
253
|
+
return (packetText.slice(0, begin) +
|
|
254
|
+
packetText.slice(end + RESUBMIT_ERROR_SPEC_END.length));
|
|
255
|
+
}
|
|
256
|
+
export function packetHasResubmitErrorSpec(packetText) {
|
|
257
|
+
return packetText.includes(RESUBMIT_ERROR_SPEC_BEGIN);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Correlated escalation decision: the same validation class failing a strict
|
|
261
|
+
* majority (>50%) of stance units signals a structural defect (prompt,
|
|
262
|
+
* schema, or context assembly), not per-unit output noise — whole-run halt
|
|
263
|
+
* stays the correct promotion there.
|
|
264
|
+
*/
|
|
265
|
+
export function correlatedValidationExceeded(args) {
|
|
266
|
+
if (args.totalUnitCount <= 0)
|
|
267
|
+
return false;
|
|
268
|
+
return args.validationFailedUnitCount * 2 > args.totalUnitCount;
|
|
269
|
+
}
|
|
@@ -115,8 +115,9 @@ function requireRecord(value, label) {
|
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
117
|
* Normalize a JSON Schema into the strict shape external workers require: every
|
|
118
|
-
* object's properties become `required
|
|
119
|
-
*
|
|
118
|
+
* object's properties become `required`, and homogeneous `anyOf` object rows are
|
|
119
|
+
* collapsed to a single provider-compatible object. Runtime submit validation
|
|
120
|
+
* remains the authority for issue-specific allowed sets.
|
|
120
121
|
*/
|
|
121
122
|
export function toWorkerStructuredOutputSchema(schema) {
|
|
122
123
|
if (Array.isArray(schema)) {
|
|
@@ -129,12 +130,89 @@ export function toWorkerStructuredOutputSchema(schema) {
|
|
|
129
130
|
for (const [key, value] of Object.entries(schema)) {
|
|
130
131
|
normalized[key] = toWorkerStructuredOutputSchema(value);
|
|
131
132
|
}
|
|
133
|
+
const collapsedAnyOf = collapseHomogeneousAnyOf(normalized);
|
|
134
|
+
if (collapsedAnyOf)
|
|
135
|
+
return collapsedAnyOf;
|
|
132
136
|
const properties = normalized.properties;
|
|
133
137
|
if (properties && typeof properties === "object" && !Array.isArray(properties)) {
|
|
134
138
|
normalized.required = Object.keys(properties);
|
|
135
139
|
}
|
|
136
140
|
return normalized;
|
|
137
141
|
}
|
|
142
|
+
function collapseHomogeneousAnyOf(schema) {
|
|
143
|
+
const variants = schema.anyOf;
|
|
144
|
+
if (!Array.isArray(variants) || variants.length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
const records = variants.filter((variant) => !!variant && typeof variant === "object" && !Array.isArray(variant));
|
|
147
|
+
if (records.length !== variants.length)
|
|
148
|
+
return null;
|
|
149
|
+
if (!records.every((record) => record.type === "object"))
|
|
150
|
+
return null;
|
|
151
|
+
const propertySets = records.map((record) => record.properties &&
|
|
152
|
+
typeof record.properties === "object" &&
|
|
153
|
+
!Array.isArray(record.properties)
|
|
154
|
+
? Object.keys(record.properties).sort()
|
|
155
|
+
: null);
|
|
156
|
+
const firstSet = propertySets[0];
|
|
157
|
+
if (!firstSet)
|
|
158
|
+
return null;
|
|
159
|
+
if (!propertySets.every((set) => !!set &&
|
|
160
|
+
set.length === firstSet.length &&
|
|
161
|
+
set.every((key, index) => key === firstSet[index]))) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const mergedProperties = {};
|
|
165
|
+
for (const key of firstSet) {
|
|
166
|
+
const merged = mergeHomogeneousProperty(records.map((record) => record.properties[key]));
|
|
167
|
+
if (merged === null)
|
|
168
|
+
return null;
|
|
169
|
+
mergedProperties[key] = merged;
|
|
170
|
+
}
|
|
171
|
+
const collapsed = {
|
|
172
|
+
type: "object",
|
|
173
|
+
additionalProperties: records.every((record) => record.additionalProperties === false)
|
|
174
|
+
? false
|
|
175
|
+
: schema.additionalProperties,
|
|
176
|
+
properties: mergedProperties,
|
|
177
|
+
required: Object.keys(mergedProperties),
|
|
178
|
+
};
|
|
179
|
+
if (typeof schema.description === "string") {
|
|
180
|
+
collapsed.description = schema.description;
|
|
181
|
+
}
|
|
182
|
+
return collapsed;
|
|
183
|
+
}
|
|
184
|
+
function mergeHomogeneousProperty(values) {
|
|
185
|
+
const records = values.filter((value) => !!value && typeof value === "object" && !Array.isArray(value));
|
|
186
|
+
if (records.length !== values.length)
|
|
187
|
+
return sameJson(values) ? values[0] : null;
|
|
188
|
+
const first = records[0];
|
|
189
|
+
if (!first)
|
|
190
|
+
return null;
|
|
191
|
+
const comparable = records.map(({ enum: _enum, description: _description, ...rest }) => rest);
|
|
192
|
+
if (sameJson(comparable)) {
|
|
193
|
+
const enumValues = records.flatMap((record) => Array.isArray(record.enum) ? record.enum : []);
|
|
194
|
+
const base = {
|
|
195
|
+
...comparable[0],
|
|
196
|
+
...(typeof first.description === "string"
|
|
197
|
+
? { description: first.description }
|
|
198
|
+
: {}),
|
|
199
|
+
};
|
|
200
|
+
if (enumValues.length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
...base,
|
|
203
|
+
enum: Array.from(new Set(enumValues.map((value) => JSON.stringify(value)))).map((value) => JSON.parse(value)),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return base;
|
|
207
|
+
}
|
|
208
|
+
return sameJson(records) ? first : null;
|
|
209
|
+
}
|
|
210
|
+
function sameJson(values) {
|
|
211
|
+
if (values.length === 0)
|
|
212
|
+
return true;
|
|
213
|
+
const first = JSON.stringify(values[0]);
|
|
214
|
+
return values.every((value) => JSON.stringify(value) === first);
|
|
215
|
+
}
|
|
138
216
|
/**
|
|
139
217
|
* Build the submit-tool JSON Schema and the submission state for an output
|
|
140
218
|
* format, without writing any file. Codex writes this to disk for its
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import fsSync from "node:fs";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { resolveClaudeBin } from "../llm/claude-bin.js";
|
|
4
6
|
const CODEX_ENV_SIGNALS = ["CODEX_THREAD_ID", "CODEX_CI"];
|
|
5
7
|
/** True when the current process is running under a Codex-owned session. */
|
|
6
8
|
export function detectCodexEnvSignal() {
|
|
@@ -31,40 +33,87 @@ export function detectCodexBinaryAvailable() {
|
|
|
31
33
|
return false;
|
|
32
34
|
return fsSync.existsSync(path.join(os.homedir(), ".codex", "auth.json"));
|
|
33
35
|
}
|
|
34
|
-
/** OAuth credential filenames Claude Code
|
|
36
|
+
/** OAuth credential filenames older Claude Code builds write under its config
|
|
37
|
+
* dir. Newer builds store the credential in the OS keychain (macOS) instead, so
|
|
38
|
+
* this file probe is the backward-compatible fallback, not the primary check. */
|
|
35
39
|
const CLAUDE_OAUTH_CREDENTIAL_FILENAMES = [
|
|
36
40
|
".credentials.json",
|
|
37
41
|
".oauth-token",
|
|
38
42
|
];
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
/** Bound on the `claude auth status` probe so a wedged CLI cannot hang review
|
|
44
|
+
* profile resolution. The command is a local credential read (~200ms observed),
|
|
45
|
+
* so this is a generous ceiling, not a normal latency. */
|
|
46
|
+
const CLAUDE_AUTH_STATUS_TIMEOUT_MS = 5000;
|
|
47
|
+
function claudeConfigDir(env) {
|
|
48
|
+
const configured = env.CLAUDE_CONFIG_DIR;
|
|
41
49
|
if (typeof configured === "string" && configured.trim().length > 0) {
|
|
42
50
|
return configured.trim();
|
|
43
51
|
}
|
|
44
52
|
return path.join(os.homedir(), ".claude");
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
* Mirrors {@link detectCodexBinaryAvailable}: both the executable and an OAuth
|
|
50
|
-
* credential are required so the external OAuth worker (`claude_code` adapter)
|
|
51
|
-
* route stays fail-loud when the host-bound worker is unavailable. The config
|
|
52
|
-
* dir honors CLAUDE_CONFIG_DIR; the credential filename is tolerant of the
|
|
53
|
-
* known Claude Code variants.
|
|
54
|
-
*/
|
|
55
|
-
export function detectClaudeBinaryAvailable() {
|
|
56
|
-
const pathEnv = process.env.PATH ?? "";
|
|
57
|
-
let claudeOnPath = false;
|
|
54
|
+
function claudeBinaryOnPath(env) {
|
|
55
|
+
const pathEnv = env.PATH ?? "";
|
|
58
56
|
for (const dir of pathEnv.split(path.delimiter)) {
|
|
59
57
|
if (!dir)
|
|
60
58
|
continue;
|
|
61
|
-
if (fsSync.existsSync(path.join(dir, "claude")))
|
|
62
|
-
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
59
|
+
if (fsSync.existsSync(path.join(dir, "claude")))
|
|
60
|
+
return true;
|
|
65
61
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
function hasClaudeCredentialFile(env) {
|
|
65
|
+
const configDir = claudeConfigDir(env);
|
|
69
66
|
return CLAUDE_OAUTH_CREDENTIAL_FILENAMES.some((name) => fsSync.existsSync(path.join(configDir, name)));
|
|
70
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Parse the JSON `claude auth status` prints. Returns its `loggedIn` boolean
|
|
70
|
+
* when the output is the recognized shape, or null otherwise (older CLI without
|
|
71
|
+
* the command, an error, or a format change) so the caller can fall back to the
|
|
72
|
+
* file-credential probe. Pure — unit-testable without spawning the CLI.
|
|
73
|
+
*/
|
|
74
|
+
export function parseClaudeAuthLoggedIn(stdout) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(stdout);
|
|
77
|
+
return typeof parsed?.loggedIn === "boolean" ? parsed.loggedIn : null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* True when the Claude Code worker path can be used from this process.
|
|
85
|
+
*
|
|
86
|
+
* The authority on whether the worker can authenticate is the claude CLI
|
|
87
|
+
* itself: `claude auth status` reports login across every credential store
|
|
88
|
+
* (macOS Keychain, credential file, or env token) and every OS — which a
|
|
89
|
+
* file-only probe cannot (a keychain-backed login leaves no credential file).
|
|
90
|
+
* The worker spawns the same CLI, so this checks the exact auth path the run
|
|
91
|
+
* will use. Binary resolution mirrors the executor via {@link resolveClaudeBin}
|
|
92
|
+
* (ONTO_CLAUDE_BIN override + PATH).
|
|
93
|
+
*
|
|
94
|
+
* Falls back to the legacy file-credential probe when the CLI cannot answer
|
|
95
|
+
* (missing binary, timeout, or a build too old for `auth status`), so setups
|
|
96
|
+
* that resolved before this change keep resolving (backward compatible). Unlike
|
|
97
|
+
* {@link detectCodexBinaryAvailable}, which reads codex's on-disk
|
|
98
|
+
* `~/.codex/auth.json` directly, claude's credential is not reliably a file —
|
|
99
|
+
* hence the CLI delegation.
|
|
100
|
+
*/
|
|
101
|
+
export function detectClaudeBinaryAvailable(env = process.env) {
|
|
102
|
+
const claudeBin = resolveClaudeBin(env);
|
|
103
|
+
try {
|
|
104
|
+
const stdout = execFileSync(claudeBin, ["auth", "status"], {
|
|
105
|
+
env,
|
|
106
|
+
timeout: CLAUDE_AUTH_STATUS_TIMEOUT_MS,
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
109
|
+
});
|
|
110
|
+
const loggedIn = parseClaudeAuthLoggedIn(stdout);
|
|
111
|
+
if (loggedIn !== null)
|
|
112
|
+
return loggedIn;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// ENOENT (no binary), timeout, non-zero exit, or a CLI too old to support
|
|
116
|
+
// `auth status` — fall through to the legacy file-credential probe.
|
|
117
|
+
}
|
|
118
|
+
return claudeBinaryOnPath(env) && hasClaudeCredentialFile(env);
|
|
119
|
+
}
|