sibyl-system 1.3.6 → 1.4.0

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 CHANGED
@@ -186,6 +186,13 @@ Plainly stated:
186
186
  A kinship hit stamps the run `independence=NOT-INDEPENDENT`: the ballot is
187
187
  preserved as data but is not in any effective path. A chain that cannot be
188
188
  read stamps `UNVERIFIABLE` — unreadable is never silently clean.
189
+ - **Refused launches leave records (v1.4.0, council 16a2):** an explicit `--run-id`
190
+ whose tool-staged `launch-context.json` is missing or malformed is REFUSED, with a
191
+ `launch-refusal.json` written into the private run dir — a pinned id claiming a
192
+ tool launch that never staged its finding is a provenance lie, not an absence.
193
+ The bare CLI lane (no `--run-id`) keeps the named `UNVERIFIABLE` leg, and once the
194
+ chamber runner regenerates its run record, that face outvouches any contradicted
195
+ staged claim ("record wins").
189
196
  - **Absence is a disability (W2).** Before any tally is honored, every declared
190
197
  voter/worker must have left a terminal row (`ok|error|timeout` + detail) in
191
198
  the run's `TERMINALS.txt`. A declared seat without its row is counted
@@ -204,7 +211,9 @@ Plainly stated:
204
211
  - Consult/swarm send the artifact text to your configured model pool providers.
205
212
  - The plugin writes under its state paths (the runs file and the per-run space) and,
206
213
  for chamber/exam lanes, sealed run directories under `lane.runRoot`
207
- (default `/tmp`) — role homes, transcripts, pidfiles, CHECKSUMS.
214
+ (default `/tmp`), every plugin-created dir `0700` (mkdir mode + explicit chmod:
215
+ unreachable to group/other regardless of umask — v1.4.0 private-dir law) —
216
+ role homes, transcripts, pidfiles, CHECKSUMS.
208
217
  - Voter/worker turns run with `bash`, `edit`, and `write` disabled per prompt.
209
218
 
210
219
  ## Architecture (`src/`)
@@ -263,7 +272,7 @@ PLAN→MINT→DISPATCH→AGGREGATE pipeline over ordinary child sessions.
263
272
 
264
273
  ```bash
265
274
  npm run typecheck # tsc --noEmit, strict + noUncheckedIndexedAccess + exactOptionalPropertyTypes
266
- npm test # full unit suite, fully offline (no network, no LLM; ~420 at v1.3.0 — the run prints the exact count)
275
+ npm test # full unit suite, fully offline (no network, no LLM; 459 at v1.4.0 — the run prints the exact count)
267
276
  npm run build # esbuild bundle → dist/index.js + dist/cli.js (ESM)
268
277
  node smoke/run-smoke.mjs # offline smoke of the shipped surface (see smoke/README.md)
269
278
  ./ship.sh # the full release gate: all of the above + portability scan of the packed tarball
package/dist/cli.js CHANGED
@@ -9,7 +9,16 @@ var __export = (target, all) => {
9
9
  import { randomBytes } from "node:crypto";
10
10
  import { spawn as spawn2 } from "node:child_process";
11
11
  import { existsSync as existsSync3 } from "node:fs";
12
- import { copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6 } from "node:fs/promises";
12
+ import { access, copyFile as copyFile2, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
13
+
14
+ // src/fsperm.ts
15
+ import { chmod, mkdir } from "node:fs/promises";
16
+ async function mkdirPrivate(dir) {
17
+ await mkdir(dir, { recursive: true, mode: 448 });
18
+ await chmod(dir, 448);
19
+ }
20
+
21
+ // src/cli.ts
13
22
  import { basename, dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "node:path";
14
23
 
15
24
  // src/state/record.ts
@@ -73,7 +82,7 @@ var COUNCIL_SIZE = COUNCILORS.length;
73
82
  var KNOWN_IDS = new Set(COUNCILORS);
74
83
 
75
84
  // src/chamber/protocol.ts
76
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
85
+ import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
77
86
  import { join as join2 } from "node:path";
78
87
 
79
88
  // src/lane/seating.ts
@@ -185,7 +194,7 @@ var FAIL_CLOSED_REJECT = rejectVerdict("no-parse-performed");
185
194
 
186
195
  // src/state/chamber.ts
187
196
  import { createHash as createHash2 } from "node:crypto";
188
- import { mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
197
+ import { mkdir as mkdir2, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
189
198
  import { homedir } from "node:os";
190
199
  import { dirname, isAbsolute, join, resolve } from "node:path";
191
200
  var DEFAULT_CHAMBER_LEDGER = join(homedir(), ".sibyl", "chamber-ledger.jsonl");
@@ -293,7 +302,7 @@ async function appendLedgerRow(ledgerPath, record2) {
293
302
  const stamped = { ...record2, serial };
294
303
  const line = JSON.stringify(stamped);
295
304
  if (line.includes("\n")) return { ok: false, reason: "row serializes with a newline \u2014 refused (ledger is one-JSON-per-line)" };
296
- await mkdir(dirname(ledgerPath), { recursive: true });
305
+ await mkdir2(dirname(ledgerPath), { recursive: true });
297
306
  await writeFile(ledgerPath, `${line}
