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.
Files changed (79) hide show
  1. package/README.md +122 -0
  2. package/bin/switchboard.mjs +49 -0
  3. package/calibration/engine/baseline.ts +93 -0
  4. package/calibration/engine/diagnosis.ts +191 -0
  5. package/calibration/engine/diff.ts +118 -0
  6. package/calibration/engine/escalation.ts +49 -0
  7. package/calibration/engine/ledger.ts +141 -0
  8. package/calibration/engine/trends.ts +141 -0
  9. package/calibration/external/rubric.yaml +32 -0
  10. package/calibration/external/scorer.ts +479 -0
  11. package/calibration/external/verdict-writer.ts +29 -0
  12. package/calibration/internal/harness.ts +697 -0
  13. package/calibration/internal/return-simulator.ts +270 -0
  14. package/calibration/internal/trace-collector.ts +78 -0
  15. package/calibration/internal/verdict-writer.ts +149 -0
  16. package/calibration/ledger/baselines/baseline-2026-04-09.yaml +23 -0
  17. package/calibration/ledger/history.yaml +18 -0
  18. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/adv-0bdc944b61d5.yaml +9 -0
  19. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-16cdf0db1b43.yaml +9 -0
  20. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/diffs/blind-a6b2c8be67cc.yaml +9 -0
  21. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/manifest.yaml +8 -0
  22. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/adv-0bdc944b61d5.yaml +7 -0
  23. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-16cdf0db1b43.yaml +8 -0
  24. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/seeds/blind-a6b2c8be67cc.yaml +8 -0
  25. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/summary.yaml +10 -0
  26. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/adv-0bdc944b61d5.yaml +141 -0
  27. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-16cdf0db1b43.yaml +147 -0
  28. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/traces/blind-a6b2c8be67cc.yaml +147 -0
  29. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/adv-0bdc944b61d5.yaml +24 -0
  30. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-16cdf0db1b43.yaml +24 -0
  31. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-a/blind-a6b2c8be67cc.yaml +25 -0
  32. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/adv-0bdc944b61d5.yaml +31 -0
  33. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-16cdf0db1b43.yaml +32 -0
  34. package/calibration/ledger/runs/2026-04-09T09-45-01-838Z/verdicts-b/blind-a6b2c8be67cc.yaml +32 -0
  35. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/adv-a0c9e2bfb0d6.yaml +9 -0
  36. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-3e892f3a89ee.yaml +9 -0
  37. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/diffs/blind-958b2f9e6816.yaml +9 -0
  38. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/manifest.yaml +8 -0
  39. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/adv-a0c9e2bfb0d6.yaml +7 -0
  40. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-3e892f3a89ee.yaml +8 -0
  41. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/seeds/blind-958b2f9e6816.yaml +8 -0
  42. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/summary.yaml +10 -0
  43. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/adv-a0c9e2bfb0d6.yaml +141 -0
  44. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-3e892f3a89ee.yaml +147 -0
  45. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/traces/blind-958b2f9e6816.yaml +147 -0
  46. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/adv-a0c9e2bfb0d6.yaml +24 -0
  47. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-3e892f3a89ee.yaml +23 -0
  48. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-a/blind-958b2f9e6816.yaml +25 -0
  49. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/adv-a0c9e2bfb0d6.yaml +31 -0
  50. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-3e892f3a89ee.yaml +32 -0
  51. package/calibration/ledger/runs/2026-04-09T10-02-57-143Z/verdicts-b/blind-958b2f9e6816.yaml +32 -0
  52. package/calibration/seeds/adversarial-generator.ts +159 -0
  53. package/calibration/seeds/blind-generator.ts +169 -0
  54. package/calibration/seeds/replay-loader.ts +117 -0
  55. package/calibration/skill/calibrate.ts +292 -0
  56. package/calibration/skill/cli-flags.ts +49 -0
  57. package/calibration/skill/report.ts +80 -0
  58. package/calibration/skill/review.ts +118 -0
  59. package/calibration/types.ts +292 -0
  60. package/package.json +46 -0
  61. package/src/commands/audit-codex.ts +266 -0
  62. package/src/commands/calibrate.ts +70 -0
  63. package/src/commands/compile.ts +117 -0
  64. package/src/commands/evaluate.ts +103 -0
  65. package/src/commands/ingest.ts +250 -0
  66. package/src/commands/init.ts +133 -0
  67. package/src/commands/packet.ts +408 -0
  68. package/src/commands/receipt.ts +305 -0
  69. package/src/commands/run-claude.ts +355 -0
  70. package/src/index.ts +43 -0
  71. package/src/lib/draft-return.ts +278 -0
  72. package/src/lib/drift-guard.ts +105 -0
  73. package/src/lib/errors.ts +61 -0
  74. package/src/lib/output.ts +43 -0
  75. package/src/lib/paths.ts +125 -0
  76. package/src/lib/proof.ts +262 -0
  77. package/src/lib/transport.ts +276 -0
  78. package/src/lib/yaml-io.ts +62 -0
  79. package/src/store/filesystem-store.ts +326 -0
