switchboard-cli 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -0
- package/bin/switchboard.mjs +49 -0
- package/calibration/engine/baseline.ts +93 -0
- package/calibration/engine/diagnosis.ts +191 -0
- package/calibration/engine/diff.ts +118 -0
- package/calibration/engine/escalation.ts +49 -0
- package/calibration/engine/ledger.ts +141 -0
- package/calibration/engine/trends.ts +141 -0
- package/calibration/external/rubric.yaml +32 -0
- package/calibration/external/scorer.ts +479 -0
- package/calibration/external/verdict-writer.ts +29 -0
- package/calibration/internal/harness.ts +697 -0
- package/calibration/internal/return-simulator.ts +270 -0
- package/calibration/internal/trace-collector.ts +78 -0
- package/calibration/internal/verdict-writer.ts +149 -0
- package/calibration/ledger/baselines/baseline-2026-04-09.yaml +23 -0
- package/calibration/ledger/history.yaml +18 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/adv-0bdc944b61d5.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-16cdf0db1b43.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-a6b2c8be67cc.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/manifest.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/adv-0bdc944b61d5.yaml +7 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-16cdf0db1b43.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-a6b2c8be67cc.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/summary.yaml +10 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/adv-0bdc944b61d5.yaml +141 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-16cdf0db1b43.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-a6b2c8be67cc.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/adv-0bdc944b61d5.yaml +24 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-16cdf0db1b43.yaml +24 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-a6b2c8be67cc.yaml +25 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/adv-0bdc944b61d5.yaml +31 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-16cdf0db1b43.yaml +32 -0
- package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-a6b2c8be67cc.yaml +32 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/adv-a0c9e2bfb0d6.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-3e892f3a89ee.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-958b2f9e6816.yaml +9 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/manifest.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/adv-a0c9e2bfb0d6.yaml +7 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-3e892f3a89ee.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-958b2f9e6816.yaml +8 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/summary.yaml +10 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/adv-a0c9e2bfb0d6.yaml +141 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-3e892f3a89ee.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-958b2f9e6816.yaml +147 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/adv-a0c9e2bfb0d6.yaml +24 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-3e892f3a89ee.yaml +23 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-958b2f9e6816.yaml +25 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/adv-a0c9e2bfb0d6.yaml +31 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-3e892f3a89ee.yaml +32 -0
- package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-958b2f9e6816.yaml +32 -0
- package/calibration/seeds/adversarial-generator.ts +159 -0
- package/calibration/seeds/blind-generator.ts +169 -0
- package/calibration/seeds/replay-loader.ts +117 -0
- package/calibration/skill/calibrate.ts +292 -0
- package/calibration/skill/cli-flags.ts +49 -0
- package/calibration/skill/report.ts +80 -0
- package/calibration/skill/review.ts +118 -0
- package/calibration/types.ts +292 -0
- package/package.json +46 -0
- package/src/commands/audit-codex.ts +266 -0
- package/src/commands/calibrate.ts +70 -0
- package/src/commands/compile.ts +117 -0
- package/src/commands/evaluate.ts +103 -0
- package/src/commands/ingest.ts +250 -0
- package/src/commands/init.ts +133 -0
- package/src/commands/packet.ts +408 -0
- package/src/commands/receipt.ts +305 -0
- package/src/commands/run-claude.ts +355 -0
- package/src/index.ts +43 -0
- package/src/lib/draft-return.ts +278 -0
- package/src/lib/drift-guard.ts +105 -0
- package/src/lib/errors.ts +61 -0
- package/src/lib/output.ts +43 -0
- package/src/lib/paths.ts +125 -0
- package/src/lib/proof.ts +262 -0
- package/src/lib/transport.ts +276 -0
- package/src/lib/yaml-io.ts +62 -0
- package/src/store/filesystem-store.ts +326 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
import type {
|
|
3
|
+
ActivationInput,
|
|
4
|
+
AuditedReceipt,
|
|
5
|
+
CanonicalDelta,
|
|
6
|
+
DriftDiff,
|
|
7
|
+
GateState,
|
|
8
|
+
HandoffRecord,
|
|
9
|
+
NormalizedReturnEnvelope,
|
|
10
|
+
ProjectContract,
|
|
11
|
+
ProjectLedger,
|
|
12
|
+
ReconcileReport,
|
|
13
|
+
ReconcileV2Result,
|
|
14
|
+
ReturnReport,
|
|
15
|
+
RoutingConstraints,
|
|
16
|
+
SynthesizedSeed,
|
|
17
|
+
} from "@switchboard/core";
|
|
18
|
+
import {
|
|
19
|
+
projectContractSchema,
|
|
20
|
+
handoffRecordSchema,
|
|
21
|
+
} from "@switchboard/core";
|
|
22
|
+
import { activateV2 } from "@switchboard/core";
|
|
23
|
+
import { routeV2 } from "@switchboard/core";
|
|
24
|
+
import { reconcileReturn } from "@switchboard/core";
|
|
25
|
+
import { evaluateGates } from "@switchboard/core";
|
|
26
|
+
import { buildAuditedReceipt } from "@switchboard/core";
|
|
27
|
+
import { computeClaimSpread } from "@switchboard/core";
|
|
28
|
+
|
|
29
|
+
import type { CalibrationSeed, PipelineTrace } from "../types";
|
|
30
|
+
import { simulateReturn } from "./return-simulator";
|
|
31
|
+
import { collectTrace } from "./trace-collector";
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Deterministic seed parser — extracts structured seed from raw_idea
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function parseSynthesizedSeed(rawIdea: string): SynthesizedSeed {
|
|
38
|
+
// Title: first sentence or first 80 chars
|
|
39
|
+
const firstSentence = rawIdea.match(/^[^.!?]+[.!?]?/)?.[0] ?? rawIdea.slice(0, 80);
|
|
40
|
+
const title = firstSentence.replace(/[.!?]+$/, "").trim();
|
|
41
|
+
|
|
42
|
+
// One-liner: short distillation
|
|
43
|
+
const oneLiner = title.length > 50
|
|
44
|
+
? title.slice(0, 50) + "..."
|
|
45
|
+
: title;
|
|
46
|
+
|
|
47
|
+
// V1 outcome: second sentence if available, otherwise derived
|
|
48
|
+
const sentences = rawIdea.split(/[.!?]+/).map(s => s.trim()).filter(Boolean);
|
|
49
|
+
const v1Outcome = sentences.length > 1
|
|
50
|
+
? sentences[1]!
|
|
51
|
+
: `A working first slice of ${title.toLowerCase()}.`;
|
|
52
|
+
|
|
53
|
+
// Detect task type
|
|
54
|
+
const isImpl = /\b(build|implement|create|add|set up|scaffold|code|develop)\b/i.test(rawIdea);
|
|
55
|
+
const taskType = isImpl ? "implementation-request" as const : "product-idea" as const;
|
|
56
|
+
|
|
57
|
+
// Confidence: more detail = higher confidence
|
|
58
|
+
const confidence = rawIdea.length > 200 ? "high" as const
|
|
59
|
+
: rawIdea.length > 80 ? "medium" as const
|
|
60
|
+
: "low" as const;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
title,
|
|
64
|
+
one_liner: oneLiner,
|
|
65
|
+
problem: rawIdea,
|
|
66
|
+
proposed_solution: `Build ${title.toLowerCase()} as a bounded V1 slice.`,
|
|
67
|
+
target_user: "Solo builders shipping software.",
|
|
68
|
+
v1_outcome: v1Outcome,
|
|
69
|
+
non_goals: ["Not a generic chat wrapper"],
|
|
70
|
+
missing_inputs: [],
|
|
71
|
+
open_questions: [],
|
|
72
|
+
first_build_slice: title,
|
|
73
|
+
confidence,
|
|
74
|
+
task_type: taskType,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Contract builder
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function buildContract(seed: SynthesizedSeed): ProjectContract {
|
|
83
|
+
return projectContractSchema.parse({
|
|
84
|
+
project_id: `proj-${randomBytes(6).toString("hex")}`,
|
|
85
|
+
title: seed.title,
|
|
86
|
+
one_liner: seed.one_liner,
|
|
87
|
+
problem: seed.problem,
|
|
88
|
+
target_user: seed.target_user,
|
|
89
|
+
desired_v1_outcome: seed.v1_outcome,
|
|
90
|
+
non_goals: seed.non_goals,
|
|
91
|
+
repo_status: "repo-not-present",
|
|
92
|
+
task_clauses: {
|
|
93
|
+
scope_in: [seed.first_build_slice || seed.title],
|
|
94
|
+
scope_out: seed.non_goals,
|
|
95
|
+
systems_expected: [],
|
|
96
|
+
files_allowed: [],
|
|
97
|
+
must_not_widen: seed.non_goals,
|
|
98
|
+
build_constraints: [],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Calibrated spec builder — 12-section SB_SPEC from seed + contract
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function buildCalibratedSpec(seed: SynthesizedSeed, contract: ProjectContract): string {
|
|
108
|
+
const scopeIn = contract.task_clauses.scope_in.map(s => `- ${s}`).join("\n");
|
|
109
|
+
const scopeOut = contract.non_goals.map(s => `- ${s}`).join("\n");
|
|
110
|
+
const openQs = seed.open_questions.length > 0
|
|
111
|
+
? seed.open_questions.map(q => `- ${q}`).join("\n")
|
|
112
|
+
: seed.missing_inputs.length > 0
|
|
113
|
+
? seed.missing_inputs.map(m => `- ${m}`).join("\n")
|
|
114
|
+
: "- None identified.";
|
|
115
|
+
const constraints = contract.task_clauses.build_constraints.length > 0
|
|
116
|
+
? contract.task_clauses.build_constraints.map(c => `- ${c}`).join("\n")
|
|
117
|
+
: "- No additional constraints.";
|
|
118
|
+
const systems = contract.task_clauses.systems_expected.length > 0
|
|
119
|
+
? contract.task_clauses.systems_expected.join(", ")
|
|
120
|
+
: "CLI, Node.js";
|
|
121
|
+
const riskLine = seed.confidence === "high"
|
|
122
|
+
? "Confidence is high — minimal unknowns."
|
|
123
|
+
: seed.confidence === "medium"
|
|
124
|
+
? "Confidence is medium — some assumptions remain unconfirmed."
|
|
125
|
+
: "Confidence is low — significant unknowns remain.";
|
|
126
|
+
|
|
127
|
+
return `# ${contract.title}
|
|
128
|
+
|
|
129
|
+
## What this product is
|
|
130
|
+
|
|
131
|
+
${seed.proposed_solution}
|
|
132
|
+
|
|
133
|
+
## Who it is for
|
|
134
|
+
|
|
135
|
+
${seed.target_user}
|
|
136
|
+
|
|
137
|
+
## The problem
|
|
138
|
+
|
|
139
|
+
${seed.problem}
|
|
140
|
+
|
|
141
|
+
## The proposed solution
|
|
142
|
+
|
|
143
|
+
${seed.proposed_solution} This is a bounded V1 slice focused on proving the core value proposition with minimal surface area.
|
|
144
|
+
|
|
145
|
+
## V1 promise
|
|
146
|
+
|
|
147
|
+
${seed.v1_outcome}
|
|
148
|
+
|
|
149
|
+
## Scope in
|
|
150
|
+
|
|
151
|
+
${scopeIn}
|
|
152
|
+
- Keep the primary artifact aligned with the V1 promise.
|
|
153
|
+
- Touch only files necessary for the first build slice.
|
|
154
|
+
|
|
155
|
+
## Scope out
|
|
156
|
+
|
|
157
|
+
${scopeOut}
|
|
158
|
+
|
|
159
|
+
## First build slice
|
|
160
|
+
|
|
161
|
+
${seed.first_build_slice || contract.title}
|
|
162
|
+
|
|
163
|
+
Systems expected: ${systems}.
|
|
164
|
+
|
|
165
|
+
Constraints to respect:
|
|
166
|
+
${constraints}
|
|
167
|
+
|
|
168
|
+
## Acceptance criteria
|
|
169
|
+
|
|
170
|
+
- Seed is valid and the idea has been compiled into a governed contract.
|
|
171
|
+
- Primary artifact is execution-ready: the build slice works as described.
|
|
172
|
+
- Next surface is explicit with a return condition.
|
|
173
|
+
- Handoff is launchable as an SBX bundle.
|
|
174
|
+
|
|
175
|
+
## Open questions
|
|
176
|
+
|
|
177
|
+
${openQs}
|
|
178
|
+
|
|
179
|
+
## What must not change (intent anchors)
|
|
180
|
+
|
|
181
|
+
${scopeOut}
|
|
182
|
+
|
|
183
|
+
These are non-negotiable boundaries. Any execution that touches these areas must be flagged for review.
|
|
184
|
+
|
|
185
|
+
## Key risks and unknowns
|
|
186
|
+
|
|
187
|
+
${riskLine}
|
|
188
|
+
|
|
189
|
+
- Is the scope narrow enough to deliver in a single bounded execution?
|
|
190
|
+
- Are there hidden dependencies that could expand the build slice?
|
|
191
|
+
- Return condition: all done_when criteria met or blocker encountered.
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Routing constraints builder
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
function buildRoutingConstraints(
|
|
200
|
+
seed: SynthesizedSeed,
|
|
201
|
+
mode: string,
|
|
202
|
+
): RoutingConstraints {
|
|
203
|
+
return {
|
|
204
|
+
capability_needed: "execute",
|
|
205
|
+
process_mode: (mode === "minimal" || mode === "structured" || mode === "governance")
|
|
206
|
+
? mode as "minimal" | "structured" | "governance"
|
|
207
|
+
: "structured",
|
|
208
|
+
task_type: seed.task_type,
|
|
209
|
+
seed_confidence: seed.confidence,
|
|
210
|
+
repo_exists: false,
|
|
211
|
+
blast_radius: "low",
|
|
212
|
+
non_functional_flags: [],
|
|
213
|
+
provider_readiness: {
|
|
214
|
+
"internal-fallback": true,
|
|
215
|
+
"claude-code-sdk": true,
|
|
216
|
+
"openai-api": false,
|
|
217
|
+
"codex": false,
|
|
218
|
+
"chatgpt-auto": false,
|
|
219
|
+
"chatgpt-manual": false,
|
|
220
|
+
"cursor-manual": false,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Handoff builder
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
function buildHandoff(
|
|
230
|
+
contract: ProjectContract,
|
|
231
|
+
route: RoutingDecision,
|
|
232
|
+
): HandoffRecord {
|
|
233
|
+
// Map provider to surface label
|
|
234
|
+
const providerId = route.selected.provider_id;
|
|
235
|
+
const surfaceMap: Record<string, "chatgpt" | "claude-code" | "cursor" | "codex"> = {
|
|
236
|
+
"claude-code-sdk": "claude-code",
|
|
237
|
+
"openai-api": "chatgpt",
|
|
238
|
+
"chatgpt-auto": "chatgpt",
|
|
239
|
+
"chatgpt-manual": "chatgpt",
|
|
240
|
+
"cursor-manual": "cursor",
|
|
241
|
+
"codex": "codex",
|
|
242
|
+
"internal-fallback": "claude-code",
|
|
243
|
+
};
|
|
244
|
+
const surface = surfaceMap[providerId] ?? "claude-code";
|
|
245
|
+
|
|
246
|
+
return handoffRecordSchema.parse({
|
|
247
|
+
handoff_id: `ho-${randomBytes(6).toString("hex")}`,
|
|
248
|
+
surface,
|
|
249
|
+
objective: `Build the first slice: ${contract.title}`,
|
|
250
|
+
included_scope: contract.task_clauses.scope_in.length > 0
|
|
251
|
+
? contract.task_clauses.scope_in
|
|
252
|
+
: [contract.desired_v1_outcome],
|
|
253
|
+
excluded_scope: contract.non_goals,
|
|
254
|
+
done_when: [`${contract.desired_v1_outcome} is functional and tested.`],
|
|
255
|
+
return_condition: "All done_when criteria met or blocker encountered.",
|
|
256
|
+
loop: {
|
|
257
|
+
objective: `Build the first slice: ${contract.title}`,
|
|
258
|
+
scope_in: contract.task_clauses.scope_in.length > 0
|
|
259
|
+
? contract.task_clauses.scope_in
|
|
260
|
+
: [contract.desired_v1_outcome],
|
|
261
|
+
scope_out: contract.non_goals,
|
|
262
|
+
done_when: [`${contract.desired_v1_outcome} is functional and tested.`],
|
|
263
|
+
success_criteria: [`${contract.title} works as described.`],
|
|
264
|
+
verify_with: ["Manual testing", "Type check passes"],
|
|
265
|
+
return_when: "All done_when criteria met or blocker encountered.",
|
|
266
|
+
blocker_escalation: ["Escalate if scope grows beyond V1."],
|
|
267
|
+
expected_artifacts: ["src/index.ts", "src/lib/core.ts"],
|
|
268
|
+
fallback_surface: "claude-code",
|
|
269
|
+
execution_shape: "single",
|
|
270
|
+
},
|
|
271
|
+
readiness: {
|
|
272
|
+
status: "ready",
|
|
273
|
+
issues: [],
|
|
274
|
+
},
|
|
275
|
+
bundle_version: "SBX-v1",
|
|
276
|
+
launched_at: new Date().toISOString(),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
// Minimal DriftDiff builder
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
function buildMinimalDriftDiff(): DriftDiff {
|
|
285
|
+
return {
|
|
286
|
+
entries: [],
|
|
287
|
+
has_drift: false,
|
|
288
|
+
summary: "No drift detected in calibration run.",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
// Minimal ProjectLedger builder
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
function buildMinimalLedger(contract: ProjectContract): ProjectLedger {
|
|
297
|
+
const now = new Date().toISOString();
|
|
298
|
+
return {
|
|
299
|
+
entries: [
|
|
300
|
+
{
|
|
301
|
+
id: `goal-${randomBytes(4).toString("hex")}`,
|
|
302
|
+
kind: "goal",
|
|
303
|
+
text: contract.desired_v1_outcome,
|
|
304
|
+
evidence_type: "user-backed",
|
|
305
|
+
evidence_refs: [],
|
|
306
|
+
parent_ids: [],
|
|
307
|
+
risk_level: "low",
|
|
308
|
+
downstream_artifacts: [],
|
|
309
|
+
created_at: now,
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: `constraint-${randomBytes(4).toString("hex")}`,
|
|
313
|
+
kind: "constraint",
|
|
314
|
+
text: contract.non_goals[0] ?? "No non-goals specified.",
|
|
315
|
+
evidence_type: "user-backed",
|
|
316
|
+
evidence_refs: [],
|
|
317
|
+
parent_ids: [],
|
|
318
|
+
risk_level: "low",
|
|
319
|
+
downstream_artifacts: [],
|
|
320
|
+
created_at: now,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
scope_changes: [],
|
|
324
|
+
version: 1,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Minimal ReconcileV2Result builder
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
function buildMinimalReconcileV2(recommendation: string): ReconcileV2Result {
|
|
333
|
+
return {
|
|
334
|
+
checks: [],
|
|
335
|
+
traceability: [],
|
|
336
|
+
passed: recommendation === "accept",
|
|
337
|
+
blockers: 0,
|
|
338
|
+
warnings: recommendation === "accept" ? 0 : 1,
|
|
339
|
+
recommendation: recommendation === "accept"
|
|
340
|
+
? "accept"
|
|
341
|
+
: recommendation === "accept-with-warnings"
|
|
342
|
+
? "accept-with-warnings"
|
|
343
|
+
: "review-required",
|
|
344
|
+
summary: `Reconcile result: ${recommendation}.`,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
// NormalizedReturnEnvelope builder
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
function buildEnvelope(
|
|
353
|
+
report: ReturnReport,
|
|
354
|
+
handoff: HandoffRecord,
|
|
355
|
+
): NormalizedReturnEnvelope {
|
|
356
|
+
const now = new Date().toISOString();
|
|
357
|
+
return {
|
|
358
|
+
envelope_id: `env-${randomBytes(6).toString("hex")}`,
|
|
359
|
+
envelope_version: 1 as const,
|
|
360
|
+
created_at: now,
|
|
361
|
+
source_surface: report.surface_used,
|
|
362
|
+
source_role: "agent",
|
|
363
|
+
objective_used: report.objective_attempted,
|
|
364
|
+
outcome_class: report.status === "done" ? "completed"
|
|
365
|
+
: report.status === "partial" ? "partial"
|
|
366
|
+
: "blocked",
|
|
367
|
+
changed_files: report.changed_files.map(f => ({ path: f, system: "" })),
|
|
368
|
+
touched_artifacts: report.artifacts_created,
|
|
369
|
+
commands_run: [],
|
|
370
|
+
evidence_items: report.evidence.map(e => ({ text: e, content_mode: "direct" as const })),
|
|
371
|
+
verification_runs: report.verification_run.map(v => ({ text: v, content_mode: "direct" as const })),
|
|
372
|
+
criteria_met: report.success_criteria_met.map(c => ({ text: c, content_mode: "direct" as const })),
|
|
373
|
+
decisions: report.decisions_made.map(d => ({ text: d, content_mode: "direct" as const })),
|
|
374
|
+
cautions: report.blockers.map(b => ({ text: b, source: "return" as const })),
|
|
375
|
+
residuals: report.escalations.map(e => ({ text: e, kind: "escalation" as const })),
|
|
376
|
+
recommended_next_step: report.recommended_next_step,
|
|
377
|
+
provenance_posture: "captured",
|
|
378
|
+
confidence_posture: "medium",
|
|
379
|
+
summary: report.summary,
|
|
380
|
+
handoff_id: report.handoff_id,
|
|
381
|
+
dispatch_id: `dsp-${randomBytes(6).toString("hex")}`,
|
|
382
|
+
return_id: `ret-${randomBytes(6).toString("hex")}`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
// CanonicalDelta builder
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
function buildMinimalDelta(
|
|
391
|
+
report: ReturnReport,
|
|
392
|
+
envelope: NormalizedReturnEnvelope,
|
|
393
|
+
): CanonicalDelta {
|
|
394
|
+
const now = new Date().toISOString();
|
|
395
|
+
return {
|
|
396
|
+
delta_id: `delta-${randomBytes(6).toString("hex")}`,
|
|
397
|
+
project_id: "",
|
|
398
|
+
parent_contract_id: "",
|
|
399
|
+
return_id: envelope.return_id,
|
|
400
|
+
gate_state: "",
|
|
401
|
+
objective_state: report.objective_met === "yes" ? "met"
|
|
402
|
+
: report.objective_met === "partial" ? "partial"
|
|
403
|
+
: "unmet",
|
|
404
|
+
created_at: now,
|
|
405
|
+
implemented: report.changed_files,
|
|
406
|
+
systems_touched: [],
|
|
407
|
+
file_changes: report.changed_files.map(f => ({ path: f, system: "" })),
|
|
408
|
+
artifact_changes: report.artifacts_created,
|
|
409
|
+
tests_or_checks: [],
|
|
410
|
+
verification_runs: report.verification_run,
|
|
411
|
+
evidence_items: report.evidence,
|
|
412
|
+
boundaries_preserved: [],
|
|
413
|
+
boundaries_violated: [],
|
|
414
|
+
cautions: report.blockers,
|
|
415
|
+
unresolved: report.escalations,
|
|
416
|
+
decisions: report.decisions_made.map(d => ({ text: d, traced: false })),
|
|
417
|
+
state_fields_advanced: report.status === "done" ? ["objective_state"] : [],
|
|
418
|
+
summary: report.summary,
|
|
419
|
+
evidence_accounting: {
|
|
420
|
+
scope_check_count: 1,
|
|
421
|
+
scope_check_pass_count: 1,
|
|
422
|
+
scope_check_caution_count: 0,
|
|
423
|
+
scope_check_blocker_count: 0,
|
|
424
|
+
verification_run_count: report.verification_run.length,
|
|
425
|
+
verification_runs: report.verification_run,
|
|
426
|
+
evidence_item_count: report.evidence.length,
|
|
427
|
+
evidence_items: report.evidence,
|
|
428
|
+
file_change_count: report.changed_files.length,
|
|
429
|
+
artifact_change_count: report.artifacts_created.length,
|
|
430
|
+
criteria_met_count: report.success_criteria_met.length,
|
|
431
|
+
criteria_met: report.success_criteria_met,
|
|
432
|
+
caution_count: report.blockers.length,
|
|
433
|
+
unresolved_count: report.escalations.length,
|
|
434
|
+
boundary_preserved_count: 0,
|
|
435
|
+
boundary_violated_count: 0,
|
|
436
|
+
consequence_type: report.status === "done" ? "code-change" : "no-change",
|
|
437
|
+
consequence_label: report.status === "done"
|
|
438
|
+
? "Code change: first slice delivered."
|
|
439
|
+
: "No meaningful change recorded.",
|
|
440
|
+
},
|
|
441
|
+
verified: [],
|
|
442
|
+
residual_resolutions: [],
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
// GateState builder
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
|
|
450
|
+
function buildGateState(
|
|
451
|
+
report: ReturnReport,
|
|
452
|
+
delta: CanonicalDelta,
|
|
453
|
+
reconcile?: ReconcileReport,
|
|
454
|
+
): GateState {
|
|
455
|
+
const evidenceCount = report.evidence.length;
|
|
456
|
+
const verificationCount = report.verification_run.length;
|
|
457
|
+
const criteriaCount = report.success_criteria_met.length;
|
|
458
|
+
|
|
459
|
+
// --- Reconcile-aware escalation (takes priority) ---
|
|
460
|
+
if (reconcile) {
|
|
461
|
+
// Rule 1: Explicit review-required recommendation → force held-for-review
|
|
462
|
+
if (reconcile.recommendation === "review-required") {
|
|
463
|
+
const reviewFindings = reconcile.findings.filter(f => f.severity === "review-required");
|
|
464
|
+
return {
|
|
465
|
+
label: "held-for-review",
|
|
466
|
+
reason: `Reconcile review-required: ${reviewFindings.map(f => f.category).join(", ")}.`,
|
|
467
|
+
evidence_summary: {
|
|
468
|
+
verification_count: verificationCount,
|
|
469
|
+
evidence_count: evidenceCount,
|
|
470
|
+
criteria_met_count: criteriaCount,
|
|
471
|
+
caution_count: reconcile.findings.length,
|
|
472
|
+
blocker_count: reviewFindings.length,
|
|
473
|
+
},
|
|
474
|
+
honest: true,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Rule 2: 3+ findings (even all warnings) → accepted-with-cautions minimum
|
|
479
|
+
if (reconcile.findings.length >= 3) {
|
|
480
|
+
return {
|
|
481
|
+
label: "accepted-with-cautions",
|
|
482
|
+
reason: `Reconcile produced ${reconcile.findings.length} findings: ${reconcile.findings.map(f => f.category).join(", ")}.`,
|
|
483
|
+
evidence_summary: {
|
|
484
|
+
verification_count: verificationCount,
|
|
485
|
+
evidence_count: evidenceCount,
|
|
486
|
+
criteria_met_count: criteriaCount,
|
|
487
|
+
caution_count: reconcile.findings.length,
|
|
488
|
+
blocker_count: 0,
|
|
489
|
+
},
|
|
490
|
+
honest: true,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Rule 3: Drift detected with any review-required finding → held-for-review
|
|
495
|
+
if (reconcile.drift_detected && reconcile.findings.some(f => f.severity === "review-required")) {
|
|
496
|
+
return {
|
|
497
|
+
label: "held-for-review",
|
|
498
|
+
reason: `Drift detected with review-required finding.`,
|
|
499
|
+
evidence_summary: {
|
|
500
|
+
verification_count: verificationCount,
|
|
501
|
+
evidence_count: evidenceCount,
|
|
502
|
+
criteria_met_count: criteriaCount,
|
|
503
|
+
caution_count: reconcile.findings.length,
|
|
504
|
+
blocker_count: reconcile.findings.filter(f => f.severity === "review-required").length,
|
|
505
|
+
},
|
|
506
|
+
honest: true,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// --- End reconcile-aware escalation, fall through to evidence checks ---
|
|
511
|
+
|
|
512
|
+
// Evidence-gap returns get a weaker gate
|
|
513
|
+
if (verificationCount === 0 && evidenceCount === 0) {
|
|
514
|
+
return {
|
|
515
|
+
label: "held-for-review",
|
|
516
|
+
reason: "No verification or evidence provided despite done claim.",
|
|
517
|
+
evidence_summary: {
|
|
518
|
+
verification_count: 0,
|
|
519
|
+
evidence_count: 0,
|
|
520
|
+
criteria_met_count: criteriaCount,
|
|
521
|
+
caution_count: 0,
|
|
522
|
+
blocker_count: 0,
|
|
523
|
+
},
|
|
524
|
+
honest: true,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Thin evidence gets cautions
|
|
529
|
+
if (verificationCount <= 1 && evidenceCount <= 1) {
|
|
530
|
+
return {
|
|
531
|
+
label: "accepted-with-cautions",
|
|
532
|
+
reason: "Minimal evidence provided. Accepted with cautions.",
|
|
533
|
+
evidence_summary: {
|
|
534
|
+
verification_count: verificationCount,
|
|
535
|
+
evidence_count: evidenceCount,
|
|
536
|
+
criteria_met_count: criteriaCount,
|
|
537
|
+
caution_count: 1,
|
|
538
|
+
blocker_count: 0,
|
|
539
|
+
},
|
|
540
|
+
honest: true,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Good evidence
|
|
545
|
+
return {
|
|
546
|
+
label: "accepted",
|
|
547
|
+
reason: "Gate accepted. Evidence supports the claims.",
|
|
548
|
+
evidence_summary: {
|
|
549
|
+
verification_count: verificationCount,
|
|
550
|
+
evidence_count: evidenceCount,
|
|
551
|
+
criteria_met_count: criteriaCount,
|
|
552
|
+
caution_count: 0,
|
|
553
|
+
blocker_count: 0,
|
|
554
|
+
},
|
|
555
|
+
honest: true,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
// Main pipeline runner
|
|
561
|
+
// ---------------------------------------------------------------------------
|
|
562
|
+
|
|
563
|
+
export function runPipeline(seed: CalibrationSeed): {
|
|
564
|
+
trace: PipelineTrace;
|
|
565
|
+
contract: ProjectContract;
|
|
566
|
+
handoff: HandoffRecord;
|
|
567
|
+
receipt: AuditedReceipt | null;
|
|
568
|
+
} {
|
|
569
|
+
const startTime = Date.now();
|
|
570
|
+
|
|
571
|
+
let contract: ProjectContract | null = null;
|
|
572
|
+
let handoff: HandoffRecord | null = null;
|
|
573
|
+
let receipt: AuditedReceipt | null = null;
|
|
574
|
+
let partialTrace: Partial<PipelineTrace> = {
|
|
575
|
+
seed_id: seed.seed_id,
|
|
576
|
+
return_mode: "honest",
|
|
577
|
+
return_mode_sealed: true,
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
// Step 1: Build ActivationInput
|
|
582
|
+
const activationInput: ActivationInput = {
|
|
583
|
+
raw_idea: seed.raw_idea,
|
|
584
|
+
repo_exists: false,
|
|
585
|
+
what_do_you_want_next: "Build the first slice",
|
|
586
|
+
constraints: "",
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// Step 2: Parse synthesized seed
|
|
590
|
+
const synthesizedSeed = parseSynthesizedSeed(seed.raw_idea);
|
|
591
|
+
|
|
592
|
+
// Step 3: Activate
|
|
593
|
+
const activationResult = activateV2(activationInput, synthesizedSeed);
|
|
594
|
+
partialTrace.complexity_score = activationResult.mode_scoring.total;
|
|
595
|
+
partialTrace.selected_mode = activationResult.selected_mode;
|
|
596
|
+
partialTrace.assumptions_count = activationResult.assumptions.length;
|
|
597
|
+
|
|
598
|
+
// Step 4: Build contract + calibrated spec
|
|
599
|
+
contract = buildContract(synthesizedSeed);
|
|
600
|
+
const compiledSpec = buildCalibratedSpec(synthesizedSeed, contract);
|
|
601
|
+
partialTrace.compiled_spec = compiledSpec;
|
|
602
|
+
|
|
603
|
+
// Step 5: Route
|
|
604
|
+
const routingConstraints = buildRoutingConstraints(
|
|
605
|
+
synthesizedSeed,
|
|
606
|
+
activationResult.selected_mode,
|
|
607
|
+
);
|
|
608
|
+
const routeDecision = routeV2(routingConstraints);
|
|
609
|
+
partialTrace.routed_surface = routeDecision.selected.provider_id;
|
|
610
|
+
partialTrace.routing_rationale = routeDecision.rationale;
|
|
611
|
+
|
|
612
|
+
// Step 6: Build handoff
|
|
613
|
+
handoff = buildHandoff(contract, routeDecision);
|
|
614
|
+
|
|
615
|
+
// Step 7: Decide return mode
|
|
616
|
+
const returnMode: "honest" | "drift" = Math.random() < 0.5 ? "honest" : "drift";
|
|
617
|
+
partialTrace.return_mode = returnMode;
|
|
618
|
+
|
|
619
|
+
// Step 8: Simulate return
|
|
620
|
+
const report = simulateReturn(returnMode, contract, handoff);
|
|
621
|
+
|
|
622
|
+
// Step 9: Reconcile (with full spec for untracked-decision context matching)
|
|
623
|
+
const reconcileResult = reconcileReturn({
|
|
624
|
+
report,
|
|
625
|
+
contract,
|
|
626
|
+
handoff,
|
|
627
|
+
spec_markdown: compiledSpec,
|
|
628
|
+
});
|
|
629
|
+
partialTrace.reconcile_findings_count = reconcileResult.findings.length;
|
|
630
|
+
partialTrace.reconcile_drift_detected = reconcileResult.drift_detected;
|
|
631
|
+
partialTrace.reconcile_recommendation = reconcileResult.recommendation;
|
|
632
|
+
|
|
633
|
+
// Step 10: Gate evaluation
|
|
634
|
+
const diff = buildMinimalDriftDiff();
|
|
635
|
+
const ledger = buildMinimalLedger(contract);
|
|
636
|
+
const reconcileV2 = buildMinimalReconcileV2(reconcileResult.recommendation);
|
|
637
|
+
const approvalGates = evaluateGates({
|
|
638
|
+
diff,
|
|
639
|
+
ledger,
|
|
640
|
+
reconcile: reconcileV2,
|
|
641
|
+
report,
|
|
642
|
+
});
|
|
643
|
+
partialTrace.gates_triggered = approvalGates.map(g => g.reason);
|
|
644
|
+
partialTrace.gate_label = approvalGates.length > 0
|
|
645
|
+
? approvalGates.map(g => g.reason).join(", ")
|
|
646
|
+
: "no-gates";
|
|
647
|
+
|
|
648
|
+
// Step 11: Build audited receipt
|
|
649
|
+
const envelope = buildEnvelope(report, handoff);
|
|
650
|
+
const delta = buildMinimalDelta(report, envelope);
|
|
651
|
+
const gate = buildGateState(report, delta, reconcileResult);
|
|
652
|
+
|
|
653
|
+
receipt = buildAuditedReceipt({
|
|
654
|
+
envelope,
|
|
655
|
+
delta,
|
|
656
|
+
gate,
|
|
657
|
+
nextSlice: null,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
partialTrace.trust_posture = receipt.trust_posture;
|
|
661
|
+
partialTrace.gate_label = gate.label; // Use the reconcile-aware gate label
|
|
662
|
+
partialTrace.receipt_verdict = receipt.gate_label;
|
|
663
|
+
partialTrace.honesty_violations = receipt.audit_cautions;
|
|
664
|
+
|
|
665
|
+
// Step 12: Claim spread
|
|
666
|
+
const spread = computeClaimSpread(receipt);
|
|
667
|
+
partialTrace.spread_score = spread.spread_score;
|
|
668
|
+
partialTrace.spread_vector = spread.vector;
|
|
669
|
+
|
|
670
|
+
// Step 13: Build final trace
|
|
671
|
+
partialTrace.outcome = "completed";
|
|
672
|
+
partialTrace.receipt_summary = receipt.summary_claimed.slice(0, 200);
|
|
673
|
+
partialTrace.duration_ms = Date.now() - startTime;
|
|
674
|
+
|
|
675
|
+
const trace = collectTrace(seed, partialTrace, startTime);
|
|
676
|
+
return { trace, contract, handoff, receipt };
|
|
677
|
+
|
|
678
|
+
} catch (error: unknown) {
|
|
679
|
+
// Capture error into trace
|
|
680
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
681
|
+
partialTrace.outcome = "error";
|
|
682
|
+
partialTrace.rejection_point = "pipeline";
|
|
683
|
+
partialTrace.rejection_reason = errorMessage.slice(0, 200);
|
|
684
|
+
partialTrace.duration_ms = Date.now() - startTime;
|
|
685
|
+
|
|
686
|
+
const trace = collectTrace(seed, partialTrace, startTime);
|
|
687
|
+
return {
|
|
688
|
+
trace,
|
|
689
|
+
contract: contract ?? buildContract(parseSynthesizedSeed(seed.raw_idea)),
|
|
690
|
+
handoff: handoff ?? buildHandoff(
|
|
691
|
+
contract ?? buildContract(parseSynthesizedSeed(seed.raw_idea)),
|
|
692
|
+
{ selected: { provider_id: "internal-fallback", score: 0, disqualified: false, disqualification_reason: "", verification_path: "none" }, candidates: [], rationale: "Fallback due to error.", constraints_applied: [], verification_path: "none" },
|
|
693
|
+
),
|
|
694
|
+
receipt: null,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
}
|