298
307
  `, { flag: "a" });
299
308
  return { ok: true, serial, line };
@@ -334,7 +343,7 @@ var RECORD_FILE = "run-record.json";
334
343
  var CHECKSUMS_FILE = "CHECKSUMS.txt";
335
344
  var LEDGER_LINE_FILE = "ledger-line.txt";
336
345
  async function atomicWrite(path, content) {
337
- await mkdir(dirname(path), { recursive: true });
346
+ await mkdir2(dirname(path), { recursive: true });
338
347
  const tmp = `${path}.${String(process.pid)}.tmp`;
339
348
  try {
340
349
  await writeFile(tmp, content, "utf8");
@@ -636,9 +645,9 @@ var Protocol = class {
636
645
  }
637
646
  };
638
647
  async function runChamber(cfg, onRecord) {
639
- await mkdir2(join2(cfg.runDir, "pids"), { recursive: true });
640
- await mkdir2(join2(cfg.runDir, "evidence"), { recursive: true });
641
- for (const role of CHAMBER_ROLES) await mkdir2(join2(cfg.runDir, role), { recursive: true });
648
+ await mkdir3(join2(cfg.runDir, "pids"), { recursive: true });
649
+ await mkdir3(join2(cfg.runDir, "evidence"), { recursive: true });
650
+ for (const role of CHAMBER_ROLES) await mkdir3(join2(cfg.runDir, role), { recursive: true });
642
651
  const roster = CHAMBER_ROLES.map((role) => {
643
652
  const d = resolveSeat(role, cfg.slots[role], cfg.pool, cfg.policy);
644
653
  return d.ok ? { role, slot: d.slot, modelId: d.modelId, policyCheck: "allow" } : { role, slot: cfg.slots[role], modelId: "", policyCheck: "deny", denyReason: d.deny };
@@ -852,7 +861,7 @@ function renderVoice(voice) {
852
861
  // src/lane/isolated.ts
853
862
  import { spawn } from "node:child_process";
854
863
  import { createWriteStream, existsSync } from "node:fs";
855
- import { copyFile, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
864
+ import { copyFile, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
856
865
  import { dirname as dirname2, join as join3 } from "node:path";
857
866
  var DEFAULT_PINNED_PATH = "/usr/bin:/bin";
858
867
  function composeSandboxArgs(opts) {
@@ -882,12 +891,12 @@ function rolePaths(cfg, role) {
882
891
  }
883
892
  async function prepareRoleHome(cfg, role) {
884
893
  const paths = rolePaths(cfg, role);
885
- await mkdir3(join3(paths.home, ".config", "opencode"), { recursive: true });
886
- await mkdir3(dirname2(paths.dbPath), { recursive: true });
887
- await mkdir3(join3(paths.home, ".cache", "opencode"), { recursive: true });
888
- await mkdir3(join3(paths.home, ".local", "state", "opencode"), { recursive: true });
889
- await mkdir3(paths.workspace, { recursive: true });
890
- await mkdir3(join3(cfg.runDir, "pids"), { recursive: true });
894
+ await mkdirPrivate(join3(paths.home, ".config", "opencode"));
895
+ await mkdirPrivate(dirname2(paths.dbPath));
896
+ await mkdirPrivate(join3(paths.home, ".cache", "opencode"));
897
+ await mkdirPrivate(join3(paths.home, ".local", "state", "opencode"));
898
+ await mkdirPrivate(paths.workspace);
899
+ await mkdirPrivate(join3(cfg.runDir, "pids"));
891
900
  try {
892
901
  if (cfg.configSource.length > 0) {
893
902
  await copyFile(cfg.configSource, join3(paths.home, ".config", "opencode", "opencode.jsonc"));
@@ -20381,17 +20390,51 @@ async function resolveRun(flags) {
20381
20390
  return { ok: false, error: `--run-id must be a bare sibyl run id, got "${flagRunId}"` };
20382
20391
  }
20383
20392
  const runId = flagRunId ?? `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