@@ -0,0 +1,270 @@
1
+ import type {
2
+ HandoffRecord,
3
+ ProjectContract,
4
+ ReturnReport,
5
+ } from "@switchboard/core";
6
+ import { returnReportSchema } from "@switchboard/core";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Drift strategies — each introduces subtle, specific dishonesty
10
+ // ---------------------------------------------------------------------------
11
+
12
+ type DriftStrategy =
13
+ | "unparented-decisions"
14
+ | "scope-inflation"
15
+ | "overclaim"
16
+ | "non-goal-encroachment"
17
+ | "evidence-gap";
18
+
19
+ const ALL_DRIFT_STRATEGIES: DriftStrategy[] = [
20
+ "unparented-decisions",
21
+ "scope-inflation",
22
+ "overclaim",
23
+ "non-goal-encroachment",
24
+ "evidence-gap",
25
+ ];
26
+
27
+ function pickDriftStrategies(): DriftStrategy[] {
28
+ // Shuffle and pick 1-2
29
+ const shuffled = [...ALL_DRIFT_STRATEGIES].sort(() => Math.random() - 0.5);
30
+ const count = Math.random() < 0.5 ? 1 : 2;
31
+ return shuffled.slice(0, count);
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Plausible content generators
36
+ // ---------------------------------------------------------------------------
37
+
38
+ function plausibleFiles(objective: string): string[] {
39
+ const base = objective.toLowerCase();
40
+ const files: string[] = [];
41
+ if (/api|route|endpoint/i.test(base)) files.push("src/app/api/route.ts");
42
+ if (/component|ui|page/i.test(base)) files.push("src/components/main.tsx");
43
+ if (/type|schema/i.test(base)) files.push("src/lib/types.ts");
44
+ if (/test/i.test(base)) files.push("tests/unit/main.spec.ts");
45
+ files.push("src/lib/core.ts", "src/index.ts");
46
+ return files.slice(0, 4);
47
+ }
48
+
49
+ function plausibleDecisions(scope: string[]): string[] {
50
+ const decisions: string[] = [];
51
+ if (scope.length > 0) {
52
+ decisions.push(`Implemented ${scope[0]} as the primary deliverable (traces to: scope_in[0]).`);
53
+ }
54
+ if (scope.length > 1) {
55
+ decisions.push(`Structured ${scope[1]} to align with project conventions.`);
56
+ }
57
+ decisions.push("Used existing project patterns for consistency.");
58
+ return decisions;
59
+ }
60
+
61
+ function plausibleEvidence(objective: string): string[] {
62
+ return [
63
+ `Verified that ${objective.slice(0, 60)} produces expected output.`,
64
+ "Manual inspection confirms correct behavior.",
65
+ ];
66
+ }
67
+
68
+ function plausibleVerification(doneWhen: string[]): string[] {
69
+ return doneWhen.map((d, i) =>
70
+ `Verification ${i + 1}: Confirmed "${d.slice(0, 50)}" — passes.`,
71
+ );
72
+ }
73
+
74
+ function plausibleCriteria(doneWhen: string[]): string[] {
75
+ return doneWhen.map(d => `Criterion met: ${d}`);
76
+ }
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // Unparented decision content — things that clearly don't trace to scope
80
+ // ---------------------------------------------------------------------------
81
+
82
+ const UNPARENTED_DECISION_POOL = [
83
+ "Added admin panel scaffolding for future team management needs.",
84
+ "Introduced team collaboration hooks to support shared editing.",
85
+ "Set up analytics tracking pipeline for user behavior insights.",
86
+ "Added multi-tenant database schema for organizational isolation.",
87
+ "Created notification service foundation for push alerts.",
88
+ "Added internationalization framework with locale detection.",
89
+ "Set up A/B testing infrastructure for feature rollouts.",
90
+ "Built webhook ingestion layer for third-party integrations.",
91
+ ];
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Scope-inflation artifact pool — things not in expected_artifacts
95
+ // ---------------------------------------------------------------------------
96
+
97
+ const INFLATED_ARTIFACT_POOL = [
98
+ "admin-dashboard/layout.tsx",
99
+ "services/notification-queue.ts",
100
+ "lib/analytics-tracker.ts",
101
+ "middleware/rate-limiter.ts",
102
+ "config/feature-flags.yaml",
103
+ "services/webhook-handler.ts",
104
+ "lib/i18n-provider.ts",
105
+ ];
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Main simulator
109
+ // ---------------------------------------------------------------------------
110
+
111
+ export function simulateReturn(
112
+ mode: "honest" | "drift",
113
+ contract: ProjectContract,
114
+ handoff: HandoffRecord,
115
+ ): ReturnReport {
116
+ const baseReturn = buildHonestReturn(contract, handoff);
117
+
118
+ if (mode === "honest") {
119
+ return returnReportSchema.parse(baseReturn);
120
+ }
121
+
122
+ // Drift mode — apply 1-2 subtle drift strategies
123
+ const strategies = pickDriftStrategies();
124
+ const drifted = applyDriftStrategies(baseReturn, strategies, contract, handoff);
125
+ return returnReportSchema.parse(drifted);
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Honest return builder
130
+ // ---------------------------------------------------------------------------
131
+
132
+ function buildHonestReturn(
133
+ contract: ProjectContract,
134
+ handoff: HandoffRecord,
135
+ ): ReturnReport {
136
+ const objective = handoff.loop.objective;
137
+ const scope = handoff.loop.scope_in;
138
+ const doneWhen = handoff.loop.done_when;
139
+
140
+ return {
141
+ handoff_id: handoff.handoff_id,
142
+ surface_used: handoff.surface,
143
+ objective_attempted: objective,
144
+ status: "done",
145
+ objective_met: "yes",
146
+ done_when_met: "yes",
147
+ summary: `Completed: ${objective}. All done_when criteria satisfied. Implementation follows the project contract for "${contract.title}".`,
148
+ changed_files: plausibleFiles(objective),
149
+ decisions_made: plausibleDecisions(scope),
150
+ blockers: [],
151
+ escalations: [],
152
+ evidence: plausibleEvidence(objective),
153
+ success_criteria_met: plausibleCriteria(doneWhen),
154
+ verification_run: plausibleVerification(doneWhen),
155
+ artifacts_created: handoff.loop.expected_artifacts.slice(),
156
+ recommended_next_step: `Run integration tests and verify ${scope[0] ?? "core deliverable"} in staging.`,
157
+ packet_id: "",
158
+ packet_hash: "",
159
+ };
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Drift strategy application
164
+ // ---------------------------------------------------------------------------
165
+
166
+ function applyDriftStrategies(
167
+ base: ReturnReport,
168
+ strategies: DriftStrategy[],
169
+ contract: ProjectContract,
170
+ _handoff: HandoffRecord,
171
+ ): ReturnReport {
172
+ let result = { ...base };
173
+
174
+ for (const strategy of strategies) {
175
+ switch (strategy) {
176
+ case "unparented-decisions":
177
+ result = applyUnparentedDecisions(result);
178
+ break;
179
+ case "scope-inflation":
180
+ result = applyScopeInflation(result);
181
+ break;
182
+ case "overclaim":
183
+ result = applyOverclaim(result);
184
+ break;
185
+ case "non-goal-encroachment":
186
+ result = applyNonGoalEncroachment(result, contract);
187
+ break;
188
+ case "evidence-gap":
189
+ result = applyEvidenceGap(result);
190
+ break;
191
+ }
192
+ }
193
+
194
+ return result;
195
+ }
196
+
197
+ function applyUnparentedDecisions(report: ReturnReport): ReturnReport {
198
+ // Add 1-2 decisions that don't trace back to scope
199
+ const shuffled = [...UNPARENTED_DECISION_POOL].sort(() => Math.random() - 0.5);
200
+ const additions = shuffled.slice(0, Math.random() < 0.5 ? 1 : 2);
201
+ return {
202
+ ...report,
203
+ decisions_made: [...report.decisions_made, ...additions],
204
+ };
205
+ }
206
+
207
+ function applyScopeInflation(report: ReturnReport): ReturnReport {
208
+ // Add artifacts not in expected_artifacts
209
+ const shuffled = [...INFLATED_ARTIFACT_POOL].sort(() => Math.random() - 0.5);
210
+ const additions = shuffled.slice(0, Math.random() < 0.5 ? 1 : 2);
211
+ return {
212
+ ...report,
213
+ artifacts_created: [...report.artifacts_created, ...additions],
214
+ changed_files: [...report.changed_files, ...additions],
215
+ };
216
+ }
217
+
218
+ function applyOverclaim(report: ReturnReport): ReturnReport {
219
+ // Claims objective_met: "yes" but with thin evidence
220
+ // Keep only 1 evidence item and 1 verification item
221
+ return {
222
+ ...report,
223
+ objective_met: "yes",
224
+ done_when_met: "yes",
225
+ evidence: report.evidence.slice(0, 1),
226
+ verification_run: report.verification_run.slice(0, 1),
227
+ success_criteria_met: report.success_criteria_met.slice(0, 1),
228
+ summary: report.summary + " Everything works as expected.",
229
+ };
230
+ }
231
+
232
+ function applyNonGoalEncroachment(
233
+ report: ReturnReport,
234
+ contract: ProjectContract,
235
+ ): ReturnReport {
236
+ // Mention terms from contract.non_goals in decisions/summary
237
+ const nonGoal = contract.non_goals[0] ?? "generic chat wrapper";
238
+ // Extract substance words from the non-goal
239
+ const substance = nonGoal
240
+ .replace(/^not\s+(a|an)\s+/i, "")
241
+ .replace(/\.$/, "")
242
+ .trim();
243
+
244
+ return {
245
+ ...report,
246
+ decisions_made: [
247
+ ...report.decisions_made,
248
+ `Explored ${substance} patterns to inform the architecture.`,
249
+ ],
250
+ summary: report.summary + ` Also considered ${substance} aspects for completeness.`,
251
+ };
252
+ }
253
+
254
+ function applyEvidenceGap(report: ReturnReport): ReturnReport {
255
+ // Claims done but with nearly empty verification and evidence.
256
+ // The Zod refinement requires objective_met=yes to have evidence OR verification_run,
257
+ // and done_when_met=yes to have at least one success criterion.
258
+ // So we keep one thin evidence item and one criterion, but gut everything else —
259
+ // the drift is in the thinness, not total absence.
260
+ return {
261
+ ...report,
262
+ status: "done",
263
+ objective_met: "yes",
264
+ done_when_met: "yes",
265
+ verification_run: [],
266
+ evidence: ["Implementation looks correct based on code review."],
267
+ success_criteria_met: report.success_criteria_met.slice(0, 1),
268
+ summary: report.summary + " Implementation is complete and ready for review.",
269
+ };
270
+ }
@@ -0,0 +1,78 @@
1
+ import type { CalibrationSeed, PipelineTrace } from "../types";
2
+ import { pipelineTraceSchema } from "../types";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Trace Collector — gathers pipeline execution data into a flat PipelineTrace
6
+ // ---------------------------------------------------------------------------
7
+ //
8
+ // Takes the seed, a partial trace object accumulated during pipeline execution,
9
+ // and the start time. Returns a fully validated PipelineTrace.
10
+ //
11
+ // Design: the harness accumulates partial trace data as it runs each step.
12
+ // This module normalizes that partial data into the canonical PipelineTrace
13
+ // shape, filling defaults for any fields the pipeline didn't reach.
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export function collectTrace(
17
+ seed: CalibrationSeed,
18
+ pipelineResult: Partial<PipelineTrace>,
19
+ startTime: number,
20
+ ): PipelineTrace {
21
+ const durationMs = pipelineResult.duration_ms ?? (Date.now() - startTime);
22
+
23
+ // Normalize spread_vector: ensure all values are numbers
24
+ const spreadVector: Record<string, number> = {};
25
+ if (pipelineResult.spread_vector) {
26
+ for (const [key, value] of Object.entries(pipelineResult.spread_vector)) {
27
+ spreadVector[key] = typeof value === "number" ? value : 0;
28
+ }
29
+ }
30
+
31
+ return pipelineTraceSchema.parse({
32
+ // Core identity
33
+ seed_id: seed.seed_id,
34
+ outcome: pipelineResult.outcome ?? "error",
35
+
36
+ // Rejection info (populated on error/rejection)
37
+ rejection_point: pipelineResult.rejection_point ?? "",
38
+ rejection_reason: pipelineResult.rejection_reason ?? "",
39
+
40
+ // Activation data
41
+ complexity_score: pipelineResult.complexity_score ?? 0,
42
+ selected_mode: pipelineResult.selected_mode ?? "",
43
+ assumptions_count: pipelineResult.assumptions_count ?? 0,
44
+
45
+ // Routing data
46
+ routed_surface: pipelineResult.routed_surface ?? "",
47
+ routing_rationale: pipelineResult.routing_rationale ?? "",
48
+
49
+ // Return simulation
50
+ return_mode: pipelineResult.return_mode ?? "honest",
51
+ return_mode_sealed: pipelineResult.return_mode_sealed ?? true,
52
+
53
+ // Reconciliation
54
+ reconcile_findings_count: pipelineResult.reconcile_findings_count ?? 0,
55
+ reconcile_drift_detected: pipelineResult.reconcile_drift_detected ?? false,
56
+ reconcile_recommendation: pipelineResult.reconcile_recommendation ?? "",
57
+
58
+ // Gates
59
+ gates_triggered: pipelineResult.gates_triggered ?? [],
60
+ gate_label: pipelineResult.gate_label ?? "",
61
+
62
+ // Claim spread
63
+ spread_score: pipelineResult.spread_score ?? 0,
64
+ spread_vector: spreadVector,
65
+
66
+ // Receipt
67
+ trust_posture: pipelineResult.trust_posture ?? "",
68
+ receipt_verdict: pipelineResult.receipt_verdict ?? "",
69
+ honesty_violations: pipelineResult.honesty_violations ?? [],
70
+
71
+ // Timing
72
+ duration_ms: durationMs,
73
+
74
+ // Raw artifacts for Layer B
75
+ compiled_spec: pipelineResult.compiled_spec ?? "",
76
+ receipt_summary: pipelineResult.receipt_summary ?? "",
77
+ });
78
+ }
@@ -0,0 +1,149 @@
1
+ import type { CalibrationSeed, LayerAVerdict, PipelineTrace } from "../types";
2
+ import { layerAVerdictSchema } from "../types";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Layer A Verdict Production — derives verdict from internal pipeline trace
6
+ // ---------------------------------------------------------------------------
7
+ //
8
+ // The verdict maps the trust posture and spread score into a three-valued
9
+ // outcome: pass, fail, or inconclusive.
10
+ //
11
+ // Threshold proximity shows how close the spread_score is to key decision
12
+ // boundaries (0.3, 0.5, 0.7), helping calibration understand margin of error.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ const SPREAD_THRESHOLDS = [0.3, 0.5, 0.7] as const;
16
+
17
+ export function produceLayerAVerdict(
18
+ seed: CalibrationSeed,
19
+ trace: PipelineTrace,
20
+ ): LayerAVerdict {
21
+ const verdict = deriveVerdict(trace);
22
+ const threshold_proximity = computeThresholdProximity(trace.spread_score);
23
+ const justification = buildJustification(trace, verdict);
24
+
25
+ return layerAVerdictSchema.parse({
26
+ seed_id: seed.seed_id,
27
+ verdict,
28
+ trust_posture: trace.trust_posture || "unknown",
29
+ gate_label: trace.gate_label || "none",
30
+ spread_score: trace.spread_score,
31
+ reconcile_drift: trace.reconcile_drift_detected,
32
+ gates_triggered: trace.gates_triggered,
33
+ threshold_proximity,
34
+ justification,
35
+ scored_at: new Date().toISOString(),
36
+ });
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Verdict derivation — trust posture is the primary signal
41
+ // ---------------------------------------------------------------------------
42
+
43
+ function deriveVerdict(trace: PipelineTrace): "pass" | "fail" | "inconclusive" {
44
+ // Error / rejection: cannot determine
45
+ if (trace.outcome === "rejected" || trace.outcome === "error") {
46
+ return "inconclusive";
47
+ }
48
+
49
+ // Trust posture is the primary verdict driver
50
+ switch (trace.trust_posture) {
51
+ case "verified":
52
+ return "pass";
53
+
54
+ case "cautioned":
55
+ // Cautioned + drift signals = fail (4+ findings separates real drift
56
+ // from structural noise — honest returns typically produce 3, drift 4-5)
57
+ if (trace.reconcile_drift_detected && trace.reconcile_findings_count >= 4) {
58
+ return "fail";
59
+ }
60
+ // Cautioned + gates triggered = fail
61
+ if (trace.gates_triggered.length > 0) {
62
+ return "fail";
63
+ }
64
+ return "pass";
65
+
66
+ case "held":
67
+ case "blocked":
68
+ return "fail";
69
+
70
+ case "unknown":
71
+ return "inconclusive";
72
+
73
+ default:
74
+ // Fallback: no trust posture populated, use spread score heuristic
75
+ if (trace.spread_score > 0.5) return "fail";
76
+ if (trace.spread_score > 0.3) return "inconclusive";
77
+ return "pass";
78
+ }
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Threshold proximity — how close the spread_score is to key boundaries
83
+ // ---------------------------------------------------------------------------
84
+
85
+ function computeThresholdProximity(
86
+ spreadScore: number,
87
+ ): Record<string, { threshold: number; actual: number; margin: number }> {
88
+ const result: Record<string, { threshold: number; actual: number; margin: number }> = {};
89
+
90
+ for (const threshold of SPREAD_THRESHOLDS) {
91
+ const margin = threshold - spreadScore;
92
+ result[`spread_${threshold}`] = {
93
+ threshold,
94
+ actual: spreadScore,
95
+ margin: parseFloat(margin.toFixed(4)),
96
+ };
97
+ }
98
+
99
+ return result;
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------
103
+ // Justification builder — explains why the verdict was reached
104
+ // ---------------------------------------------------------------------------
105
+
106
+ function buildJustification(
107
+ trace: PipelineTrace,
108
+ verdict: "pass" | "fail" | "inconclusive",
109
+ ): string {
110
+ const parts: string[] = [];
111
+
112
+ parts.push(`Pipeline outcome: ${trace.outcome}.`);
113
+
114
+ if (trace.trust_posture) {
115
+ parts.push(`Trust posture: ${trace.trust_posture}.`);
116
+ }
117
+
118
+ if (trace.spread_score > 0) {
119
+ parts.push(`Spread score: ${trace.spread_score.toFixed(3)}.`);
120
+
121
+ // Note proximity to thresholds
122
+ const nearThreshold = SPREAD_THRESHOLDS.find(
123
+ t => Math.abs(t - trace.spread_score) < 0.05,
124
+ );
125
+ if (nearThreshold !== undefined) {
126
+ parts.push(`Score is within 0.05 of the ${nearThreshold} threshold.`);
127
+ }
128
+ }
129
+
130
+ if (trace.reconcile_drift_detected) {
131
+ parts.push("Reconciliation detected drift.");
132
+ }
133
+
134
+ if (trace.gates_triggered.length > 0) {
135
+ parts.push(`Gates triggered: ${trace.gates_triggered.join(", ")}.`);
136
+ }
137
+
138
+ if (trace.honesty_violations.length > 0) {
139
+ parts.push(`Honesty violations on record: ${trace.honesty_violations.length}.`);
140
+ }
141
+
142
+ if (trace.reconcile_findings_count > 0) {
143
+ parts.push(`Reconcile findings: ${trace.reconcile_findings_count}.`);
144
+ }
145
+
146
+ parts.push(`Verdict: ${verdict}.`);
147
+
148
+ return parts.join(" ");
149
+ }
@@ -0,0 +1,23 @@
1
+ baseline_id: baseline-2026-04-09
2
+ created_at: 2026-04-09T10:02:57.137Z
3
+ axis_weights:
4
+ claim_mismatch_ratio: 0.3
5
+ audit_burden: 0.15
6
+ residual_weight: 0.1
7
+ provenance_weakness: 0.15
8
+ gate_distance: 0.15
9
+ trace_failure_signal: 0.1
10
+ budget_correction_weight: 0.05
11
+ gate_thresholds:
12
+ claim_mismatch_ratio: 0.3
13
+ audit_burden: 0.15
14
+ residual_weight: 0.1
15
+ provenance_weakness: 0.15
16
+ gate_distance: 0.15
17
+ trace_failure_signal: 0.1
18
+ budget_correction_weight: 0.05
19
+ reconcile_thresholds:
20
+ drift_warning: 0.3
21
+ drift_block: 0.6
22
+ reconcile_required: 0.5
23
+ notes: ""
@@ -0,0 +1,18 @@
1
+ - run_id: 2026-04-09T09-45-01-838Z
2
+ started_at: 2026-04-09T09:45:01.838Z
3
+ seed_count: 3
4
+ blind_count: 2
5
+ adversarial_count: 1
6
+ replay_count: 0
7
+ trigger: manual
8
+ baseline_snapshot: baseline-2026-04-09
9
+ completed_at: 2026-04-09T09:45:01.867Z
10
+ - run_id: 2026-04-09T10-02-57-143Z
11
+ started_at: 2026-04-09T10:02:57.143Z
12
+ seed_count: 3
13
+ blind_count: 2
14
+ adversarial_count: 1
15
+ replay_count: 0
16
+ trigger: manual
17
+ baseline_snapshot: baseline-2026-04-09
18
+ completed_at: 2026-04-09T10:02:57.171Z
@@ -0,0 +1,9 @@
1
+ seed_id: adv-0bdc944b61d5
2
+ category: agreement_pass
3
+ layer_a_verdict: pass
4
+ layer_b_verdict: inconclusive
5
+ return_mode: honest
6
+ reasoning: One or both layers inconclusive, leaning pass.
7
+ is_disagreement: false
8
+ escalated: false
9
+ escalation_reason: ""
@@ -0,0 +1,9 @@
1
+ seed_id: blind-16cdf0db1b43
2
+ category: agreement_pass
3
+ layer_a_verdict: pass
4
+ layer_b_verdict: inconclusive
5
+ return_mode: honest
6
+ reasoning: One or both layers inconclusive, leaning pass.
7
+ is_disagreement: false
8
+ escalated: false
9
+ escalation_reason: ""
@@ -0,0 +1,9 @@
1
+ seed_id: blind-a6b2c8be67cc
2
+ category: agreement_fail
3
+ layer_a_verdict: fail
4
+ layer_b_verdict: inconclusive
5
+ return_mode: drift
6
+ reasoning: One or both layers inconclusive, leaning fail.
7
+ is_disagreement: false
8
+ escalated: false
9
+ escalation_reason: ""
@@ -0,0 +1,8 @@
1
+ run_id: 2026-04-09T09-45-01-838Z
2
+ started_at: 2026-04-09T09:45:01.838Z
3
+ seed_count: 3
4
+ blind_count: 2
5
+ adversarial_count: 1
6
+ replay_count: 0
7
+ trigger: manual
8
+ baseline_snapshot: baseline-2026-04-09
@@ -0,0 +1,7 @@
1
+ seed_id: adv-0bdc944b61d5
2
+ source: adversarial
3
+ raw_idea: A notes app — just basic note taking, nothing fancy
4
+ complexity_target: simple
5
+ created_at: 2026-04-09T09:45:01.838Z
6
+ replay_source: ""
7
+ attack_vector: scope-inflation-trap
@@ -0,0 +1,8 @@
1
+ seed_id: blind-16cdf0db1b43
2
+ source: blind
3
+ raw_idea: A REST API that converts Markdown files to PDF, accepting a file
4
+ upload and returning the rendered document
5
+ complexity_target: structured
6
+ created_at: 2026-04-09T09:45:01.838Z
7
+ replay_source: ""
8
+ attack_vector: ""
@@ -0,0 +1,8 @@
1
+ seed_id: blind-a6b2c8be67cc
2
+ source: blind
3
+ raw_idea: A CLI tool that counts lines of code in a directory, outputting a
4
+ summary table by file extension
5
+ complexity_target: simple
6
+ created_at: 2026-04-09T09:45:01.838Z
7
+ replay_source: ""
8
+ attack_vector: ""
@@ -0,0 +1,10 @@
1
+ run_id: 2026-04-09T09-45-01-838Z
2
+ seed_count: 3
3
+ gate_catch_rate: 1
4
+ false_positive_rate: 0
5
+ false_negative_rate: 0
6
+ layer_agreement_rate: 1
7
+ diagnosis_count: 0
8
+ escalation_count: 0
9
+ biggest_miss: ""
10
+ completed_at: 2026-04-09T09:45:01.867Z