sibyl-system 1.3.1 → 1.3.3
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 +3 -1
- package/dist/index.js +161 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ A standalone [opencode](https://opencode.ai) plugin:
|
|
|
12
12
|
the result is aggregated into a verdict.
|
|
13
13
|
- **`sibyl_review` / `sibyl-chamber`** (v1.1) — a general review chamber with
|
|
14
14
|
isolated role sessions and one conclusion spoken in one voice (see below).
|
|
15
|
+
- **`sibyl_audit`** — the outside lane as a product organ: completion-state ballots convene themselves from a fresh root session (see Tools).
|
|
15
16
|
- **`sibyl_status` + audit primitives** (`sibyl_anchor_check`, `sibyl_time_probe`,
|
|
16
17
|
`sibyl_attribute`) — read-only verification tools: run-store view, human-message
|
|
17
18
|
anchor re-verification, clock integrity, ref-move attribution.
|
|
@@ -98,6 +99,7 @@ stderr, registers nothing, and returns empty hooks.
|
|
|
98
99
|
| Tool | Arguments | Behavior |
|
|
99
100
|
|------|-----------|----------|
|
|
100
101
|
| `sibyl_consult` | `{ artifact, goal }` | `artifact` is a file path or inline multi-line text (≤ 256 KiB). The three councilors audit it in parallel; each reply is parsed into a verdict (one in-session JSON-only repair shot per voter). Returns the tally, merged reasons/must-fix, run id, and per-voter reply file paths. |
|
|
102
|
+
| `sibyl_audit` | `{ artifact, goal }` | Independent audit lane: the council is convened by a FRESH engine-provisioned root session (structurally non-kin to any drafting chain), so the ballot enters the effective path without the caller's identity touching it. Receipt: auditor session id (retained, DB-reconcilable), artifact sha256 bound at start and end (mid-flight drift => VOID), verdict face; anything but INDEPENDENT on the fresh convener => REFUSED, no receipt. |
|
|
101
103
|
| `sibyl_swarm` | `{ artifact, goal, judge? }` | ARCHITECT decomposes goal + artifact into a strict-JSON workflow schema; workers are minted deterministically and dispatched in dependency waves; drafts land in the run's space dir. Verdict: `APPROVE` / `REJECT` / `EXHAUSTED`, forced to `CANNOT_ANSWER` when a declared worker left no terminal row (W2) or zero drafts were produced. With `judge: true`, one extra judge pass may replace the derived verdict — an unrecognized or failed judge reply keeps the derived one. |
|
|
102
104
|
| `sibyl_status` | `{ runId? }` | Read-only. Lists all recorded runs (newest last) + the last chamber-ledger rows, or shows one run's full record and space dir. |
|
|
103
105
|
| `sibyl_review` | `{ target, goal, seed?, maxRounds? }` | v1.1 general democratic-centralism chamber: launches the isolated evidence→clash→judge pipeline detached and returns a receipt (explicitly NOT a verdict); the single voice lands in the run record at terminal state. CLI twin: `sibyl-chamber`. |
|
|
@@ -211,7 +213,7 @@ Dependencies point downward only:
|
|
|
211
213
|
|
|
212
214
|
```
|
|
213
215
|
index.ts plugin entry: parse options → share one RunStore + client
|
|
214
|
-
adapter → register the
|
|
216
|
+
adapter → register the eight sibyl_* tools
|
|
215
217
|
├── engine/ runPersona(): create + prompt one child session through a
|
|
216
218
|
│ structural client seam; per-stage timeouts; never throws —
|
|
217
219
|
│ every failure is a structured PersonaRunResult
|
package/dist/index.js
CHANGED
|
@@ -31971,7 +31971,53 @@ function contentMatches(input2, needle) {
|
|
|
31971
31971
|
}
|
|
31972
31972
|
return false;
|
|
31973
31973
|
}
|
|
31974
|
-
|
|
31974
|
+
var SHELL_TOOLS = ["bash"];
|
|
31975
|
+
var SHELL_KEYS = ["command"];
|
|
31976
|
+
var LINEAGE_CHUNK = 256;
|
|
31977
|
+
var LINEAGE_SAMPLES = 32;
|
|
31978
|
+
var LINEAGE_UNION_RATIO = 0.6;
|
|
31979
|
+
var LINEAGE_MIN_WINDOWS = 3;
|
|
31980
|
+
function sampleWindows(text) {
|
|
31981
|
+
const total = Math.floor(text.length / LINEAGE_CHUNK);
|
|
31982
|
+
if (total < LINEAGE_MIN_WINDOWS) return [];
|
|
31983
|
+
const step = Math.max(1, Math.floor(total / LINEAGE_SAMPLES));
|
|
31984
|
+
const out = [];
|
|
31985
|
+
for (let i = 0; i < total; i += step) out.push(text.slice(i * LINEAGE_CHUNK, (i + 1) * LINEAGE_CHUNK));
|
|
31986
|
+
return out.slice(0, LINEAGE_SAMPLES);
|
|
31987
|
+
}
|
|
31988
|
+
function beginLineage(artifact) {
|
|
31989
|
+
if (artifact.kind !== "path" || artifact.mtimeMs === void 0) return null;
|
|
31990
|
+
const chunks = sampleWindows(artifact.text);
|
|
31991
|
+
if (chunks.length < LINEAGE_MIN_WINDOWS) return null;
|
|
31992
|
+
return {
|
|
31993
|
+
chunks,
|
|
31994
|
+
need: Math.max(2, Math.ceil(chunks.length * LINEAGE_UNION_RATIO)),
|
|
31995
|
+
mtimeMs: artifact.mtimeMs,
|
|
31996
|
+
matched: /* @__PURE__ */ new Map(),
|
|
31997
|
+
copiedAfterMtime: 0,
|
|
31998
|
+
timeless: 0
|
|
31999
|
+
};
|
|
32000
|
+
}
|
|
32001
|
+
function messageTime(m) {
|
|
32002
|
+
const t = m.info?.time?.created;
|
|
32003
|
+
return typeof t === "number" ? t : void 0;
|
|
32004
|
+
}
|
|
32005
|
+
function partCandidates(p) {
|
|
32006
|
+
const shell = p.tool !== void 0 && SHELL_TOOLS.includes(p.tool);
|
|
32007
|
+
if (!shell && !isDraftingTool(p.tool)) return [];
|
|
32008
|
+
const keys = shell ? SHELL_KEYS : CONTENT_KEYS;
|
|
32009
|
+
const out = [];
|
|
32010
|
+
for (const key of keys) {
|
|
32011
|
+
const v = p.input?.[key];
|
|
32012
|
+
if (typeof v === "string" && v.length >= LINEAGE_CHUNK) out.push(v);
|
|
32013
|
+
}
|
|
32014
|
+
return out;
|
|
32015
|
+
}
|
|
32016
|
+
function lineageVerdict(lg, artifact) {
|
|
32017
|
+
const sources = [.../* @__PURE__ */ new Set([...lg.matched.values()])].slice(0, 3).join("; ");
|
|
32018
|
+
return `content-lineage evidence: ${String(lg.matched.size)} of ${String(lg.chunks.length)} verbatim ${String(LINEAGE_CHUNK)}-byte windows of ${artifact.source} appear in convener-chain tool inputs that predate its mtime (${sources}) \u2014 the chain authored this artifact's bytes before/independently of this path`;
|
|
32019
|
+
}
|
|
32020
|
+
function scanMessages(rows, convenerIndex, sid, artifact, lg) {
|
|
31975
32021
|
for (const [mi, m] of rows.entries()) {
|
|
31976
32022
|
for (const p of m.parts ?? []) {
|
|
31977
32023
|
if (p.type === "tool" && isDraftingTool(p.tool)) {
|
|
@@ -31985,6 +32031,26 @@ function scanMessages(rows, convenerIndex, sid, artifact) {
|
|
|
31985
32031
|
if (artifact.kind === "inline" && m.info?.role === "assistant" && p.type === "text" && typeof p.text === "string" && artifact.text.trim().length >= MIN_INLINE_MATCH && p.text.includes(artifact.text.trim())) {
|
|
31986
32032
|
return `drafting evidence: assistant text in convener-chain session ${sid} (chain position ${String(convenerIndex)}, message ${String(mi)}) contains the inline artifact`;
|
|
31987
32033
|
}
|
|
32034
|
+
if (p.type === "tool" && lg !== null) {
|
|
32035
|
+
const t = messageTime(m);
|
|
32036
|
+
if (t === void 0) {
|
|
32037
|
+
lg.timeless += 1;
|
|
32038
|
+
continue;
|
|
32039
|
+
}
|
|
32040
|
+
if (t > lg.mtimeMs) {
|
|
32041
|
+
lg.copiedAfterMtime += 1;
|
|
32042
|
+
continue;
|
|
32043
|
+
}
|
|
32044
|
+
const label = `${p.tool ?? "tool"}@${sid}:${String(mi)}`;
|
|
32045
|
+
for (const cand of partCandidates(p)) {
|
|
32046
|
+
if (artifact.kind === "path" && cand.includes(artifact.text.trim())) {
|
|
32047
|
+
return `${lineageVerdict({ ...lg, matched: /* @__PURE__ */ new Map([[0, label]]) }, artifact)}; full-text containment in ${label}`;
|
|
32048
|
+
}
|
|
32049
|
+
for (const [wi, chunk] of lg.chunks.entries()) {
|
|
32050
|
+
if (!lg.matched.has(wi) && cand.includes(chunk)) lg.matched.set(wi, label);
|
|
32051
|
+
}
|
|
32052
|
+
}
|
|
32053
|
+
}
|
|
31988
32054
|
}
|
|
31989
32055
|
}
|
|
31990
32056
|
return null;
|
|
@@ -32025,6 +32091,7 @@ async function assessIndependence(client, directory, convenerSessionID, artifact
|
|
|
32025
32091
|
seen.add(parent);
|
|
32026
32092
|
current = parent;
|
|
32027
32093
|
}
|
|
32094
|
+
const lg = beginLineage(artifact);
|
|
32028
32095
|
let scanFailed = "";
|
|
32029
32096
|
for (const [i, sid] of chain.entries()) {
|
|
32030
32097
|
const res = await client.session.messages({ path: { id: sid }, query: { directory } });
|
|
@@ -32032,22 +32099,33 @@ async function assessIndependence(client, directory, convenerSessionID, artifact
|
|
|
32032
32099
|
scanFailed = scanFailed.length > 0 ? scanFailed : `messages(${sid}) refused`;
|
|
32033
32100
|
continue;
|
|
32034
32101
|
}
|
|
32035
|
-
const hit = scanMessages(res.data, i, sid, artifact);
|
|
32102
|
+
const hit = scanMessages(res.data, i, sid, artifact, lg);
|
|
32036
32103
|
if (hit !== null) {
|
|
32037
32104
|
return { status: "NOT-INDEPENDENT", convenerChain: chain, evidence: hit };
|
|
32038
32105
|
}
|
|
32039
32106
|
}
|
|
32107
|
+
if (lg !== null && lg.matched.size >= lg.need) {
|
|
32108
|
+
return { status: "NOT-INDEPENDENT", convenerChain: chain, evidence: lineageVerdict(lg, artifact) };
|
|
32109
|
+
}
|
|
32040
32110
|
if (scanFailed.length > 0 || walkIncomplete) {
|
|
32041
32111
|
const why = [walkIncomplete ? walkReason : "", scanFailed].filter((s) => s.length > 0).join("; ");
|
|
32042
32112
|
return { status: "UNVERIFIABLE", convenerChain: chain, evidence: `partial read: ${why} \u2014 independence was NOT established` };
|
|
32043
32113
|
}
|
|
32044
32114
|
const note = walkReason.length > 0 ? `; walk note: ${walkReason}` : "";
|
|
32115
|
+
const lineageNote = lineageFaceNote(lg);
|
|
32045
32116
|
return {
|
|
32046
32117
|
status: "INDEPENDENT",
|
|
32047
32118
|
convenerChain: chain,
|
|
32048
|
-
evidence: `scanned ${String(chain.length)} session(s) of the convener chain to root; no drafting evidence within read scope${note}`
|
|
32119
|
+
evidence: `scanned ${String(chain.length)} session(s) of the convener chain to root; no drafting evidence within read scope${lineageNote}${note}`
|
|
32049
32120
|
};
|
|
32050
32121
|
}
|
|
32122
|
+
function lineageFaceNote(lg) {
|
|
32123
|
+
if (lg === null) return "";
|
|
32124
|
+
const parts = [`${String(lg.matched.size)}/${String(lg.chunks.length)} lineage windows matched (need ${String(lg.need)})`];
|
|
32125
|
+
if (lg.copiedAfterMtime > 0) parts.push(`${String(lg.copiedAfterMtime)} part(s) excluded as post-mtime copies`);
|
|
32126
|
+
if (lg.timeless > 0) parts.push(`${String(lg.timeless)} part(s) lacked timestamps`);
|
|
32127
|
+
return `; content-lineage: ${parts.join(", ")}`;
|
|
32128
|
+
}
|
|
32051
32129
|
function independenceLabel(v) {
|
|
32052
32130
|
return v.status === "INDEPENDENT" ? "independence=INDEPENDENT" : `independence=${v.status} \u2014 ${v.evidence}`;
|
|
32053
32131
|
}
|
|
@@ -32526,7 +32604,7 @@ async function readArtifact(raw, cwd) {
|
|
|
32526
32604
|
} catch (err) {
|
|
32527
32605
|
return { ok: false, error: `artifact "${path}": ${errMessage(err)}` };
|
|
32528
32606
|
}
|
|
32529
|
-
return { ok: true, kind: "path", source: path, text };
|
|
32607
|
+
return { ok: true, kind: "path", source: path, text, mtimeMs: info.mtimeMs };
|
|
32530
32608
|
}
|
|
32531
32609
|
function errMessage(err) {
|
|
32532
32610
|
if (err instanceof Error) return err.message;
|
|
@@ -32953,6 +33031,79 @@ function buildAnchorTool(deps) {
|
|
|
32953
33031
|
});
|
|
32954
33032
|
}
|
|
32955
33033
|
|
|
33034
|
+
// src/tools/audit.ts
|
|
33035
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
33036
|
+
var AUDIT_TOOL_NAME = "sibyl_audit";
|
|
33037
|
+
var RUN_ID_RE = /run (sibyl-[0-9TZ-]+-[0-9a-f]+)/;
|
|
33038
|
+
function sha2562(text) {
|
|
33039
|
+
return createHash3("sha256").update(text, "utf8").digest("hex");
|
|
33040
|
+
}
|
|
33041
|
+
async function artifactHash(raw, dir) {
|
|
33042
|
+
const art = await readArtifact(raw, dir);
|
|
33043
|
+
return art.ok ? sha2562(art.text) : null;
|
|
33044
|
+
}
|
|
33045
|
+
function buildAuditTool(deps) {
|
|
33046
|
+
return tool({
|
|
33047
|
+
description: "SIBYL independent audit lane: convenes the three-councilor consult from a FRESH engine-provisioned root session (structurally non-kin to any drafting chain), so the ballot enters the effective path without the caller's identity touching it. Emits an audit receipt \u2014 auditor session id (retained, DB-reconcilable), artifact sha256 bound at start and end, verdict face. REFUSES (no receipt) if the fresh convener resolves to anything but INDEPENDENT, or the artifact drifts mid-ballot (VOID).",
|
|
33048
|
+
args: {
|
|
33049
|
+
artifact: tool.schema.string().min(1).describe("Path to the artifact, or its inline content (multi-line)."),
|
|
33050
|
+
goal: tool.schema.string().min(1).describe("The question the audit must answer.")
|
|
33051
|
+
},
|
|
33052
|
+
execute: async (args, context) => {
|
|
33053
|
+
try {
|
|
33054
|
+
return await auditExecute(deps, args, context);
|
|
33055
|
+
} catch (err) {
|
|
33056
|
+
return internalError(AUDIT_TOOL_NAME, err);
|
|
33057
|
+
}
|
|
33058
|
+
}
|
|
33059
|
+
});
|
|
33060
|
+
}
|
|
33061
|
+
async function auditExecute(deps, args, ctx) {
|
|
33062
|
+
const start = await readArtifact(args.artifact, ctx.directory);
|
|
33063
|
+
if (!start.ok) {
|
|
33064
|
+
return `SIBYL AUDIT: refused \u2014 artifact unreadable: ${start.error}`;
|
|
33065
|
+
}
|
|
33066
|
+
const shaStart = sha2562(start.text);
|
|
33067
|
+
const created = await deps.client.session.create({
|
|
33068
|
+
body: { title: `sibyl:audit ${(/* @__PURE__ */ new Date()).toISOString()}` },
|
|
33069
|
+
query: { directory: ctx.directory }
|
|
33070
|
+
});
|
|
33071
|
+
const auditor = created.data?.id ?? "";
|
|
33072
|
+
if (auditor.length === 0) {
|
|
33073
|
+
const why = created.error === void 0 ? "no id in response" : JSON.stringify(created.error).slice(0, 140);
|
|
33074
|
+
return `SIBYL AUDIT: refused \u2014 engine did not provision the fresh root convener (${why}); an audit without a resolvable auditor session is not an audit`;
|
|
33075
|
+
}
|
|
33076
|
+
const caller = ctx.sessionID.length > 0 ? ctx.sessionID : "headless";
|
|
33077
|
+
const ballot = await consultExecute(deps, args, { ...ctx, sessionID: auditor });
|
|
33078
|
+
const runId = RUN_ID_RE.exec(ballot)?.[1] ?? "";
|
|
33079
|
+
const runs = await deps.store.load();
|
|
33080
|
+
const rec = runs.find((r) => r.runId === runId);
|
|
33081
|
+
const ind = rec?.independence;
|
|
33082
|
+
const independence = ind?.status ?? "MISSING-RECORD";
|
|
33083
|
+
if (independence !== "INDEPENDENT") {
|
|
33084
|
+
const ev = ind?.evidence ?? `run ${runId.length > 0 ? runId : "(unresolvable)"} not found in the store`;
|
|
33085
|
+
return `SIBYL AUDIT: REFUSED \u2014 the fresh convener ${auditor} resolved to independence=${independence}: ${ev}
|
|
33086
|
+
the ballot ${runId.length > 0 ? runId : "(no run id)"} is archived as DATA and carries no audit receipt; requested-by=` + caller + " (DATA only)";
|
|
33087
|
+
}
|
|
33088
|
+
const shaEnd = await artifactHash(args.artifact, ctx.directory);
|
|
33089
|
+
if (shaEnd !== shaStart) {
|
|
33090
|
+
return `SIBYL AUDIT: VOID \u2014 artifact bytes moved while the ballot was in flight (start sha256 ${shaStart.slice(0, 16)}\u2026, end ${shaEnd === null ? "unreadable" : `${shaEnd.slice(0, 16)}\u2026`}); run ${runId} archived as DATA; re-audit the current bytes`;
|
|
33091
|
+
}
|
|
33092
|
+
const v = rec?.verdict;
|
|
33093
|
+
const counts = v === void 0 ? "no verdict" : `${String(v.approvals)}A/${String(v.rejects)}R/${String(v.errors)}E/${String(v.missing)}M`;
|
|
33094
|
+
const rules = rec?.instrument?.rulesHash.slice(0, 12) ?? "no-instrument-face";
|
|
33095
|
+
return [
|
|
33096
|
+
`SIBYL AUDIT RECEIPT run=${runId}`,
|
|
33097
|
+
` verdict=${v?.verdict ?? "unknown"} (${counts}) rules=${rules}`,
|
|
33098
|
+
` auditor-convener=${auditor} (fresh engine-provisioned root; session row RETAINED for DB reconciliation)`,
|
|
33099
|
+
` requested-by=${caller} \u2014 recorded as ledger data only; it neither convenes nor votes`,
|
|
33100
|
+
` artifact-sha256=${shaStart} (re-hashed after the ballot, identical \u2014 the receipt binds to these bytes)`,
|
|
33101
|
+
"",
|
|
33102
|
+
"--- ballot face ---",
|
|
33103
|
+
ballot
|
|
33104
|
+
].join("\n");
|
|
33105
|
+
}
|
|
33106
|
+
|
|
32956
33107
|
// src/tools/probes.ts
|
|
32957
33108
|
var DEFAULT_TIME_POOL = [
|
|
32958
33109
|
{ url: "https://www.cloudflare.com", ownerDept: "cloudflare" },
|
|
@@ -33858,7 +34009,11 @@ function toEngineClient(sdk) {
|
|
|
33858
34009
|
...m.info.providerID !== void 0 && { providerID: m.info.providerID },
|
|
33859
34010
|
...m.info.modelID !== void 0 && { modelID: m.info.modelID },
|
|
33860
34011
|
...m.info.time !== void 0 && {
|
|
33861
|
-
time: {
|
|
34012
|
+
time: {
|
|
34013
|
+
...m.info.time.completed !== void 0 && { completed: m.info.time.completed },
|
|
34014
|
+
// W1-v2 lineage time gate needs creation stamps too
|
|
34015
|
+
..."created" in m.info.time && m.info.time.created !== void 0 && { created: m.info.time.created }
|
|
34016
|
+
}
|
|
33862
34017
|
},
|
|
33863
34018
|
...m.info.error !== void 0 && { error: m.info.error }
|
|
33864
34019
|
}
|
|
@@ -33909,6 +34064,7 @@ async function SibylPlugin(input2, options) {
|
|
|
33909
34064
|
return {
|
|
33910
34065
|
tool: {
|
|
33911
34066
|
sibyl_consult: buildConsultTool(deps),
|
|
34067
|
+
sibyl_audit: buildAuditTool(deps),
|
|
33912
34068
|
sibyl_swarm: buildSwarmTool(deps),
|
|
33913
34069
|
sibyl_status: buildStatusTool(deps),
|
|
33914
34070
|
sibyl_review: buildReviewTool(deps),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sibyl-system",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
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",
|