20393
+ if (targetRaw !== "-") {
20394
+ const abs = isAbsolute2(targetRaw) ? targetRaw : resolve2(process.cwd(), targetRaw);
20395
+ try {
20396
+ await access(abs);
20397
+ } catch (e) {
20398
+ return { ok: false, error: `--target unreadable: ${e instanceof Error ? e.message : String(e)}` };
20399
+ }
20400
+ }
20384
20401
  const runDir = join7(laneCfg.runRoot, `sibyl-run-${runId}`);
20385
- await mkdir6(runDir, { recursive: true });
20386
- let launchIndependence;
20402
+ await mkdirPrivate(runDir);
20403
+ let contextLeg = null;
20404
+ let contextProblem = null;
20387
20405
  try {
20388
20406
  const lc = JSON.parse(await readFile6(join7(runDir, "launch-context.json"), "utf8"));
20389
- launchIndependence = isIndependenceShape(lc["independence"]) ? lc["independence"] : {
20390
- status: "UNVERIFIABLE",
20391
- convenerChain: [],
20392
- evidence: "launch-context staged but its independence leg is malformed \u2014 not trusted; this review carries no independence anchor"
20407
+ if (isIndependenceShape(lc["independence"])) contextLeg = lc;
20408
+ else contextProblem = "launch-context staged but its independence leg is malformed";
20409
+ } catch (err) {
20410
+ contextProblem = err instanceof Error && err.name === "SyntaxError" ? "launch-context exists but is not valid JSON" : "no launch-context staged in the run dir";
20411
+ }
20412
+ if (flagRunId !== null && contextProblem !== null) {
20413
+ const refusal = {
20414
+ kind: "refused-launch",
20415
+ runId: flagRunId,
20416
+ reason: `${contextProblem} \u2014 a --run-id launch claims a tool-staged review that never happened; rerun through the sibyl_review tool, or drop --run-id for the supported bare headless launch`,
20417
+ at: now.toISOString()
20393
20418
  };
20394
- } catch {
20419
+ await writeFile7(join7(runDir, "launch-refusal.json"), `${JSON.stringify(refusal, null, 2)}
20420
+ `, "utf8");
20421
+ return { ok: false, error: refusal.reason };
20422
+ }
20423
+ let launchIndependence;
20424
+ if (contextLeg !== null) {
20425
+ launchIndependence = contextLeg["independence"];
20426
+ try {
20427
+ const face = JSON.parse(await readFile6(join7(runDir, "run-record.json"), "utf8"));
20428
+ const faceInd = face["independence"];
20429
+ if (isIndependenceShape(faceInd) && faceInd.status !== launchIndependence.status) {
20430
+ launchIndependence = {
20431
+ ...faceInd,
20432
+ evidence: `context claimed ${launchIndependence.status}, run record carries ${faceInd.status} \u2014 record wins. ${faceInd.evidence}`
20433
+ };
20434
+ }
20435
+ } catch {
20436
+ }
20437
+ } else {
20395
20438
  launchIndependence = {
20396
20439
  status: "UNVERIFIABLE",
20397
20440
  convenerChain: [],
package/dist/index.js CHANGED
@@ -18981,7 +18981,16 @@ function parseOptions(raw) {
18981
18981
 
18982
18982
  // src/state/index.ts
18983
18983
  import { randomBytes } from "node:crypto";
18984
- import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
18984
+ import { readFile, rename, unlink, writeFile } from "node:fs/promises";
18985
+
18986
+ // src/fsperm.ts
18987
+ import { chmod, mkdir } from "node:fs/promises";
18988
+ async function mkdirPrivate(dir) {
18989
+ await mkdir(dir, { recursive: true, mode: 448 });
18990
+ await chmod(dir, 448);
18991
+ }
18992
+
18993
+ // src/state/index.ts
18985
18994
  import { homedir as homedir2 } from "node:os";
18986
18995
  import { dirname, join as join2 } from "node:path";
18987
18996
 
@@ -19176,7 +19185,7 @@ var RunStore = class {
19176
19185
  * which briefly widens the crash window but never writes torn JSON. */
19177
19186
  async save(records, opts) {
19178
19187
  const dir = dirname(this.runsFile);
19179
- await mkdir(dir, { recursive: true });
19188
+ await mkdirPrivate(dir);
19180
19189
  const tmp = join2(dir, `runs.${process.pid}.${randomBytes(4).toString("hex")}.tmp`);
19181
19190
  const renameStep = opts?.rename ?? this.renameFile;
19182
19191
  try {
@@ -19219,7 +19228,7 @@ var RunStore = class {
19219
19228
  const now = /* @__PURE__ */ new Date();
19220
19229
  const runId = `sibyl-${compactIso(now)}-${randomBytes(2).toString("hex")}`;
19221
19230
  const spaceDir = join2(input2.spaceRootDir ?? this.spaceRoot, runId);
19222
- await mkdir(spaceDir, { recursive: true });
19231
+ await mkdirPrivate(spaceDir);
19223
19232
  const record3 = {
19224
19233
  runId,
19225
19234
  kind: input2.kind,
@@ -32870,7 +32879,7 @@ function ballotTag(vote) {
32870
32879
  import { spawn } from "node:child_process";
32871
32880
  import { randomBytes as randomBytes2 } from "node:crypto";
32872
32881
  import { existsSync as existsSync2 } from "node:fs";
32873
- import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
32882
+ import { writeFile as writeFile4 } from "node:fs/promises";
32874
32883
  import { dirname as dirname3, join as join6 } from "node:path";
32875
32884
  import { fileURLToPath } from "node:url";
32876
32885
  var REVIEW_TOOL_NAME = "sibyl_review";
@@ -32879,12 +32888,16 @@ function nodeBinary() {
32879
32888
  }
32880
32889
  function cliCommand(thisUrl, node2) {
32881
32890
  const moduleDir = dirname3(fileURLToPath(thisUrl));
32882
- const repoRoot = dirname3(dirname3(moduleDir));
32883
- const distCli = join6(repoRoot, "dist", "cli.js");
32884
- if (existsSync2(distCli)) return { bin: node2, args: [distCli] };
32885
- const srcCli = join6(repoRoot, "src", "cli.ts");
32886
- const hooks = join6(repoRoot, "tools", "register-ts.mjs");
32887
- return { bin: node2, args: ["--import", hooks, srcCli] };
32891
+ const roots = [dirname3(moduleDir), dirname3(dirname3(moduleDir))];
32892
+ for (const root of roots) {
32893
+ const distCli = join6(root, "dist", "cli.js");
32894
+ if (existsSync2(distCli)) return { bin: node2, args: [distCli] };
32895
+ const srcCli = join6(root, "src", "cli.ts");
32896
+ const hooks = join6(root, "tools", "register-ts.mjs");
32897
+ if (existsSync2(srcCli) && existsSync2(hooks)) return { bin: node2, args: ["--import", hooks, srcCli] };
32898
+ }
32899
+ const repoRoot = roots[1] ?? dirname3(dirname3(moduleDir));
32900
+ return { bin: node2, args: ["--import", join6(repoRoot, "tools", "register-ts.mjs"), join6(repoRoot, "src", "cli.ts")] };
32888
32901
  }
32889
32902
  async function reviewExecute(deps, args, context) {
32890
32903
  try {
@@ -32893,7 +32906,7 @@ async function reviewExecute(deps, args, context) {
32893
32906
  const now = /* @__PURE__ */ new Date();
32894
32907
  const runId = `sibyl-${compactIso(now)}-${randomBytes2(2).toString("hex")}`;
32895
32908
  const runDir = join6(opts.lane.runRoot, `sibyl-run-${runId}`);
32896
- await mkdir3(runDir, { recursive: true });
32909
+ await mkdirPrivate(runDir);
32897
32910
  const artifact = await readArtifact(args.target, context.directory);
32898
32911
  if (!artifact.ok) return `SIBYL review: ${artifact.error}`;
32899
32912
  let targetArg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sibyl-system",
3
- "version": "1.3.6",
3
+ "version": "1.4.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Sibyl-System: democratic-centralism review chamber (one conclusion, one voice) + fail-closed council voting + dynamic swarm plugin for OpenCode",
@@ -35,7 +35,7 @@
35
35
  "prepack": "npm run build"
36
36
  },
37
37
  "dependencies": {
38
- "@opencode-ai/plugin": "1.15.13",
38
+ "@opencode-ai/plugin": "1.19.4",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
@@ -45,5 +45,11 @@
45
45
  },
46
46
  "bin": {
47
47
  "sibyl-chamber": "./dist/cli.js"
48
+ },
49
+ "overrides": {
50
+ "toml": "5.0.1"
51
+ },
52
+ "engines": {
53
+ "node": ">=22"
48
54
  }
49
55
  }