sibyl-system 1.2.2 → 1.3.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 (4) hide show
  1. package/README.md +57 -14
  2. package/dist/cli.js +393 -199
  3. package/dist/index.js +572 -175
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8,10 +8,40 @@ var __export = (target, all) => {
8
8
  // src/cli.ts
9
9
  import { randomBytes } from "node:crypto";
10
10
  import { spawn as spawn2 } from "node:child_process";
11
+ import { existsSync as existsSync3 } from "node:fs";
11
12
  import { copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6 } from "node:fs/promises";
12
13
  import { basename, dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
13
14
 
14
15
  // src/state/record.ts
16
+ var INDEPENDENCE_STATUSES = ["INDEPENDENT", "NOT-INDEPENDENT", "UNVERIFIABLE"];
17
+ function isIndependenceShape(v) {
18
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
19
+ const o = v;
20
+ if (!INDEPENDENCE_STATUSES.includes(o["status"])) return false;
21
+ if (!Array.isArray(o["convenerChain"]) || !o["convenerChain"].every((s) => typeof s === "string" && s.length > 0)) return false;
22
+ return typeof o["evidence"] === "string";
23
+ }
24
+ function rebuildIndependence(v) {
25
+ return { status: v.status, convenerChain: [...v.convenerChain], evidence: v.evidence };
26
+ }
27
+ var INSTRUMENT_NAME = /^[a-z0-9-]{1,40}$/;
28
+ var HEX64 = /^[0-9a-f]{64}$/;
29
+ function isInstrumentShape(v) {
30
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
31
+ const o = v;
32
+ if (typeof o["rulesHash"] !== "string" || !HEX64.test(o["rulesHash"])) return false;
33
+ const comps = o["components"];
34
+ if (typeof comps !== "object" || comps === null || Array.isArray(comps)) return false;
35
+ return Object.entries(comps).every(
36
+ ([name, hash2]) => INSTRUMENT_NAME.test(name) && typeof hash2 === "string" && HEX64.test(hash2)
37
+ );
38
+ }
39
+ function rebuildInstrument(v) {
40
+ return {
41
+ rulesHash: v.rulesHash,
42
+ components: Object.fromEntries(Object.entries(v.components).map(([name, hash2]) => [name, hash2]))
43
+ };
44
+ }
15
45
  function isNonEmptyString(v) {
16
46
  return typeof v === "string" && v.length > 0;
17
47
  }
@@ -19,8 +49,142 @@ function compactIso(now) {
19
49
  return now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
20
50
  }
21
51
 
22
- // src/state/chamber.ts
52
+ // src/instrument.ts
53
+ import { createHash as createHash3 } from "node:crypto";
54
+
55
+ // src/council/index.ts
56
+ var COUNCILORS = ["MELCHIOR", "BALTHASAR", "CASPER"];
57
+ var OUTPUT_CONTRACT = 'Respond with EXACTLY one JSON object and nothing else - no prose, no markdown fences, no extra keys - matching: {"verdict":"APPROVE"|"REJECT","confidence":<number between 0 and 1>,"reasons":[<strings>],"must_fix":[<strings>]}. "verdict" is your final ballot, "confidence" your certainty (0..1), "reasons" the justification, "must_fix" the concrete defects that block approval (empty array if none).';
58
+ var COUNCILOR_PERSONAS = {
59
+ MELCHIOR: {
60
+ title: "correctness & safety skeptic",
61
+ system: "You are MELCHIOR, the correctness-and-safety skeptic on the SIBYL council. Audit the artifact for logical errors, unproven assumptions, correctness bugs, safety hazards, and irreversible side effects. Approve only if it survives strict technical scrutiny; default to REJECT when doubt remains. " + OUTPUT_CONTRACT
62
+ },
63
+ BALTHASAR: {
64
+ title: "adversarial red-teamer",
65
+ system: "You are BALTHASAR, the adversarial red-teamer on the SIBYL council. Attack the artifact as an adversary: probe abuse cases, exploits, race conditions, input-boundary failures, and ways it breaks under hostile use. If you can name a concrete attack or failure mode, REJECT and put it in must_fix. " + OUTPUT_CONTRACT
66
+ },
67
+ CASPER: {
68
+ title: "pragmatic utilist",
69
+ system: "You are CASPER, the pragmatic utilist on the SIBYL council. Judge the artifact by real-world utility: does it solve the stated problem, is it maintainable, worth the complexity, and shippable? Weigh benefit against cost, but REJECT when the defects outweigh the value. " + OUTPUT_CONTRACT
70
+ }
71
+ };
72
+ var COUNCIL_SIZE = COUNCILORS.length;
73
+ var KNOWN_IDS = new Set(COUNCILORS);
74
+
75
+ // src/chamber/protocol.ts
76
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
77
+ import { join as join2 } from "node:path";
78
+
79
+ // src/lane/seating.ts
23
80
  import { createHash } from "node:crypto";
81
+ function toModelId(model) {
82
+ return `${model.providerID}/${model.modelID}`;
83
+ }
84
+ function policyAllows(modelId, policy) {
85
+ return policy.allowedPrefixes.some((prefix) => prefix.length > 0 && modelId.startsWith(prefix));
86
+ }
87
+ function resolveSeat(role, requestedSlot, pool, policy) {
88
+ const found = pool[requestedSlot] ?? pool["default"];
89
+ if (found === void 0) {
90
+ return {
91
+ ok: false,
92
+ role,
93
+ deny: `seat denied: slot "${requestedSlot}" (and fallback "default") absent from pool`
94
+ };
95
+ }
96
+ const modelId = toModelId(found);
97
+ if (found.providerID.length === 0 || found.modelID.length === 0) {
98
+ return { ok: false, role, deny: `seat denied: slot "${requestedSlot}" is an empty placeholder (${modelId})` };
99
+ }
100
+ if (!policyAllows(modelId, policy)) {
101
+ return {
102
+ ok: false,
103
+ role,
104
+ deny: `seat denied by modelPolicy (E4/F1): "${modelId}" is not under any allowed prefix [${policy.allowedPrefixes.join(", ")}] \u2014 launch must not be attempted`
105
+ };
106
+ }
107
+ return { ok: true, role, slot: requestedSlot, modelId };
108
+ }
109
+ var SEP = "";
110
+ function sha256Hex(text) {
111
+ return createHash("sha256").update(text, "utf8").digest("hex");
112
+ }
113
+ function computeDrawCommit(poolIds, seed) {
114
+ return sha256Hex(`sibyl-draw-v1${SEP}${poolIds.join(SEP)}${SEP}${seed}`);
115
+ }
116
+ function drawJudge(poolIds, seed, salt) {
117
+ if (poolIds.length === 0) {
118
+ return { ok: false, deny: "judge draw denied: empty judge pool" };
119
+ }
120
+ const drawCommit = computeDrawCommit(poolIds, seed);
121
+ const digest = sha256Hex(`${drawCommit}${SEP}${salt}`);
122
+ const uint = Number.parseInt(digest.slice(0, 8), 16);
123
+ const index = uint % poolIds.length;
124
+ const modelId = poolIds[index];
125
+ return { ok: true, draw: { index, modelId, drawCommit } };
126
+ }
127
+
128
+ // src/verdict/index.ts
129
+ var OPEN_BRACE = 123;
130
+ var CLOSE_BRACE = 125;
131
+ var QUOTE = 34;
132
+ var BACKSLASH = 92;
133
+ var FENCE_RE = /```(?:json)?[ \t]*\r?\n?([\s\S]*?)```/gi;
134
+ function findBalancedObject(text, start) {
135
+ if (text.charCodeAt(start) !== OPEN_BRACE) return null;
136
+ let depth = 0;
137
+ let inString = false;
138
+ let escaped = false;
139
+ for (let i = start; i < text.length; i++) {
140
+ const c = text.charCodeAt(i);
141
+ if (inString) {
142
+ if (escaped) escaped = false;
143
+ else if (c === BACKSLASH) escaped = true;
144
+ else if (c === QUOTE) inString = false;
145
+ continue;
146
+ }
147
+ if (c === QUOTE) inString = true;
148
+ else if (c === OPEN_BRACE) depth++;
149
+ else if (c === CLOSE_BRACE) {
150
+ depth--;
151
+ if (depth === 0) return text.slice(start, i + 1);
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ function firstParseableObject(text) {
157
+ for (let i = 0; i < text.length; i++) {
158
+ if (text.charCodeAt(i) !== OPEN_BRACE) continue;
159
+ const candidate = findBalancedObject(text, i);
160
+ if (candidate === null) continue;
161
+ try {
162
+ JSON.parse(candidate);
163
+ return candidate;
164
+ } catch {
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ function extractVerdictJson(text) {
170
+ for (const m of text.matchAll(FENCE_RE)) {
171
+ const fenced = firstParseableObject(m[1] ?? "");
172
+ if (fenced !== null) return fenced;
173
+ }
174
+ return firstParseableObject(text);
175
+ }
176
+ function rejectVerdict(reason) {
177
+ return {
178
+ verdict: "REJECT",
179
+ confidence: 0,
180
+ reasons: [`verdict-unparseable: ${reason}`],
181
+ must_fix: []
182
+ };
183
+ }
184
+ var FAIL_CLOSED_REJECT = rejectVerdict("no-parse-performed");
185
+
186
+ // src/state/chamber.ts
187
+ import { createHash as createHash2 } from "node:crypto";
24
188
  import { mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
25
189
  import { homedir } from "node:os";
26
190
  import { dirname, isAbsolute, join, resolve } from "node:path";
@@ -29,7 +193,7 @@ var TERMINAL_STATES = ["CONVERGED", "NEEDS_ROUND", "MEMBER_LOST", "TIMEOUT"];
29
193
  var RUN_PROFILES = ["review", "exam"];
30
194
  var RECORD_SCHEMA = 1;
31
195
  function sha256Text(text) {
32
- return createHash("sha256").update(text, "utf8").digest("hex");
196
+ return createHash2("sha256").update(text, "utf8").digest("hex");
33
197
  }
34
198
  async function sha256File(path) {
35
199
  let buf;
@@ -39,7 +203,7 @@ async function sha256File(path) {
39
203
  const code = typeof err === "object" && err !== null ? String(Reflect.get(err, "code")) : "unknown";
40
204
  return { ok: false, reason: `read failed (${code})` };
41
205
  }
42
- return { ok: true, sha256: createHash("sha256").update(buf).digest("hex"), bytes: buf.byteLength };
206
+ return { ok: true, sha256: createHash2("sha256").update(buf).digest("hex"), bytes: buf.byteLength };
43
207
  }
44
208
  async function assertOnDisk(path, claim2) {
45
209
  const now = await sha256File(path);
@@ -85,6 +249,12 @@ function validateChamberRecord(raw) {
85
249
  if (!Array.isArray(o["artifacts"]) || !o["artifacts"].every(isArtifactRef)) return { ok: false, reason: "artifacts must be an array of ArtifactRef" };
86
250
  if (typeof o["evidenceRows"] !== "number" || !Number.isInteger(o["evidenceRows"]) || o["evidenceRows"] < 0) return { ok: false, reason: "evidenceRows must be a non-negative integer" };
87
251
  if (o["notes"] !== void 0 && typeof o["notes"] !== "string") return { ok: false, reason: "notes must be a string when present" };
252
+ if (o["instrument"] !== void 0 && !isInstrumentShape(o["instrument"])) {
253
+ return { ok: false, reason: "instrument must be {rulesHash: 64-hex, components: name->64-hex} when present" };
254
+ }
255
+ if (o["independence"] !== void 0 && !isIndependenceShape(o["independence"])) {
256
+ return { ok: false, reason: "independence must be {status: INDEPENDENT|NOT-INDEPENDENT|UNVERIFIABLE, convenerChain: string[], evidence: string} when present" };
257
+ }
88
258
  const record2 = {
89
259
  schema: RECORD_SCHEMA,
90
260
  runId: o["runId"],
@@ -106,6 +276,8 @@ function validateChamberRecord(raw) {
106
276
  evidenceRows: o["evidenceRows"]
107
277
  };
108
278
  if (typeof o["notes"] === "string") record2.notes = o["notes"];
279
+ if (o["instrument"] !== void 0) record2.instrument = rebuildInstrument(o["instrument"]);
280
+ if (o["independence"] !== void 0) record2.independence = rebuildIndependence(o["independence"]);
109
281
  return { ok: true, record: record2 };
110
282
  }
111
283
  async function appendLedgerRow(ledgerPath, record2) {
@@ -245,176 +417,6 @@ function spotcheckCommand(runDir) {
245
417
  return `cd ${runDir} && sha256sum -c ${CHECKSUMS_FILE}`;
246
418
  }
247
419
 
248
- // src/chamber/synthesis.ts
249
- var PERFORMANCE_ONLY_IN_LOOP = "PERFORMANCE-ONLY-IN-LOOP";
250
- var MAX_RATIONALE = 6;
251
- function conclude(result) {
252
- const { terminal, judge, gaps } = result;
253
- if (gaps.length > 0 && (terminal === "MEMBER_LOST" || terminal === "TIMEOUT")) {
254
- return { conclusion: "NEEDS_HUMAN", confidence: 0 };
255
- }
256
- if (terminal === "CONVERGED" && judge !== null) {
257
- const base = judge.conclusion;
258
- const conclusion = base === "NEEDS_HUMAN" ? "NEEDS_HUMAN" : gaps.length > 0 ? "NEEDS_HUMAN" : base;
259
- return { conclusion, confidence: conclusion === "NEEDS_HUMAN" ? Math.min(judge.confidence, 0.5) : judge.confidence };
260
- }
261
- if (judge !== null && judge.convergence === "NEEDS_ROUND") {
262
- return { conclusion: "NEEDS_HUMAN", confidence: Math.min(judge.confidence, 0.5) };
263
- }
264
- return { conclusion: "NEEDS_HUMAN", confidence: 0 };
265
- }
266
- function buildVoice(result) {
267
- const { record: record2, judge, terminal, gaps } = result;
268
- const { conclusion, confidence } = conclude(result);
269
- const rationale = judge !== null && conclusion !== "NEEDS_HUMAN" ? judge.reasons.slice(0, MAX_RATIONALE) : (judge?.reasons ?? []).slice(0, MAX_RATIONALE).concat(gaps).slice(0, MAX_RATIONALE);
270
- const sealed = [
271
- "DISAGREEMENT-LOG.md",
272
- ...new Set(record2.artifacts.map((a) => a.relPath))
273
- ];
274
- return {
275
- label: "SIBYL-ONE-VOICE",
276
- honesty: PERFORMANCE_ONLY_IN_LOOP,
277
- conclusion,
278
- confidence: conclusion === "NEEDS_HUMAN" ? Number(confidence.toFixed(2)) : confidence,
279
- rationale,
280
- must_fix: judge?.must_fix ?? [],
281
- open_charges: judge?.charges.length ?? 0,
282
- dissent_sealed_in: sealed,
283
- terminal,
284
- run_id: record2.runId,
285
- serial: record2.serial,
286
- spotcheck: spotcheckCommand(record2.runDir)
287
- };
288
- }
289
- function renderVoice(voice) {
290
- const lines = [
291
- `SIBYL \u2014 ONE CONCLUSION, ONE VOICE [${voice.label}]`,
292
- `conclusion: ${voice.conclusion} confidence: ${String(voice.confidence)} terminal: ${voice.terminal}`,
293
- `run: ${voice.run_id} (serial ${String(voice.serial)}) open charges: ${String(voice.open_charges)}`,
294
- `honesty: ${voice.honesty} (until C-09/C-14 containment rulings \u2014 see docs/isolation-laws.md)`,
295
- "rationale:",
296
- ...voice.rationale.map((r, i) => ` ${String(i + 1)}. ${r}`)
297
- ];
298
- if (voice.must_fix.length > 0) {
299
- lines.push("must_fix:");
300
- lines.push(...voice.must_fix.map((m) => ` - ${m}`));
301
- }
302
- lines.push(`internal debate sealed in run dir: ${voice.dissent_sealed_in.length} artifacts + DISAGREEMENT-LOG.md (hashes in CHECKSUMS.txt)`);
303
- lines.push(`verify every recorded byte: ${voice.spotcheck}`);
304
- return lines.join("\n");
305
- }
306
-
307
- // src/chamber/protocol.ts
308
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
309
- import { join as join2 } from "node:path";
310
-
311
- // src/lane/seating.ts
312
- import { createHash as createHash2 } from "node:crypto";
313
- function toModelId(model) {
314
- return `${model.providerID}/${model.modelID}`;
315
- }
316
- function policyAllows(modelId, policy) {
317
- return policy.allowedPrefixes.some((prefix) => prefix.length > 0 && modelId.startsWith(prefix));
318
- }
319
- function resolveSeat(role, requestedSlot, pool, policy) {
320
- const found = pool[requestedSlot] ?? pool["default"];
321
- if (found === void 0) {
322
- return {
323
- ok: false,
324
- role,
325
- deny: `seat denied: slot "${requestedSlot}" (and fallback "default") absent from pool`
326
- };
327
- }
328
- const modelId = toModelId(found);
329
- if (found.providerID.length === 0 || found.modelID.length === 0) {
330
- return { ok: false, role, deny: `seat denied: slot "${requestedSlot}" is an empty placeholder (${modelId})` };
331
- }
332
- if (!policyAllows(modelId, policy)) {
333
- return {
334
- ok: false,
335
- role,
336
- deny: `seat denied by modelPolicy (E4/F1): "${modelId}" is not under any allowed prefix [${policy.allowedPrefixes.join(", ")}] \u2014 launch must not be attempted`
337
- };
338
- }
339
- return { ok: true, role, slot: requestedSlot, modelId };
340
- }
341
- var SEP = "";
342
- function sha256Hex(text) {
343
- return createHash2("sha256").update(text, "utf8").digest("hex");
344
- }
345
- function computeDrawCommit(poolIds, seed) {
346
- return sha256Hex(`sibyl-draw-v1${SEP}${poolIds.join(SEP)}${SEP}${seed}`);
347
- }
348
- function drawJudge(poolIds, seed, salt) {
349
- if (poolIds.length === 0) {
350
- return { ok: false, deny: "judge draw denied: empty judge pool" };
351
- }
352
- const drawCommit = computeDrawCommit(poolIds, seed);
353
- const digest = sha256Hex(`${drawCommit}${SEP}${salt}`);
354
- const uint = Number.parseInt(digest.slice(0, 8), 16);
355
- const index = uint % poolIds.length;
356
- const modelId = poolIds[index];
357
- return { ok: true, draw: { index, modelId, drawCommit } };
358
- }
359
-
360
- // src/verdict/index.ts
361
- var OPEN_BRACE = 123;
362
- var CLOSE_BRACE = 125;
363
- var QUOTE = 34;
364
- var BACKSLASH = 92;
365
- var FENCE_RE = /```(?:json)?[ \t]*\r?\n?([\s\S]*?)```/gi;
366
- function findBalancedObject(text, start) {
367
- if (text.charCodeAt(start) !== OPEN_BRACE) return null;
368
- let depth = 0;
369
- let inString = false;
370
- let escaped = false;
371
- for (let i = start; i < text.length; i++) {
372
- const c = text.charCodeAt(i);
373
- if (inString) {
374
- if (escaped) escaped = false;
375
- else if (c === BACKSLASH) escaped = true;
376
- else if (c === QUOTE) inString = false;
377
- continue;
378
- }
379
- if (c === QUOTE) inString = true;
380
- else if (c === OPEN_BRACE) depth++;
381
- else if (c === CLOSE_BRACE) {
382
- depth--;
383
- if (depth === 0) return text.slice(start, i + 1);
384
- }
385
- }
386
- return null;
387
- }
388
- function firstParseableObject(text) {
389
- for (let i = 0; i < text.length; i++) {
390
- if (text.charCodeAt(i) !== OPEN_BRACE) continue;
391
- const candidate = findBalancedObject(text, i);
392
- if (candidate === null) continue;
393
- try {
394
- JSON.parse(candidate);
395
- return candidate;
396
- } catch {
397
- }
398
- }
399
- return null;
400
- }
401
- function extractVerdictJson(text) {
402
- for (const m of text.matchAll(FENCE_RE)) {
403
- const fenced = firstParseableObject(m[1] ?? "");
404
- if (fenced !== null) return fenced;
405
- }
406
- return firstParseableObject(text);
407
- }
408
- function rejectVerdict(reason) {
409
- return {
410
- verdict: "REJECT",
411
- confidence: 0,
412
- reasons: [`verdict-unparseable: ${reason}`],
413
- must_fix: []
414
- };
415
- }
416
- var FAIL_CLOSED_REJECT = rejectVerdict("no-parse-performed");
417
-
418
420
  // src/chamber/protocol.ts
419
421
  var CHAMBER_ROLES = ["evidence", "pro", "con", "judge"];
420
422
  function artifactRel(role, round, kind) {
@@ -679,17 +681,184 @@ async function startRecord(cfg, roster, drawCommit) {
679
681
  artifacts: [],
680
682
  evidenceRows: 0
681
683
  };
684
+ if (cfg.instrument !== void 0) base.instrument = cfg.instrument;
685
+ if (cfg.independence !== void 0) base.independence = cfg.independence;
682
686
  const first = await finalizeRecord(base);
683
687
  if (!first.ok) throw new Error(`START record refused: ${first.drift.join("; ")}`);
684
688
  return first.record;
685
689
  }
686
690
 
691
+ // src/personas.ts
692
+ var ARCHITECT_OUTPUT_CONTRACT = 'Respond with EXACTLY one JSON object and nothing else - no prose, no markdown fences, no extra keys - matching: {"tasks":[{"id":<string>,"title":<string>,"instructions":<string>,"dependsOn":[<task id strings>]}],"concurrency":<integer >= 1>,"notes":<string>}. "tasks" are the units of work decomposing the goal, "dependsOn" lists task ids that must finish first, "concurrency" is the maximum number of tasks safe to run in parallel, "notes" carries planning remarks (empty string if none).';
693
+ var ARCHITECT_SYSTEM = "You are ARCHITECT, the SIBYL planning persona. Decompose the given artifact-production goal into a dependency-ordered workflow of minimal, independently executable tasks. " + ARCHITECT_OUTPUT_CONTRACT;
694
+ var ARCHITECT_PERSONA = {
695
+ id: "ARCHITECT",
696
+ title: "workflow architect (planner)",
697
+ system: ARCHITECT_SYSTEM,
698
+ modelSlot: "architect"
699
+ };
700
+ function repairDemand(why) {
701
+ return `Your previous reply was not a valid verdict JSON (${why}). Reply again with EXACTLY one JSON object and nothing else - no prose, no markdown fences: {"verdict":"APPROVE"|"REJECT","confidence":<number between 0 and 1>,"reasons":[<strings>],"must_fix":[<strings>]}`;
702
+ }
703
+ var SWARM_JUDGE_SYSTEM = "You are the SIBYL swarm judge. You rule on pipeline reports with exactly one word: APPROVE, REJECT, or EXHAUSTED.";
704
+ var SWARM_JUDGE_WORD_CONTRACT = "Respond with EXACTLY one word and nothing else: APPROVE, REJECT, or EXHAUSTED.";
705
+ var PERSONAS = {
706
+ MELCHIOR: {
707
+ id: "MELCHIOR",
708
+ title: COUNCILOR_PERSONAS.MELCHIOR.title,
709
+ system: COUNCILOR_PERSONAS.MELCHIOR.system,
710
+ modelSlot: "melchior"
711
+ },
712
+ BALTHASAR: {
713
+ id: "BALTHASAR",
714
+ title: COUNCILOR_PERSONAS.BALTHASAR.title,
715
+ system: COUNCILOR_PERSONAS.BALTHASAR.system,
716
+ modelSlot: "balthasar"
717
+ },
718
+ CASPER: {
719
+ id: "CASPER",
720
+ title: COUNCILOR_PERSONAS.CASPER.title,
721
+ system: COUNCILOR_PERSONAS.CASPER.system,
722
+ modelSlot: "casper"
723
+ },
724
+ ARCHITECT: ARCHITECT_PERSONA
725
+ };
726
+ var PERSONA_IDS = [...COUNCILORS, "ARCHITECT"];
727
+ var BY_ID = new Map(
728
+ PERSONA_IDS.map((id) => [id, PERSONAS[id]])
729
+ );
730
+
731
+ // src/instrument.ts
732
+ function sha256Hex2(text) {
733
+ return createHash3("sha256").update(text, "utf8").digest("hex");
734
+ }
735
+ var REPAIR_SENTINEL = "<parse-failure-why>";
736
+ var SENTINEL_CFG = {
737
+ runId: "sibyl-sentinel",
738
+ runDir: "/sentinel/run",
739
+ goal: "<goal>",
740
+ targetPath: "/sentinel/target.md",
741
+ ledgerPath: "/sentinel/ledger.jsonl",
742
+ seed: "<seed>",
743
+ maxRounds: 1,
744
+ pool: { default: { providerID: "", modelID: "" } },
745
+ policy: null,
746
+ slots: { evidence: "default", pro: "default", con: "default", judge: "default" },
747
+ judgePoolIds: [],
748
+ lane: null
749
+ };
750
+ function chamberRoleTexts() {
751
+ return {
752
+ "chamber-evidence-prompt": evidencePrompt(SENTINEL_CFG, "/sentinel/evidence.jsonl"),
753
+ "chamber-pro-prompt": proPrompt(SENTINEL_CFG, 1, "/sentinel/pro.md", []),
754
+ "chamber-con-prompt": conPrompt(SENTINEL_CFG, 1, "/sentinel/con.md", []),
755
+ "chamber-rebuttal-prompt": rebuttalPrompt(SENTINEL_CFG, "pro", 1, "/sentinel/con.md", "/sentinel/pro-rebuttal.md"),
756
+ "chamber-judge-prompt": judgePrompt(SENTINEL_CFG, 1, ["/sentinel/pro.md", "/sentinel/con.md"], "/sentinel/judge.json")
757
+ };
758
+ }
759
+ function componentTexts() {
760
+ const texts = {
761
+ "repair-grammar": repairDemand(REPAIR_SENTINEL),
762
+ "swarm-judge-system": SWARM_JUDGE_SYSTEM,
763
+ "swarm-judge-word-contract": SWARM_JUDGE_WORD_CONTRACT,
764
+ ...chamberRoleTexts()
765
+ };
766
+ for (const id of COUNCILORS) {
767
+ texts[`councilor-${id.toLowerCase()}`] = COUNCILOR_PERSONAS[id].system;
768
+ }
769
+ texts["architect-system"] = ARCHITECT_SYSTEM;
770
+ return texts;
771
+ }
772
+ var cached = null;
773
+ function instrumentFace() {
774
+ if (cached !== null) return cached;
775
+ const components = {};
776
+ for (const [name, text] of Object.entries(componentTexts())) {
777
+ components[name] = sha256Hex2(text);
778
+ }
779
+ const canonical = JSON.stringify(
780
+ Object.keys(components).sort().map((name) => [name, components[name]])
781
+ );
782
+ cached = { rulesHash: sha256Hex2(canonical), components };
783
+ return cached;
784
+ }
785
+
786
+ // src/chamber/synthesis.ts
787
+ var PERFORMANCE_ONLY_IN_LOOP = "PERFORMANCE-ONLY-IN-LOOP";
788
+ var MAX_RATIONALE = 6;
789
+ function conclude(result) {
790
+ const { terminal, judge, gaps } = result;
791
+ if (gaps.length > 0 && (terminal === "MEMBER_LOST" || terminal === "TIMEOUT")) {
792
+ return { conclusion: "NEEDS_HUMAN", confidence: 0 };
793
+ }
794
+ if (terminal === "CONVERGED" && judge !== null) {
795
+ const base = judge.conclusion;
796
+ const conclusion = base === "NEEDS_HUMAN" ? "NEEDS_HUMAN" : gaps.length > 0 ? "NEEDS_HUMAN" : base;
797
+ return { conclusion, confidence: conclusion === "NEEDS_HUMAN" ? Math.min(judge.confidence, 0.5) : judge.confidence };
798
+ }
799
+ if (judge !== null && judge.convergence === "NEEDS_ROUND") {
800
+ return { conclusion: "NEEDS_HUMAN", confidence: Math.min(judge.confidence, 0.5) };
801
+ }
802
+ return { conclusion: "NEEDS_HUMAN", confidence: 0 };
803
+ }
804
+ function buildVoice(result) {
805
+ const { record: record2, judge, terminal, gaps } = result;
806
+ const { conclusion, confidence } = conclude(result);
807
+ const rationale = judge !== null && conclusion !== "NEEDS_HUMAN" ? judge.reasons.slice(0, MAX_RATIONALE) : (judge?.reasons ?? []).slice(0, MAX_RATIONALE).concat(gaps).slice(0, MAX_RATIONALE);
808
+ const sealed = [
809
+ "DISAGREEMENT-LOG.md",
810
+ ...new Set(record2.artifacts.map((a) => a.relPath))
811
+ ];
812
+ return {
813
+ label: "SIBYL-ONE-VOICE",
814
+ honesty: PERFORMANCE_ONLY_IN_LOOP,
815
+ conclusion,
816
+ confidence: conclusion === "NEEDS_HUMAN" ? Number(confidence.toFixed(2)) : confidence,
817
+ rationale,
818
+ must_fix: judge?.must_fix ?? [],
819
+ open_charges: judge?.charges.length ?? 0,
820
+ dissent_sealed_in: sealed,
821
+ terminal,
822
+ run_id: record2.runId,
823
+ serial: record2.serial,
824
+ spotcheck: spotcheckCommand(record2.runDir)
825
+ };
826
+ }
827
+ function renderVoice(voice) {
828
+ const lines = [
829
+ `SIBYL \u2014 ONE CONCLUSION, ONE VOICE [${voice.label}]`,
830
+ `conclusion: ${voice.conclusion} confidence: ${String(voice.confidence)} terminal: ${voice.terminal}`,
831
+ `run: ${voice.run_id} (serial ${String(voice.serial)}) open charges: ${String(voice.open_charges)}`,
832
+ `honesty: ${voice.honesty} (until C-09/C-14 containment rulings \u2014 see docs/isolation-laws.md)`,
833
+ "rationale:",
834
+ ...voice.rationale.map((r, i) => ` ${String(i + 1)}. ${r}`)
835
+ ];
836
+ if (voice.must_fix.length > 0) {
837
+ lines.push("must_fix:");
838
+ lines.push(...voice.must_fix.map((m) => ` - ${m}`));
839
+ }
840
+ lines.push(`internal debate sealed in run dir: ${voice.dissent_sealed_in.length} artifacts + DISAGREEMENT-LOG.md (hashes in CHECKSUMS.txt)`);
841
+ lines.push(`verify every recorded byte: ${voice.spotcheck}`);
842
+ return lines.join("\n");
843
+ }
844
+
687
845
  // src/lane/isolated.ts
688
846
  import { spawn } from "node:child_process";
689
847
  import { createWriteStream, existsSync } from "node:fs";
690
848
  import { copyFile, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
691
849
  import { dirname as dirname2, join as join3 } from "node:path";
692
850
  var DEFAULT_PINNED_PATH = "/usr/bin:/bin";
851
+ function composeSandboxArgs(opts) {
852
+ const argv = [opts.bwrapBin, "--ro-bind", "/", "/", "--dev", "/dev", "--die-with-parent"];
853
+ for (const m of opts.maskDirs) argv.push("--tmpfs", m);
854
+ for (const k of opts.keepDirs) argv.push("--dir", posixDirname(k), "--bind", k, k);
855
+ argv.push("--", opts.bin, ...opts.args);
856
+ return argv;
857
+ }
858
+ function posixDirname(p) {
859
+ const i = p.lastIndexOf("/");
860
+ return i <= 0 ? "/" : p.slice(0, i);
861
+ }
693
862
  function rolePaths(cfg, role) {
694
863
  const roleDir = join3(cfg.runDir, role);
695
864
  const home = join3(roleDir, "home");
@@ -754,8 +923,12 @@ async function launchRole(cfg, role, spec) {
754
923
  await writeFile3(transcriptPath, "", "utf8");
755
924
  await writeFile3(stderrPath, "", "utf8");
756
925
  const args = composeRunArgs(spec);
926
+ const bwrap = cfg.bwrapBin ?? "/usr/bin/bwrap";
927
+ const mask = cfg.sandboxMaskDirs ?? [];
928
+ const sandboxed = mask.length > 0 && existsSync(bwrap);
929
+ const spawnArgv = sandboxed ? composeSandboxArgs({ bwrapBin: bwrap, maskDirs: mask, keepDirs: [cfg.runDir], bin: cfg.opencodeBin, args }) : [cfg.opencodeBin, ...args];
757
930
  return new Promise((resolvePromise) => {
758
- const child = spawn(cfg.opencodeBin, args, {
931
+ const child = spawn(spawnArgv[0], spawnArgv.slice(1), {
759
932
  cwd: paths.workspace,
760
933
  env,
761
934
  detached: true,
@@ -777,7 +950,8 @@ async function launchRole(cfg, role, spec) {
777
950
  transcriptPath,
778
951
  sessionId: extractSessionId(transcript),
779
952
  startedAt,
780
- endedAt: (/* @__PURE__ */ new Date()).toISOString()
953
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
954
+ sandboxed
781
955
  };
782
956
  if (spawnError2 !== void 0) result.error = spawnError2;
783
957
  resolvePromise(result);
@@ -1737,7 +1911,7 @@ __export(util_exports, {
1737
1911
  attachSchema: () => attachSchema,
1738
1912
  base64ToUint8Array: () => base64ToUint8Array,
1739
1913
  base64urlToUint8Array: () => base64urlToUint8Array,
1740
- cached: () => cached,
1914
+ cached: () => cached2,
1741
1915
  captureStackTrace: () => captureStackTrace,
1742
1916
  cleanEnum: () => cleanEnum,
1743
1917
  cleanRegex: () => cleanRegex,
@@ -1825,7 +1999,7 @@ function jsonStringifyReplacer(_, value) {
1825
1999
  return value.toString();
1826
2000
  return value;
1827
2001
  }
1828
- function cached(getter) {
2002
+ function cached2(getter) {
1829
2003
  const set2 = false;
1830
2004
  return {
1831
2005
  get value() {
@@ -1934,7 +2108,7 @@ var captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace :
1934
2108
  function isObject(data) {
1935
2109
  return typeof data === "object" && data !== null && !Array.isArray(data);
1936
2110
  }
1937
- var allowsEval = /* @__PURE__ */ cached(() => {
2111
+ var allowsEval = /* @__PURE__ */ cached2(() => {
1938
2112
  if (globalConfig.jitless) {
1939
2113
  return false;
1940
2114
  }
@@ -4628,7 +4802,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
4628
4802
  }
4629
4803
  });
4630
4804
  }
4631
- const _normalized = cached(() => normalizeDef(def));
4805
+ const _normalized = cached2(() => normalizeDef(def));
4632
4806
  defineLazyInternal(inst, "propValues", (zod) => {
4633
4807
  const shape = zod.def.shape;
4634
4808
  const propValues = {};
@@ -4688,7 +4862,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
4688
4862
  var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) => {
4689
4863
  $ZodObject.init(inst, def);
4690
4864
  const superParse = inst._zod.parse;
4691
- const _normalized = cached(() => normalizeDef(def));
4865
+ const _normalized = cached2(() => normalizeDef(def));
4692
4866
  const memo2 = globalConfig.memoizer;
4693
4867
  const generateFastpass = (shape) => {
4694
4868
  const normalized = _normalized.value;
@@ -4967,7 +5141,7 @@ var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnio
4967
5141
  throw new Error(`Invalid discriminated union option at index "${i}"`);
4968
5142
  }
4969
5143
  });
4970
- const disc = cached(() => {
5144
+ const disc = cached2(() => {
4971
5145
  const opts = def.options;
4972
5146
  const map2 = /* @__PURE__ */ new Map();
4973
5147
  for (const o of opts) {
@@ -6050,9 +6224,9 @@ function cloneIssues(issues) {
6050
6224
  }
6051
6225
  var recursive = /* @__PURE__ */ new WeakMap();
6052
6226
  function isRecursive(inst, stack) {
6053
- const cached2 = recursive.get(inst);
6054
- if (cached2 !== void 0)
6055
- return cached2;
6227
+ const cached3 = recursive.get(inst);
6228
+ if (cached3 !== void 0)
6229
+ return cached3;
6056
6230
  if (stack.has(inst))
6057
6231
  return true;
6058
6232
  stack.add(inst);
@@ -19808,15 +19982,15 @@ function visit(schema, fnOrHandlers) {
19808
19982
  };
19809
19983
  const cache = /* @__PURE__ */ new Map();
19810
19984
  function run(s) {
19811
- const cached2 = cache.get(s);
19812
- if (cached2 === RESOLVING) {
19985
+ const cached3 = cache.get(s);
19986
+ if (cached3 === RESOLVING) {
19813
19987
  return new $ZodLazy({
19814
19988
  type: "lazy",
19815
19989
  getter: () => cache.get(s)
19816
19990
  });
19817
19991
  }
19818
- if (cached2 !== void 0)
19819
- return cached2;
19992
+ if (cached3 !== void 0)
19993
+ return cached3;
19820
19994
  cache.set(s, RESOLVING);
19821
19995
  const inner = mapInner(s);
19822
19996
  const mapped = fn(inner, inner !== s);
@@ -20065,7 +20239,10 @@ var pluginOptionsSchema = external_exports.object({
20065
20239
  runRoot: external_exports.string().min(1).default("/tmp"),
20066
20240
  opencodeBin: external_exports.string().min(1).prefault(discoverOpencodeBin),
20067
20241
  configSource: external_exports.string().prefault(() => join6(homedir2(), ".config", "opencode", "opencode.jsonc")),
20068
- roleTimeoutMs: external_exports.number().int().min(5e3).default(6e5)
20242
+ roleTimeoutMs: external_exports.number().int().min(5e3).default(6e5),
20243
+ /** Exam venue: absolute dirs tmpfs-masked from the candidate (grader files,
20244
+ * other runs). Empty = venue runs unsandboxed and SAYS SO on the voice face. */
20245
+ examMaskDirs: external_exports.array(external_exports.string().min(2)).default([])
20069
20246
  }).prefault({}),
20070
20247
  /** v1.1 E4/F1: seats only from allowed model-id prefixes; deny at launch. */
20071
20248
  modelPolicy: external_exports.object({
@@ -20192,9 +20369,19 @@ async function resolveRun(flags) {
20192
20369
  judgePoolIds.push(...flags["judge-pool"].split(",").map((s) => s.trim()).filter((s) => s.length > 0));
20193
20370
  }
20194
20371
  const now = /* @__PURE__ */ new Date();
20195
- const runId = `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
20372
+ const flagRunId = typeof flags["run-id"] === "string" ? flags["run-id"] : null;
20373
+ if (flagRunId !== null && !/^sibyl-[A-Za-z0-9-]+$/.test(flagRunId)) {
20374
+ return { ok: false, error: `--run-id must be a bare sibyl run id, got "${flagRunId}"` };
20375
+ }
20376
+ const runId = flagRunId ?? `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
20196
20377
  const runDir = join7(laneCfg.runRoot, `sibyl-run-${runId}`);
20197
20378
  await mkdir6(runDir, { recursive: true });
20379
+ let launchIndependence;
20380
+ try {
20381
+ const lc = JSON.parse(await readFile6(join7(runDir, "launch-context.json"), "utf8"));
20382
+ if (isIndependenceShape(lc["independence"])) launchIndependence = lc["independence"];
20383
+ } catch {
20384
+ }
20198
20385
  let targetPath = targetRaw;
20199
20386
  if (targetRaw !== "-") {
20200
20387
  const abs = isAbsolute2(targetRaw) ? targetRaw : resolve2(process.cwd(), targetRaw);
@@ -20227,7 +20414,7 @@ async function resolveRun(flags) {
20227
20414
  if (scenarios.length === 0) return { ok: false, error: "--profile exam needs at least one --scenario" };
20228
20415
  const only = splitModelId(modelFlag ?? "") ?? null;
20229
20416
  if (only === null) return { ok: false, error: "--profile exam requires --model provider/model" };
20230
- return { ok: true, run: { runId, runDir, laneCfg, timeoutMs, chamber: null, exam: { scenarios, modelId: modelFlag }, killInjection } };
20417
+ return { ok: true, run: { runId, runDir, laneCfg, timeoutMs, chamber: null, exam: { scenarios, modelId: modelFlag }, killInjection, examMaskDirs: opts?.lane.examMaskDirs ?? [] } };
20231
20418
  }
20232
20419
  if (profile !== "review") return { ok: false, error: `unknown --profile ${profile}` };
20233
20420
  const slots = {
@@ -20252,13 +20439,15 @@ async function resolveRun(flags) {
20252
20439
  maxRounds,
20253
20440
  pool: effectivePool,
20254
20441
  policy,
20442
+ instrument: instrumentFace(),
20443
+ independence: launchIndependence,
20255
20444
  slots,
20256
20445
  judgePoolIds: judgePoolIds.length > 0 ? judgePoolIds : [...new Set(judgeSlotNames.map((s) => slotToId(s, effectivePool)).filter((x) => x !== null))],
20257
20446
  lane: null
20258
20447
  // wired below (type seam: lane needs cfg, cfg needs lane)
20259
20448
  };
20260
20449
  chamber.lane = makeIsolatedLane(chamber, laneCfg, timeoutMs);
20261
- return { ok: true, run: { runId, runDir, laneCfg, timeoutMs, chamber, exam: null, killInjection } };
20450
+ return { ok: true, run: { runId, runDir, laneCfg, timeoutMs, chamber, exam: null, killInjection, examMaskDirs: opts?.lane.examMaskDirs ?? [] } };
20262
20451
  }
20263
20452
  function slotToId(slot, pool) {
20264
20453
  const m = pool[slot] ?? pool["default"];
@@ -20282,8 +20471,8 @@ function makeIsolatedLane(cfg, laneCfg, timeoutMs) {
20282
20471
  }
20283
20472
  };
20284
20473
  }
20285
- function makeExamDriver(laneCfgBase, runDir, modelId, timeoutMs) {
20286
- const lc = { runDir, opencodeBin: laneCfgBase.opencodeBin, configSource: laneCfgBase.configSource };
20474
+ function makeExamDriver(laneCfgBase, runDir, modelId, timeoutMs, maskDirs, bwrapProbe) {
20475
+ const lc = { runDir, opencodeBin: laneCfgBase.opencodeBin, configSource: laneCfgBase.configSource, sandboxMaskDirs: maskDirs, bwrapBin: bwrapProbe };
20287
20476
  let turnNo = 0;
20288
20477
  const run = async (message, resume) => {
20289
20478
  turnNo += 1;
@@ -20300,11 +20489,14 @@ function makeExamDriver(laneCfgBase, runDir, modelId, timeoutMs) {
20300
20489
  return { facts: { ok: o.ok, rc: o.rc, signal: o.signal, timedOut: o.timedOut }, transcript, sessionId: o.sessionId };
20301
20490
  };
20302
20491
  return {
20303
- start: async (prompt) => await run(prompt),
20304
- continueTurn: async (sid, message) => await run(message, sid)
20492
+ sandboxed: maskDirs.length > 0 && existsSync3(bwrapProbe),
20493
+ driver: {
20494
+ start: async (prompt) => await run(prompt),
20495
+ continueTurn: async (sid, message) => await run(message, sid)
20496
+ }
20305
20497
  };
20306
20498
  }
20307
- function examVoice(results, runId, runDir) {
20499
+ function examVoice(results, runId, runDir, venueSandbox) {
20308
20500
  const lines = ["SIBYL \u2014 ONE CONCLUSION, ONE VOICE [SIBYL-ONE-VOICE]"];
20309
20501
  let anyFail = false;
20310
20502
  let anyHuman = false;
@@ -20318,6 +20510,7 @@ function examVoice(results, runId, runDir) {
20318
20510
  const conclusion = anyFail ? "REJECT" : anyHuman ? "NEEDS_HUMAN" : "APPROVE";
20319
20511
  lines.splice(1, 0, `conclusion: ${conclusion} terminal: CONVERGED run: ${runId}`);
20320
20512
  lines.push(`honesty: PERFORMANCE-ONLY-IN-LOOP (single-uid box until C-09/C-14 rulings)`);
20513
+ lines.push(`venue sandbox: ${venueSandbox}`);
20321
20514
  lines.push(`verify every recorded byte: ${spotcheckCommand(runDir)}`);
20322
20515
  return lines.join("\n");
20323
20516
  }
@@ -20381,7 +20574,7 @@ async function main(argv) {
20381
20574
  return 0;
20382
20575
  }
20383
20576
  if (run.exam !== null) {
20384
- const driver = makeExamDriver(run.laneCfg, run.runDir, run.exam.modelId, run.timeoutMs);
20577
+ const { driver, sandboxed } = makeExamDriver(run.laneCfg, run.runDir, run.exam.modelId, run.timeoutMs, run.examMaskDirs, "/usr/bin/bwrap");
20385
20578
  const results = [];
20386
20579
  const scPaths = typeof flags["scenario"] === "string" ? [flags["scenario"]] : [];
20387
20580
  if (flags["allow-unbellied"] !== true) {
@@ -20395,7 +20588,8 @@ async function main(argv) {
20395
20588
  }
20396
20589
  }
20397
20590
  for (const sc of run.exam.scenarios) results.push(await runExamScenario(sc, run.runDir, driver));
20398
- console.log(examVoice(results, run.runId, run.runDir));
20591
+ await writeChecksums(run.runDir, await listChecksumTargets(run.runDir));
20592
+ console.log(examVoice(results, run.runId, run.runDir, sandboxed ? `ON (masked ${String(run.examMaskDirs.length)} dir(s))` : run.examMaskDirs.length === 0 ? "OFF (lane.examMaskDirs empty \u2014 grader files are candidate-readable)" : "OFF (bwrap missing at /usr/bin/bwrap)"));
20399
20593
  return 0;
20400
20594
  }
20401
20595
  return 2;