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,138 @@
|
|
|
1
|
+
import { reviewMaterialGoals, } from "../target-material-kind.js";
|
|
2
|
+
/**
|
|
3
|
+
* SEALED authority: obligations whose backing evidence intrinsically spans sections
|
|
4
|
+
* (cross-section), so independent sharding would destroy it (🔴 ILC-2). Keyed by obligation
|
|
5
|
+
* identity, decoupled from the shardability declaration. Editing this set is the ONLY way to
|
|
6
|
+
* change an obligation's relational status — a conspicuous, test-locked change
|
|
7
|
+
* (obligation-shardability.invariant.test.ts fixes membership), which is exactly what makes the
|
|
8
|
+
* gate fail-closed against a silent relational→independent weakening.
|
|
9
|
+
*
|
|
10
|
+
* `cross_sheet_reference_integrity` is the sole relational spreadsheet obligation: its backing
|
|
11
|
+
* (formula_patterns[].cross_sheet_refs + cross_sheet_key_overlap) is inherently cross-sheet.
|
|
12
|
+
* `formula_integrity` is NOT relational — its cross-sheet evidence is owned by the separate
|
|
13
|
+
* `cross_sheet_reference_integrity` obligation (computeBackedGoals splits them).
|
|
14
|
+
*/
|
|
15
|
+
const RELATIONAL_OBLIGATIONS = new Set([
|
|
16
|
+
"cross_sheet_reference_integrity",
|
|
17
|
+
]);
|
|
18
|
+
/** True when an obligation carries cross-section evidence (sealed ground truth). Pure. */
|
|
19
|
+
export function isRelationalObligation(obligation) {
|
|
20
|
+
return RELATIONAL_OBLIGATIONS.has(obligation);
|
|
21
|
+
}
|
|
22
|
+
/** Derived projection: a mandatory cross-section seam is required to shard this obligation.
|
|
23
|
+
* Deterministic from `material_shardability` — never stored. */
|
|
24
|
+
export function requiresSeam(declaration) {
|
|
25
|
+
return declaration.material_shardability === "shardable_with_seam";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Per-obligation shardability declarations for a material kind, one-to-one with
|
|
29
|
+
* `reviewMaterialGoals(kind)`. Spreadsheet obligations are distilled from §3 of the design spec
|
|
30
|
+
* (conservative `whole` unless splitting is proven; `cross_sheet_reference_integrity` is the
|
|
31
|
+
* sole seam-gated relational obligation). Other kinds return `[]` until their per-material review
|
|
32
|
+
* adapters land — mirroring `reviewMaterialGoals`.
|
|
33
|
+
*/
|
|
34
|
+
export function reviewObligationShardability(kind) {
|
|
35
|
+
if (kind === "spreadsheet") {
|
|
36
|
+
return [
|
|
37
|
+
{ obligation: "formula_integrity", material_shardability: "whole" },
|
|
38
|
+
{
|
|
39
|
+
obligation: "cross_sheet_reference_integrity",
|
|
40
|
+
material_shardability: "shardable_with_seam",
|
|
41
|
+
},
|
|
42
|
+
{ obligation: "named_range_hygiene", material_shardability: "shardable_independent" },
|
|
43
|
+
{ obligation: "data_validation_coverage", material_shardability: "shardable_independent" },
|
|
44
|
+
{ obligation: "access_and_protection_hygiene", material_shardability: "whole" },
|
|
45
|
+
{ obligation: "structural_risk_signals", material_shardability: "whole" },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Pure validator core (git-free, injectable — mirrors check-obligation-coverage's
|
|
52
|
+
* `evaluateObligationCoverage`). Returns every violation; `[]` means the declarations are
|
|
53
|
+
* exhaustive AND consistent with the sealed relational authority. Relationality is read from the
|
|
54
|
+
* module's sealed `isRelationalObligation` — NOT from the (injectable) declarations — so a
|
|
55
|
+
* crafted declaration cannot launder its own relational status. Enforces INV-SHARD-1.
|
|
56
|
+
*/
|
|
57
|
+
export function evaluateObligationShardability(inputs) {
|
|
58
|
+
const violations = [];
|
|
59
|
+
const obligationSet = new Set(inputs.obligations);
|
|
60
|
+
const declarations = inputs.declarations;
|
|
61
|
+
// (1) exhaustiveness: no orphan / duplicate declarations.
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const declaration of declarations) {
|
|
64
|
+
if (!obligationSet.has(declaration.obligation)) {
|
|
65
|
+
violations.push({
|
|
66
|
+
obligation: declaration.obligation,
|
|
67
|
+
kind: "orphan_declaration",
|
|
68
|
+
detail: "declaration for an obligation not in the catalog",
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(declaration.obligation)) {
|
|
73
|
+
violations.push({
|
|
74
|
+
obligation: declaration.obligation,
|
|
75
|
+
kind: "duplicate_declaration",
|
|
76
|
+
detail: "obligation declared more than once",
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
seen.add(declaration.obligation);
|
|
81
|
+
// (2) relational fail-closed: a relational obligation must not be shardable_independent.
|
|
82
|
+
if (isRelationalObligation(declaration.obligation) &&
|
|
83
|
+
declaration.material_shardability === "shardable_independent") {
|
|
84
|
+
violations.push({
|
|
85
|
+
obligation: declaration.obligation,
|
|
86
|
+
kind: "relational_independent",
|
|
87
|
+
detail: "relational obligation declared shardable_independent — would destroy cross-section evidence (ILC-2)",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// (3) seam is only for relational obligations.
|
|
91
|
+
if (declaration.material_shardability === "shardable_with_seam" &&
|
|
92
|
+
!isRelationalObligation(declaration.obligation)) {
|
|
93
|
+
violations.push({
|
|
94
|
+
obligation: declaration.obligation,
|
|
95
|
+
kind: "seam_on_local",
|
|
96
|
+
detail: "non-relational obligation declared shardable_with_seam — a seam is only for relational evidence",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// (1, cont.) every obligation has a declaration.
|
|
101
|
+
for (const obligation of inputs.obligations) {
|
|
102
|
+
if (!seen.has(obligation)) {
|
|
103
|
+
violations.push({
|
|
104
|
+
obligation,
|
|
105
|
+
kind: "missing_declaration",
|
|
106
|
+
detail: "obligation in the catalog has no shardability declaration",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return violations;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* fail-closed validator (the immediate consumer of the shardability declarations, so they are
|
|
114
|
+
* never a consumer-less dead struct). Wraps the pure core with the real per-kind catalog and
|
|
115
|
+
* declarations. `[]` for every kind is the Stage 2 lock (asserted by the G3 invariant test).
|
|
116
|
+
*/
|
|
117
|
+
export function validateObligationShardability(kind) {
|
|
118
|
+
return evaluateObligationShardability({
|
|
119
|
+
obligations: reviewMaterialGoals(kind),
|
|
120
|
+
declarations: reviewObligationShardability(kind),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Pure shardability gate (Stage 3 calls this; no consumer at Stage 2 — a pure function, not a
|
|
125
|
+
* data struct, so not a dead struct). A shard is permitted only when the obligation is not
|
|
126
|
+
* `whole`, the proposed shard keeps each element intact, and either it is independently
|
|
127
|
+
* shardable or its mandatory cross-section seam is actually covered. (design §5.3.)
|
|
128
|
+
*/
|
|
129
|
+
export function isObligationShardable(args) {
|
|
130
|
+
const { declaration, seam_covered, element_intact } = args;
|
|
131
|
+
if (declaration.material_shardability === "whole")
|
|
132
|
+
return false;
|
|
133
|
+
if (!element_intact)
|
|
134
|
+
return false;
|
|
135
|
+
if (declaration.material_shardability === "shardable_independent")
|
|
136
|
+
return true;
|
|
137
|
+
return seam_covered; // shardable_with_seam
|
|
138
|
+
}
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { deliberationResolutionPath, issueDeliberationPromptPacketPath, issueDeliberationResponsePath, } from "./controlled-lens-deliberation.js";
|
|
3
3
|
import { synthesisLedgerPath, synthesisWorkItemsPath, } from "./synthesis-map-reduce.js";
|
|
4
4
|
import { fileExists, readYamlDocument } from "./review-artifact-utils.js";
|
|
5
|
-
import { PIPELINE_EXECUTION_LEDGER_SCHEMA_VERSION, buildLedgerTrust, buildOutputHashes, fileSha256IfPresent,
|
|
5
|
+
import { PIPELINE_EXECUTION_LEDGER_SCHEMA_VERSION, buildLedgerTrust, buildOutputHashes, fileSha256IfPresent, isResolvedLedgerUnit, normalizeLedgerRefs, } from "../pipeline-execution-ledger.js";
|
|
6
6
|
const PRE_DELIBERATION_ISSUE_ARTIFACT_ORDER = [
|
|
7
7
|
"finding-ledger",
|
|
8
8
|
"finding-relation-graph",
|
|
@@ -442,6 +442,32 @@ async function buildUnitEntry(args) {
|
|
|
442
442
|
downstreamUnitIds: args.downstreamUnitIds,
|
|
443
443
|
};
|
|
444
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* 설계 A demotion-disclosure consumer: lenses listed in the persisted stance
|
|
447
|
+
* matrix's `validation.missing_stances` were demoted complete-with-failure
|
|
448
|
+
* after bounded resubmit exhaustion; the matrix consumed the gap, so their
|
|
449
|
+
* per-lens ledger units are terminally resolved (owe no further dispatch).
|
|
450
|
+
*/
|
|
451
|
+
async function demotedStanceUnitIdsFromMatrix(executionPlan) {
|
|
452
|
+
const matrixPath = executionPlan.issue_stance_matrix_path;
|
|
453
|
+
if (!matrixPath || !(await fileExists(matrixPath)))
|
|
454
|
+
return new Set();
|
|
455
|
+
try {
|
|
456
|
+
const matrix = await readYamlDocument(matrixPath);
|
|
457
|
+
return new Set((matrix.validation?.missing_stances ?? [])
|
|
458
|
+
.map((entry) => entry?.lens_id)
|
|
459
|
+
.filter((lensId) => typeof lensId === "string")
|
|
460
|
+
.map((lensId) => `issue-stance:${lensId}`));
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
// The matrix's `validation.missing_stances` is the only durable authority
|
|
464
|
+
// for stance demotion. A present-but-unreadable/unparseable matrix (torn
|
|
465
|
+
// write, out-of-band corruption) must fail loud instead of silently
|
|
466
|
+
// returning an empty set — a silent empty set would drop the demotion
|
|
467
|
+
// signal and regress the affected stance unit(s) to unresolved/blocked.
|
|
468
|
+
throw new Error(`Failed to read demoted stance units from issue-stance matrix at ${matrixPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
445
471
|
export async function buildReviewPipelineExecutionLedger(params) {
|
|
446
472
|
const [deliberationDiskUnits, synthesisDiskUnits] = await Promise.all([
|
|
447
473
|
deliberationUnitsFromDisk(params.executionPlan),
|
|
@@ -471,6 +497,10 @@ export async function buildReviewPipelineExecutionLedger(params) {
|
|
|
471
497
|
const workerUnitsByUnitId = firstWinsByUnitId((params.reviewRunManifest?.worker_units ?? []).filter((unit) => typeof unit.unit_id === "string"), (unit) => unit.unit_id);
|
|
472
498
|
const trustedUnitIds = new Set();
|
|
473
499
|
const ledgerUnits = [];
|
|
500
|
+
// Lazy + memoized: the matrix read matters only when some stance unit is
|
|
501
|
+
// not completed; a healthy run (frontier loops call this builder per
|
|
502
|
+
// iteration) never pays the stat+read+parse.
|
|
503
|
+
let demotedStanceUnitIds = null;
|
|
474
504
|
for (const plannedUnit of units) {
|
|
475
505
|
const entry = await buildUnitEntry({
|
|
476
506
|
plannedUnit,
|
|
@@ -484,8 +514,18 @@ export async function buildReviewPipelineExecutionLedger(params) {
|
|
|
484
514
|
hasReviewRunManifest: params.reviewRunManifest !== null && params.reviewRunManifest !== undefined,
|
|
485
515
|
trustedUnitIds,
|
|
486
516
|
});
|
|
517
|
+
if (plannedUnit.unitId.startsWith("issue-stance:") &&
|
|
518
|
+
entry.status !== "completed") {
|
|
519
|
+
demotedStanceUnitIds ??= await demotedStanceUnitIdsFromMatrix(params.executionPlan);
|
|
520
|
+
if (demotedStanceUnitIds.has(plannedUnit.unitId)) {
|
|
521
|
+
entry.resolution = "demoted";
|
|
522
|
+
}
|
|
523
|
+
}
|
|
487
524
|
ledgerUnits.push(entry);
|
|
488
|
-
|
|
525
|
+
// Trusted output or terminally resolved — a demoted unit satisfies
|
|
526
|
+
// downstream upstream-trust because the stage product consumed the
|
|
527
|
+
// disclosed gap, not the unit's (absent) output.
|
|
528
|
+
if (isResolvedLedgerUnit(entry))
|
|
489
529
|
trustedUnitIds.add(entry.unitId);
|
|
490
530
|
}
|
|
491
531
|
return {
|
|
@@ -2,6 +2,8 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
4
|
import { atomicWriteFile } from "../artifact-io.js";
|
|
5
|
+
import { isSpreadsheetRef } from "../target-material-kind.js";
|
|
6
|
+
import { DEFAULT_WORKBOOK_INVENTORY_PROMPT_CAPS, observeSpreadsheetSource, projectInventoryForAdmission, projectInventoryForPrompt, } from "../spreadsheet-structure-observer.js";
|
|
5
7
|
export const DEFAULT_EXCLUDED_NAMES = [
|
|
6
8
|
".git",
|
|
7
9
|
"node_modules",
|
|
@@ -174,9 +176,219 @@ export async function collectFilePathsRecursively(rootPath, options, currentDept
|
|
|
174
176
|
}
|
|
175
177
|
return collected.sort();
|
|
176
178
|
}
|
|
177
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Render the workbook inventory as a compact text view for the review prompt
|
|
181
|
+
* (design §3.2 — the "inventory projection (text view)"). Honesty
|
|
182
|
+
* (review-target-profile §6): structure is inspected only, cell values and formula
|
|
183
|
+
* results are not evaluated.
|
|
184
|
+
*
|
|
185
|
+
* The view carries bounded DETAIL — not just counts — for every section that backs a
|
|
186
|
+
* kind-derived review obligation (`reviewMaterialGoals`): formula TEXT + cross-sheet
|
|
187
|
+
* refs, named-range refers_to, data-validation rule_summary, error-cell tokens, and
|
|
188
|
+
* external-link targets. Without the detail those obligations would be unverifiable
|
|
189
|
+
* (the reviewer would be told to audit content that is only an integer in the prompt).
|
|
190
|
+
* Two projections compose: `projectInventoryForAdmission` drops raw cell values, then
|
|
191
|
+
* `projectInventoryForPrompt` bounds array SIZE so a large workbook cannot overflow the
|
|
192
|
+
* prompt. Anything trimmed is surfaced honestly (the "structural sample bounded" line);
|
|
193
|
+
* the full detail stays in the persisted inventory.
|
|
194
|
+
*/
|
|
195
|
+
function renderSpreadsheetStructuralView(inventory, caps) {
|
|
196
|
+
const lines = [
|
|
197
|
+
"[Spreadsheet Structural Inventory — structure inspected only; cell values and formula results are not evaluated]",
|
|
198
|
+
`workbook_kind: ${inventory.workbook_kind}`,
|
|
199
|
+
`content_sha256: ${inventory.content_sha256}`,
|
|
200
|
+
];
|
|
201
|
+
if (inventory.unsupported_reason) {
|
|
202
|
+
lines.push(`unsupported: ${inventory.unsupported_reason}`);
|
|
203
|
+
return `${lines.join("\n")}\n`;
|
|
204
|
+
}
|
|
205
|
+
// Stage 1: when the caller threads window-proportional caps, size the prompt
|
|
206
|
+
// projection to the lens model's window; otherwise the DEFAULT caps keep the
|
|
207
|
+
// render byte-identical to today (no regression).
|
|
208
|
+
const { inventory: inv, sections } = projectInventoryForPrompt(projectInventoryForAdmission(inventory), caps ?? DEFAULT_WORKBOOK_INVENTORY_PROMPT_CAPS);
|
|
209
|
+
// One rendered formula PATTERN line (Stage 1.1): the deduped formula TEXT + how many
|
|
210
|
+
// cells carry it (occurrence_count) + a representative cell + the sheets it spans + any
|
|
211
|
+
// cross-sheet references — the obligation-backing detail for formula_integrity /
|
|
212
|
+
// cross_sheet_reference_integrity (a count alone is not auditable). A fill-down that was
|
|
213
|
+
// tens of thousands of per-cell rows now renders as one auditable line.
|
|
214
|
+
const renderFormulaPatternLine = (pattern) => {
|
|
215
|
+
const xref = pattern.cross_sheet_refs.length > 0
|
|
216
|
+
? ` [cross-sheet → ${pattern.cross_sheet_refs.join(", ")}]`
|
|
217
|
+
: "";
|
|
218
|
+
return (` - ${pattern.pattern} (×${pattern.occurrence_count}, e.g. ${pattern.sample_cell}` +
|
|
219
|
+
`; sheets: ${pattern.sheets.join(", ")})${xref}`);
|
|
220
|
+
};
|
|
221
|
+
// Friendly labels for the bounded-sample note. A3: count-only sections
|
|
222
|
+
// (tables/merged_ranges, printed as full counts below) are SUPPRESSED from the note so it
|
|
223
|
+
// never contradicts them (RC-2); every OTHER projector-recorded trim is disclosed —
|
|
224
|
+
// friendly label when known, else the raw section key — so a newly-added
|
|
225
|
+
// projectInventoryForPrompt section can never be silently dropped from disclosure (#4 class).
|
|
226
|
+
const NOTE_SECTION_LABELS = {
|
|
227
|
+
per_sheet_data: "sheet bodies",
|
|
228
|
+
"per_sheet_data.columns": "columns",
|
|
229
|
+
distinct_value_vocab: "distinct-value columns",
|
|
230
|
+
formula_patterns: "formula patterns",
|
|
231
|
+
named_ranges: "named_ranges",
|
|
232
|
+
data_validations: "data_validations",
|
|
233
|
+
external_links: "external_links",
|
|
234
|
+
error_cells: "error_cells",
|
|
235
|
+
pivot_tables: "pivot_tables",
|
|
236
|
+
cross_sheet_key_overlap: "cross_sheet_key_overlap",
|
|
237
|
+
"cross_sheet_key_overlap.pairwise_overlap": "cross-sheet pairwise overlaps",
|
|
238
|
+
risk_signals: "risk_signals",
|
|
239
|
+
};
|
|
240
|
+
const COUNT_ONLY_TRIM_SECTIONS = new Set(["tables", "merged_ranges"]);
|
|
241
|
+
const trimSectionLabel = (section) => NOTE_SECTION_LABELS[section] ?? section;
|
|
242
|
+
const renderedTrims = sections.filter((s) => !COUNT_ONLY_TRIM_SECTIONS.has(s.section));
|
|
243
|
+
// Total sheet count comes from the FULL inventory; the per-sheet layout below renders only
|
|
244
|
+
// the sheets whose (capped) per_sheet_data body survived projection.
|
|
245
|
+
// When observation was bounded by the sheet cap, sheet_count_total holds the true total —
|
|
246
|
+
// render "N of M observed" so the count is not mis-reported as the post-cap number, and a
|
|
247
|
+
// reviewer knows beyond-cap sheets (and any protection/structure on them) were not observed.
|
|
248
|
+
const sheetCountLabel = inventory.sheet_count_total !== undefined
|
|
249
|
+
? `${inventory.sheets.length} of ${inventory.sheet_count_total} observed`
|
|
250
|
+
: `${inventory.sheets.length}`;
|
|
251
|
+
lines.push(`sheets: ${sheetCountLabel}${inv.capture_truncated ? " (capture truncated)" : ""}`);
|
|
252
|
+
if (renderedTrims.length > 0) {
|
|
253
|
+
// Honest about WHAT this note means: review renders a bounded PROMPT sample and does not
|
|
254
|
+
// persist the full WorkbookStructuralInventory, so the trimmed detail is not separately
|
|
255
|
+
// recoverable — never imply the dropped rows can be retrieved later (R1).
|
|
256
|
+
lines.push(`structural sample bounded (prompt sample only — review does not persist the full inventory): ${renderedTrims
|
|
257
|
+
.map((s) => `${trimSectionLabel(s.section)} ${s.kept}/${s.total}`)
|
|
258
|
+
.join(", ")}`);
|
|
259
|
+
}
|
|
260
|
+
const renderedSheetNames = new Set(inv.per_sheet_data.map((d) => d.sheet));
|
|
261
|
+
// B3: protected/hidden sheets beyond the per_sheet_data render cap keep
|
|
262
|
+
// access_and_protection_hygiene attached, but their flags are emitted only inside the
|
|
263
|
+
// per-sheet layout below (rendered-sheet set) — disclose the beyond-cap count so a
|
|
264
|
+
// reviewer does not read the silence as "the rest are unprotected/visible".
|
|
265
|
+
const beyondCapAccessFlags = inventory.sheets.filter((sheet) => !renderedSheetNames.has(sheet.name) && (sheet.protected || sheet.hidden));
|
|
266
|
+
if (beyondCapAccessFlags.length > 0) {
|
|
267
|
+
lines.push(`${beyondCapAccessFlags.length} protected/hidden sheet(s) beyond the rendered sample (flags not individually shown; access_and_protection_hygiene still applies)`);
|
|
268
|
+
}
|
|
269
|
+
// B2: obligation-backing detail is emitted UP FRONT — before the per-sheet bodies — so the
|
|
270
|
+
// downstream 300-line embed head-cut (truncateForEmbedding) preserves the evidence backing
|
|
271
|
+
// named_range_hygiene / data_validation_coverage / structural_risk_signals /
|
|
272
|
+
// cross_sheet_reference_integrity even for a many-sheet workbook. Reordering alone is
|
|
273
|
+
// zero-sum under a fixed budget, so each obligation class gets a bounded in-band sample
|
|
274
|
+
// here; the per-sheet layout (columns) follows and may be truncated under heavy load
|
|
275
|
+
// without dropping any obligation backing.
|
|
276
|
+
if (inventory.named_ranges.length) {
|
|
277
|
+
lines.push("", `named_ranges: ${inventory.named_ranges.length}`);
|
|
278
|
+
for (const nr of inv.named_ranges) {
|
|
279
|
+
lines.push(` - ${nr.name} (${nr.scope}) → ${nr.refers_to}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (inventory.data_validations.length) {
|
|
283
|
+
lines.push("", `data_validations: ${inventory.data_validations.length}`);
|
|
284
|
+
for (const dv of inv.data_validations) {
|
|
285
|
+
// design-C: surface the DECLARED type=list enum members HERE (this loop runs over EVERY
|
|
286
|
+
// validation, independent of per-sheet column/sheet rendering) so they reach the prompt even
|
|
287
|
+
// when the column is trimmed by the column cap, the sheet is dropped by the sheet cap, the
|
|
288
|
+
// sheet has no profiled columns, or one column carries multiple disjoint list validations
|
|
289
|
+
// (Codex round3 #2/#3/#4/#7). Members are DECLARED labels (bounded), not observed data.
|
|
290
|
+
const enumLabels = dv.members && dv.members.length > 0 ? ` enum=[${dv.members.join(", ")}]` : "";
|
|
291
|
+
lines.push(` - ${dv.sheet}!${dv.range}: ${dv.rule_summary}${enumLabels}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (inventory.error_cells.length) {
|
|
295
|
+
lines.push("", `error_cells: ${inventory.error_cells.length}`);
|
|
296
|
+
for (const ec of inv.error_cells) {
|
|
297
|
+
lines.push(` - ${ec.sheet}!${ec.cell}: ${ec.token}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (inventory.external_links.length) {
|
|
301
|
+
lines.push("", `external_links: ${inventory.external_links.length}`);
|
|
302
|
+
for (const el of inv.external_links) {
|
|
303
|
+
lines.push(` - ${el.kind}: ${el.target}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Remaining structural facts that are obligation context but not per-item audit detail.
|
|
307
|
+
const counts = [];
|
|
308
|
+
if (inventory.tables.length)
|
|
309
|
+
counts.push(`tables: ${inventory.tables.length}`);
|
|
310
|
+
if (inventory.merged_ranges.length)
|
|
311
|
+
counts.push(`merged_ranges: ${inventory.merged_ranges.length}`);
|
|
312
|
+
if (inventory.macro_present)
|
|
313
|
+
counts.push("macro_present: true");
|
|
314
|
+
if (counts.length)
|
|
315
|
+
lines.push("", counts.join("; "));
|
|
316
|
+
if (inventory.pivot_tables.length) {
|
|
317
|
+
lines.push("", `pivot_tables: ${inventory.pivot_tables.length}`);
|
|
318
|
+
for (const p of inv.pivot_tables) {
|
|
319
|
+
const src = p.source_sheet ? ` source=${p.source_sheet}!${p.source_ref ?? ""}` : "";
|
|
320
|
+
lines.push(` - ${p.name} @ ${p.sheet}!${p.location}${src}`);
|
|
321
|
+
lines.push(` rows=[${p.row_fields.join(", ")}] cols=[${p.column_fields.join(", ")}]` +
|
|
322
|
+
` data=[${p.data_fields.join(", ")}] filters=[${p.page_fields.join(", ")}]`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (inventory.cross_sheet_key_overlap.length) {
|
|
326
|
+
lines.push("", "cross_sheet_key_overlap (shared-column value overlap, counts only):");
|
|
327
|
+
for (const o of inv.cross_sheet_key_overlap) {
|
|
328
|
+
const pairs = o.pairwise_overlap.map((p) => `${p.a}∩${p.b}=${p.count}`).join(", ");
|
|
329
|
+
lines.push(` - ${o.key_name} across [${o.sheets.join(", ")}]: ${pairs}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (inventory.risk_signals.length) {
|
|
333
|
+
lines.push("", "risk_signals:");
|
|
334
|
+
for (const signal of inv.risk_signals) {
|
|
335
|
+
lines.push(` - ${signal.kind} @ ${signal.location}: ${signal.literal}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// formula_integrity / cross_sheet_reference_integrity backing: the deduplicated formula
|
|
339
|
+
// PATTERNS (Stage 1.1), emitted up front (before the per-sheet bodies) so the formula TEXT
|
|
340
|
+
// survives the embed cut. A fill-down (one master replicated across N cells) is one pattern
|
|
341
|
+
// here; formula_cells_total reports the honest cell count (lower-bound when the distinct-
|
|
342
|
+
// pattern cap dropped a new pattern), so the reviewer sees both the distinct logic and its
|
|
343
|
+
// true reach. Each pattern carries the sheets it spans, so a workbook whose formulas live
|
|
344
|
+
// only on a trimmed sheet still shows its formula text.
|
|
345
|
+
if (inv.formula_patterns.length > 0) {
|
|
346
|
+
const lowerBound = inv.formula_cells_total_is_lower_bound ? ", lower-bound" : "";
|
|
347
|
+
lines.push("", `formulas: ${inv.formula_patterns.length} distinct patterns over ${inv.formula_cells_total} cells${lowerBound}:`);
|
|
348
|
+
for (const pattern of inv.formula_patterns) {
|
|
349
|
+
lines.push(renderFormulaPatternLine(pattern));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Per-sheet LAYOUT (dimensions + columns) — context that follows the obligation backing
|
|
353
|
+
// above and may be truncated under heavy load without losing any obligation evidence.
|
|
354
|
+
for (const sheet of inv.sheets.filter((s) => renderedSheetNames.has(s.name))) {
|
|
355
|
+
const flags = `${sheet.hidden ? " (hidden)" : ""}${sheet.protected ? " (protected)" : ""}`;
|
|
356
|
+
lines.push("", `## sheet: ${sheet.name}`, `dimensions: ${sheet.dimensions.rows} rows × ${sheet.dimensions.cols} cols${flags}`);
|
|
357
|
+
const data = inv.per_sheet_data.find((d) => d.sheet === sheet.name);
|
|
358
|
+
if (data) {
|
|
359
|
+
const lowConfidence = data.header_confidence === "low" ? "; header_confidence: low (layout uncertain)" : "";
|
|
360
|
+
lines.push(`layout_kind: ${data.layout_kind}; header_rows: ${data.header_rows ? data.header_rows.join(",") : "none"}${lowConfidence}`);
|
|
361
|
+
if (data.columns.length > 0) {
|
|
362
|
+
lines.push("columns:");
|
|
363
|
+
for (const col of data.columns) {
|
|
364
|
+
const vocab = inv.distinct_value_vocab.find((v) => v.sheet === sheet.name && v.column === col.name);
|
|
365
|
+
const distinct = vocab
|
|
366
|
+
? `; distinct≈${vocab.distinct_count}${vocab.distinct_count_is_estimate ? "+" : ""}`
|
|
367
|
+
: "";
|
|
368
|
+
// design-C per-column cardinality: distinct/non_empty COUNT over scanned rows. Declared
|
|
369
|
+
// type=list enum members are surfaced in the data_validations section (above), NOT here —
|
|
370
|
+
// so they survive column/sheet trimming and multi-validation columns (Codex round3).
|
|
371
|
+
const cardinality = `; cardinality=${col.distinct_count}${col.distinct_count_is_estimate ? "+" : ""}/${col.non_empty_count}`;
|
|
372
|
+
lines.push(` - ${col.name} (${col.inferred_type}; non_empty=${col.non_empty_ratio.toFixed(2)}${cardinality}${distinct})`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return `${lines.join("\n")}\n`;
|
|
378
|
+
}
|
|
379
|
+
export async function readTextOrDirectoryListing(targetPath, options, inventory, inventoryPromptCaps) {
|
|
178
380
|
const stats = await fs.stat(targetPath);
|
|
179
381
|
if (!stats.isDirectory()) {
|
|
382
|
+
// Spreadsheet targets are rendered as a structural/aggregate inventory view,
|
|
383
|
+
// not raw bytes (design §3.2). review routes through the SAME shared projection
|
|
384
|
+
// as reconstruct — raw cell values never enter the prompt, and a binary
|
|
385
|
+
// workbook is never dumped as garbage utf8. When the orchestrator already
|
|
386
|
+
// observed this ref (single-observation, design §3.2), reuse that inventory;
|
|
387
|
+
// otherwise observe here. The render applies the admission + prompt projections,
|
|
388
|
+
// sized by the optional Stage 1 window-proportional caps (undefined → DEFAULT).
|
|
389
|
+
if (isSpreadsheetRef(targetPath)) {
|
|
390
|
+
return renderSpreadsheetStructuralView(inventory ?? (await observeSpreadsheetSource(targetPath)), inventoryPromptCaps);
|
|
391
|
+
}
|
|
180
392
|
try {
|
|
181
393
|
return await fs.readFile(targetPath, "utf8");
|
|
182
394
|
}
|
|
@@ -208,20 +420,20 @@ export async function readTextOrDirectoryListing(targetPath, options) {
|
|
|
208
420
|
: "";
|
|
209
421
|
return `[Directory Listing]\n${listing}\n${truncationNote}`;
|
|
210
422
|
}
|
|
211
|
-
export async function renderTargetSnapshot(resolvedTargetRefs, options) {
|
|
423
|
+
export async function renderTargetSnapshot(resolvedTargetRefs, options, inventoryByRef, inventoryPromptCaps) {
|
|
212
424
|
const sections = [];
|
|
213
425
|
for (const resolvedTargetRef of resolvedTargetRefs) {
|
|
214
|
-
sections.push(`## ${resolvedTargetRef}`, "", await readTextOrDirectoryListing(resolvedTargetRef, options), "");
|
|
426
|
+
sections.push(`## ${resolvedTargetRef}`, "", await readTextOrDirectoryListing(resolvedTargetRef, options, inventoryByRef?.get(path.resolve(resolvedTargetRef)), inventoryPromptCaps), "");
|
|
215
427
|
}
|
|
216
428
|
return `${sections.join("\n").trimEnd()}\n`;
|
|
217
429
|
}
|
|
218
|
-
export async function renderReviewTargetMaterializedInput(materializedKind, materializedRefs, options) {
|
|
430
|
+
export async function renderReviewTargetMaterializedInput(materializedKind, materializedRefs, options, inventoryByRef, inventoryPromptCaps) {
|
|
219
431
|
const sections = [`kind: ${materializedKind}`, ""];
|
|
220
432
|
for (const materializedRef of materializedRefs) {
|
|
221
433
|
sections.push(`## ${path.basename(materializedRef)}`);
|
|
222
434
|
sections.push(`ref: ${materializedRef}`);
|
|
223
435
|
sections.push("");
|
|
224
|
-
sections.push(await readTextOrDirectoryListing(materializedRef, options));
|
|
436
|
+
sections.push(await readTextOrDirectoryListing(materializedRef, options, inventoryByRef?.get(path.resolve(materializedRef)), inventoryPromptCaps));
|
|
225
437
|
sections.push("");
|
|
226
438
|
}
|
|
227
439
|
return `${sections.join("\n").trimEnd()}\n`;
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileExists, isoNow, readYamlDocument, writeYamlDocument, } from "./review-artifact-utils.js";
|
|
4
4
|
import { buildReviewPipelineExecutionLedger, } from "./pipeline-execution-ledger.js";
|
|
5
|
-
import { fileSha256IfPresent, isTrustedLedgerUnit, } from "../pipeline-execution-ledger.js";
|
|
5
|
+
import { fileSha256IfPresent, isResolvedLedgerUnit, isTrustedLedgerUnit, } from "../pipeline-execution-ledger.js";
|
|
6
6
|
import { buildReviewContinuationPlan, } from "./continuation-plan.js";
|
|
7
7
|
import { readValidatedLensSidecarArtifact } from "./lens-sidecar-artifact.js";
|
|
8
8
|
import { computeLensCompletionBarrier, resolveRequiredParticipatingLensCount, } from "./lens-completion-policy.js";
|
|
@@ -30,6 +30,23 @@ import { renderReviewUnitBoundaryDetailsSection } from "./unit-boundary-details.
|
|
|
30
30
|
* step calls `completeReviewSession`, which lives in the cli layer, so the
|
|
31
31
|
* core-api wrapper runs it when reviewAdvance signals `ready_to_assemble`.
|
|
32
32
|
*/
|
|
33
|
+
/**
|
|
34
|
+
* Single-source runtime mirror of the closed {@link ReviewUnitKind} union. The
|
|
35
|
+
* `satisfies Record<ReviewUnitKind, true>` makes adding an enum member a compile
|
|
36
|
+
* error here, so a new kind cannot be planned/executed until it is wired into the
|
|
37
|
+
* result buckets ({@link mergeUnitResultIntoExecutionResult}) and the seat gate.
|
|
38
|
+
* This is the guard against the silent-drop where an unhandled unit_kind would
|
|
39
|
+
* leave the frontier permanently stalled with no error.
|
|
40
|
+
*/
|
|
41
|
+
const REVIEW_UNIT_KINDS = {
|
|
42
|
+
lens: true,
|
|
43
|
+
issue_artifact: true,
|
|
44
|
+
deliberation: true,
|
|
45
|
+
synthesize: true,
|
|
46
|
+
};
|
|
47
|
+
function isReviewUnitKind(value) {
|
|
48
|
+
return Object.prototype.hasOwnProperty.call(REVIEW_UNIT_KINDS, value);
|
|
49
|
+
}
|
|
33
50
|
async function readOptionalYamlArtifact(filePath) {
|
|
34
51
|
if (!(await fileExists(filePath)))
|
|
35
52
|
return null;
|
|
@@ -136,6 +153,9 @@ export async function computeReviewFrontier(sessionRoot, targetUnits) {
|
|
|
136
153
|
*/
|
|
137
154
|
export async function validateUnitSeatToResult(args) {
|
|
138
155
|
const { unit } = args;
|
|
156
|
+
if (!isReviewUnitKind(unit.unitKind)) {
|
|
157
|
+
throw new Error(`validateUnitSeatToResult: unit ${unit.unitId} has unknown unit_kind "${unit.unitKind}" (not a ReviewUnitKind); a new kind must be wired into the result buckets and frontier routing before it can be executed.`);
|
|
158
|
+
}
|
|
139
159
|
const executionPlan = args.executionPlan ?? (await loadExecutionPlan(args.sessionRoot));
|
|
140
160
|
const recordedAt = args.recordedAt ?? isoNow();
|
|
141
161
|
const baseResult = (extra) => ({
|
|
@@ -251,8 +271,10 @@ export async function mergeUnitResultIntoExecutionResult(args) {
|
|
|
251
271
|
...artifact,
|
|
252
272
|
synthesis_map_execution_results: upsertUnitResult(artifact.synthesis_map_execution_results ?? [], result),
|
|
253
273
|
};
|
|
254
|
-
default:
|
|
255
|
-
|
|
274
|
+
default: {
|
|
275
|
+
const exhaustive = result.unit_kind;
|
|
276
|
+
throw new Error(`mergeUnitResultIntoExecutionResult: no execution-result bucket for unit_kind "${String(exhaustive)}" (unit ${result.unit_id}); extend this switch when adding a ReviewUnitKind.`);
|
|
277
|
+
}
|
|
256
278
|
}
|
|
257
279
|
})();
|
|
258
280
|
await writeYamlDocument(resultPath, merged);
|
|
@@ -829,7 +851,8 @@ async function ensureDeliberationMarkdownProjection(sessionRoot, plan) {
|
|
|
829
851
|
*/
|
|
830
852
|
async function finalizeHostExecutionResultIfComplete(sessionRoot, plan) {
|
|
831
853
|
const ledger = await buildSessionLedger(sessionRoot, plan);
|
|
832
|
-
|
|
854
|
+
// Convergence = no owed work: trusted OR terminally resolved (demoted).
|
|
855
|
+
if (!ledger.units.every((unit) => isResolvedLedgerUnit(unit)))
|
|
833
856
|
return;
|
|
834
857
|
const resultPath = executionResultPath(sessionRoot);
|
|
835
858
|
const existing = await readOptionalYamlArtifact(resultPath);
|
|
@@ -874,10 +897,11 @@ async function finalizeHostExecutionResultIfComplete(sessionRoot, plan) {
|
|
|
874
897
|
*/
|
|
875
898
|
async function computeRoundResult(sessionRoot, plan) {
|
|
876
899
|
const frontier = await computeReviewFrontier(sessionRoot);
|
|
877
|
-
// Terminal: every ledger unit
|
|
878
|
-
//
|
|
879
|
-
//
|
|
880
|
-
|
|
900
|
+
// Terminal: every ledger unit owes no work (trusted or terminally
|
|
901
|
+
// resolved), so there is nothing left to run and the caller assembles.
|
|
902
|
+
// (A continuation plan with no untrusted frontier is `eligible: false`,
|
|
903
|
+
// so this must precede the ineligibility check.)
|
|
904
|
+
if (frontier.unitLedger.units.every((unit) => isResolvedLedgerUnit(unit))) {
|
|
881
905
|
return { status: "ready_to_assemble" };
|
|
882
906
|
}
|
|
883
907
|
if (!frontier.eligible) {
|
|
@@ -105,6 +105,10 @@ export function reviewProgressStepIdFromHalt(args) {
|
|
|
105
105
|
if (args.haltPhase === "lens_completion_barrier") {
|
|
106
106
|
return "lens_completion_barrier";
|
|
107
107
|
}
|
|
108
|
+
// 설계 B: lens 풀 dispatch breaker 트립은 lens 디스패치 단계에서 멈춘
|
|
109
|
+
// 것이다 (배리어 판정 이전; halt_unit_kind는 null로 온다).
|
|
110
|
+
if (args.haltPhase === "lens_dispatch_breaker")
|
|
111
|
+
return "lens_dispatch";
|
|
108
112
|
if (args.haltPhase === "synthesize")
|
|
109
113
|
return "synthesize";
|
|
110
114
|
if (args.haltPhase === "controlled_lens_deliberation") {
|
|
@@ -114,6 +118,10 @@ export function reviewProgressStepIdFromHalt(args) {
|
|
|
114
118
|
}
|
|
115
119
|
if (args.haltPhase === "issue_artifact") {
|
|
116
120
|
const unitId = args.haltUnitId;
|
|
121
|
+
// 동적 per-lens stance 유닛(issue-stance:<lens>)은 issue-stance-matrix
|
|
122
|
+
// 산출 단계에 속한다 — issue_artifact_id 정확 일치로는 매핑되지 않는다.
|
|
123
|
+
if (unitId?.startsWith("issue-stance:"))
|
|
124
|
+
return "issue_stance_matrix";
|
|
117
125
|
const step = REVIEW_PROGRESS_STEPS.find((candidate) => candidate.issue_artifact_id === unitId);
|
|
118
126
|
return step?.id ?? "finding_ledger";
|
|
119
127
|
}
|