signafaide 0.2.1 → 0.3.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 +32 -1
- package/dist/{chunk-MBXHRQ2J.js → chunk-6UDL5LZL.js} +96 -6
- package/dist/{chunk-S2RSID5F.js → chunk-DRRRZNIH.js} +1 -1
- package/dist/{chunk-4C2JEYAU.js → chunk-P3GJDXSQ.js} +291 -32
- package/dist/cli.js +403 -6
- package/dist/{core-BMfko_ex.d.ts → core-BTJGPYVW.d.cts} +64 -2
- package/dist/{core-BMfko_ex.d.cts → core-BTJGPYVW.d.ts} +64 -2
- package/dist/core.cjs +104 -6
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +20 -2
- package/dist/{http-YWG535HP.js → http-BZI6VZM3.js} +1 -1
- package/dist/index.cjs +391 -30
- package/dist/index.d.cts +98 -5
- package/dist/index.d.ts +98 -5
- package/dist/index.js +29 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var VERSION;
|
|
|
35
35
|
var init_version = __esm({
|
|
36
36
|
"src/version.ts"() {
|
|
37
37
|
"use strict";
|
|
38
|
-
VERSION = "0.
|
|
38
|
+
VERSION = "0.3.0";
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -248,6 +248,7 @@ __export(src_exports, {
|
|
|
248
248
|
HEADER: () => HEADER,
|
|
249
249
|
JsonNumber: () => JsonNumber,
|
|
250
250
|
KeysRequired: () => KeysRequired,
|
|
251
|
+
MAX_DECLARED_NAME: () => MAX_DECLARED_NAME,
|
|
251
252
|
MAX_NODES: () => MAX_NODES,
|
|
252
253
|
MAX_PROOF_BYTES: () => MAX_PROOF_BYTES,
|
|
253
254
|
MAX_ROUND: () => MAX_ROUND,
|
|
@@ -260,11 +261,17 @@ __export(src_exports, {
|
|
|
260
261
|
SigningKey: () => SigningKey,
|
|
261
262
|
State: () => State,
|
|
262
263
|
StateError: () => StateError,
|
|
264
|
+
TOOLS: () => TOOLS,
|
|
265
|
+
TOOL_PREFIX: () => TOOL_PREFIX,
|
|
266
|
+
TOOL_SOURCE_MAX_AGE_MS: () => TOOL_SOURCE_MAX_AGE_MS,
|
|
267
|
+
TOOL_SOURCE_REFRESH_MS: () => TOOL_SOURCE_REFRESH_MS,
|
|
268
|
+
TOOL_UNRECORDED_PREFIX: () => TOOL_UNRECORDED_PREFIX,
|
|
263
269
|
VERSION: () => VERSION,
|
|
264
270
|
VerificationFailed: () => VerificationFailed,
|
|
265
271
|
agentName: () => agentName,
|
|
266
272
|
assertPortableNumbers: () => assertPortableNumbers,
|
|
267
273
|
auditPath: () => auditPath,
|
|
274
|
+
autoDeclareTools: () => autoDeclareTools,
|
|
268
275
|
b64url: () => b64url,
|
|
269
276
|
b64urlDecode: () => b64urlDecode,
|
|
270
277
|
blockAttestations: () => blockAttestations,
|
|
@@ -282,6 +289,10 @@ __export(src_exports, {
|
|
|
282
289
|
contentFromText: () => contentFromText,
|
|
283
290
|
contentHash: () => contentHash,
|
|
284
291
|
countsText: () => countsText,
|
|
292
|
+
coverage: () => coverage,
|
|
293
|
+
coverageLines: () => coverageLines,
|
|
294
|
+
declareTools: () => declareTools,
|
|
295
|
+
declaredName: () => declaredName,
|
|
285
296
|
detectAgent: () => detectAgent,
|
|
286
297
|
draftErrors: () => draftErrors,
|
|
287
298
|
envelopeErrors: () => envelopeErrors,
|
|
@@ -337,6 +348,8 @@ __export(src_exports, {
|
|
|
337
348
|
testMode: () => testMode,
|
|
338
349
|
timestampHeights: () => timestampHeights,
|
|
339
350
|
toJsonable: () => toJsonable,
|
|
351
|
+
toolNote: () => toolNote,
|
|
352
|
+
toolNoteName: () => toolNoteName,
|
|
340
353
|
validProjectId: () => validProjectId,
|
|
341
354
|
validTime: () => validTime,
|
|
342
355
|
verify: () => verify3,
|
|
@@ -1468,6 +1481,8 @@ function fsError(e, home) {
|
|
|
1468
1481
|
}
|
|
1469
1482
|
return e;
|
|
1470
1483
|
}
|
|
1484
|
+
var TOOL_SOURCE_REFRESH_MS = 36e5;
|
|
1485
|
+
var TOOL_SOURCE_MAX_AGE_MS = 864e5;
|
|
1471
1486
|
var LOCK_STALE_MS = 1e4;
|
|
1472
1487
|
var LOCK_WAIT_MS = 15e3;
|
|
1473
1488
|
var PENDING = "_pending";
|
|
@@ -2068,18 +2083,97 @@ var State = class _State {
|
|
|
2068
2083
|
* and put `wire(record)` in the outbox. Returns null when `stillValid()` is false once
|
|
2069
2084
|
* the lock is held (the chain was just moved under its project; resolve it again). */
|
|
2070
2085
|
append(chain, build, wire, stillValid = () => true) {
|
|
2086
|
+
return this.appendRun(chain, [build], wire, stillValid)?.[0] ?? null;
|
|
2087
|
+
}
|
|
2088
|
+
/** `append` for several records that must follow each other with nothing between them (a tool
|
|
2089
|
+
* declaration, §2.6): all of them are built and written under one hold of the chain lock, in
|
|
2090
|
+
* one write, so no other writer's record lands inside the run. */
|
|
2091
|
+
appendRun(chain, builds, wire, stillValid = () => true) {
|
|
2071
2092
|
this.ensure();
|
|
2072
2093
|
return this.lock(chain, () => {
|
|
2073
2094
|
if (!stillValid()) return null;
|
|
2074
|
-
|
|
2075
|
-
const
|
|
2095
|
+
let [seq, prev, prevTime] = this.tail(chain);
|
|
2096
|
+
const recs = [];
|
|
2097
|
+
for (const build of builds) {
|
|
2098
|
+
const rec = build(seq + 1, prev, prevTime);
|
|
2099
|
+
recs.push(rec);
|
|
2100
|
+
[seq, prev, prevTime] = [rec.envelope.seq, recordHash(rec), rec.envelope.time];
|
|
2101
|
+
}
|
|
2076
2102
|
try {
|
|
2077
|
-
fs.appendFileSync(this.chainPath(chain), canonicalize(
|
|
2103
|
+
fs.appendFileSync(this.chainPath(chain), recs.map((r) => canonicalize(r) + "\n").join(""));
|
|
2078
2104
|
} catch (e) {
|
|
2079
2105
|
throw fsError(e, this.home);
|
|
2080
2106
|
}
|
|
2081
|
-
if (!rec.draft) this.writeOutbox(chain, wire(rec));
|
|
2082
|
-
return
|
|
2107
|
+
for (const rec of recs) if (!rec.draft) this.writeOutbox(chain, wire(rec));
|
|
2108
|
+
return recs;
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
// --- tool declarations --------------------------------------------------------
|
|
2112
|
+
/** `tools/<agent>.json` (§8): the tools each source (the SDK in an application, each gateway)
|
|
2113
|
+
* last declared for the agent. */
|
|
2114
|
+
toolsPath(agent) {
|
|
2115
|
+
return path.join(this.home, "tools", `${agent}.json`);
|
|
2116
|
+
}
|
|
2117
|
+
/** The tools every source last declared for `agent`: source -> {tool name: recorded}. */
|
|
2118
|
+
toolSources(agent) {
|
|
2119
|
+
return Object.fromEntries(Object.entries(this.toolEntries(agent)).map(([src, e]) => [src, e.tools]));
|
|
2120
|
+
}
|
|
2121
|
+
/** The entries of `tools/<agent>.json`: source -> when it last declared or refreshed, and its tools. */
|
|
2122
|
+
toolEntries(agent) {
|
|
2123
|
+
const doc = readJson(this.toolsPath(agent));
|
|
2124
|
+
const out = {};
|
|
2125
|
+
const sources = doc?.sources;
|
|
2126
|
+
if (!sources || typeof sources !== "object" || Array.isArray(sources)) return out;
|
|
2127
|
+
for (const [src, entry] of Object.entries(sources)) {
|
|
2128
|
+
const tools = entry?.tools;
|
|
2129
|
+
if (!tools || typeof tools !== "object" || Array.isArray(tools)) continue;
|
|
2130
|
+
out[src] = { at: typeof entry.at === "string" ? entry.at : "", tools: Object.fromEntries(Object.entries(tools).filter(([, v]) => typeof v === "boolean")) };
|
|
2131
|
+
}
|
|
2132
|
+
return out;
|
|
2133
|
+
}
|
|
2134
|
+
/** A source that ends cleanly takes its own entry out of `tools/<agent>.json`, so the next
|
|
2135
|
+
* declaration by any source no longer counts its tools (§8). Nothing is written to the chain. */
|
|
2136
|
+
releaseToolSource(agent, source) {
|
|
2137
|
+
const file = this.toolsPath(agent);
|
|
2138
|
+
if (!fs.existsSync(file)) return;
|
|
2139
|
+
this.lockAt(path.join(this.home, "tools", `${agent}.lock`), () => {
|
|
2140
|
+
const entries = this.toolEntries(agent);
|
|
2141
|
+
if (!(source in entries)) return;
|
|
2142
|
+
delete entries[source];
|
|
2143
|
+
writeJson(file, { sources: entries });
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
/** Set the tools `source` declares for `agent` and call `write` with the agent's whole tool
|
|
2147
|
+
* list: the union over every source, a tool recorded when any source records it (§8). Held
|
|
2148
|
+
* under `tools/<agent>.lock`, so two sources never write declarations that miss each other's
|
|
2149
|
+
* tools. The source's own entry gets the time `nowMs`; another source's entry not refreshed for
|
|
2150
|
+
* `TOOL_SOURCE_MAX_AGE_MS` is dropped (that source has stopped), and `expired` tells `write`
|
|
2151
|
+
* whether dropping changed the agent's tool list. The file is updated only when `write` returns. */
|
|
2152
|
+
declareTools(agent, source, tools, write, nowMs = Date.now()) {
|
|
2153
|
+
this.ensure();
|
|
2154
|
+
const file = this.toolsPath(agent);
|
|
2155
|
+
return this.lockAt(path.join(this.home, "tools", `${agent}.lock`), () => {
|
|
2156
|
+
const union = (entries) => {
|
|
2157
|
+
const all2 = {};
|
|
2158
|
+
for (const src of Object.keys(entries).sort()) for (const [name, recorded] of Object.entries(entries[src].tools)) all2[name] = all2[name] === true || recorded;
|
|
2159
|
+
return all2;
|
|
2160
|
+
};
|
|
2161
|
+
const sorted = (all2) => JSON.stringify(Object.keys(all2).sort().map((n) => [n, all2[n]]));
|
|
2162
|
+
const held = { ...this.toolEntries(agent), [source]: { at: new Date(nowMs).toISOString(), tools } };
|
|
2163
|
+
const live = Object.fromEntries(
|
|
2164
|
+
Object.entries(held).filter(([src, e]) => {
|
|
2165
|
+
const at = Date.parse(e.at);
|
|
2166
|
+
return src === source || Number.isFinite(at) && nowMs - at <= TOOL_SOURCE_MAX_AGE_MS;
|
|
2167
|
+
})
|
|
2168
|
+
);
|
|
2169
|
+
const all = union(live);
|
|
2170
|
+
const out = write(all, sorted(all) !== sorted(union(held)));
|
|
2171
|
+
try {
|
|
2172
|
+
writeJson(file, { sources: live });
|
|
2173
|
+
} catch (e) {
|
|
2174
|
+
throw fsError(e, this.home);
|
|
2175
|
+
}
|
|
2176
|
+
return out;
|
|
2083
2177
|
});
|
|
2084
2178
|
}
|
|
2085
2179
|
writeOutbox(chain, rec) {
|
|
@@ -2384,6 +2478,71 @@ function wireOf(rec, store) {
|
|
|
2384
2478
|
|
|
2385
2479
|
// src/runtime.ts
|
|
2386
2480
|
init_telemetry();
|
|
2481
|
+
|
|
2482
|
+
// src/coverage.ts
|
|
2483
|
+
var TOOLS = "signafaide.tools";
|
|
2484
|
+
var TOOL_PREFIX = "signafaide.tool/";
|
|
2485
|
+
var TOOL_UNRECORDED_PREFIX = "signafaide.tool_unrecorded/";
|
|
2486
|
+
var RESERVED_PREFIX = "signafaide.";
|
|
2487
|
+
var MAX_DECLARED_NAME = 101;
|
|
2488
|
+
var KEPT = MAX_DECLARED_NAME - 9;
|
|
2489
|
+
function declaredName(tool) {
|
|
2490
|
+
const chars = [...tool];
|
|
2491
|
+
if (chars.length <= MAX_DECLARED_NAME) return tool;
|
|
2492
|
+
return `${chars.slice(0, KEPT).join("")}~${sha256Hex(new TextEncoder().encode(tool)).slice(0, 8)}`;
|
|
2493
|
+
}
|
|
2494
|
+
function toolNoteName(tool, recorded) {
|
|
2495
|
+
return (recorded ? TOOL_PREFIX : TOOL_UNRECORDED_PREFIX) + declaredName(tool);
|
|
2496
|
+
}
|
|
2497
|
+
function toolNote(kind, name) {
|
|
2498
|
+
if (kind !== "note" || typeof name !== "string") return null;
|
|
2499
|
+
if (name === TOOLS) return "header";
|
|
2500
|
+
const recorded = name.startsWith(TOOL_PREFIX);
|
|
2501
|
+
if (!recorded && !name.startsWith(TOOL_UNRECORDED_PREFIX)) return null;
|
|
2502
|
+
const rest = name.slice((recorded ? TOOL_PREFIX : TOOL_UNRECORDED_PREFIX).length);
|
|
2503
|
+
return rest ? { name: rest, recorded } : null;
|
|
2504
|
+
}
|
|
2505
|
+
function byCodePoint(a, b) {
|
|
2506
|
+
const [x, y] = [[...a], [...b]];
|
|
2507
|
+
for (let i = 0; i < x.length && i < y.length; i++) {
|
|
2508
|
+
const d = x[i].codePointAt(0) - y[i].codePointAt(0);
|
|
2509
|
+
if (d) return d;
|
|
2510
|
+
}
|
|
2511
|
+
return x.length - y.length;
|
|
2512
|
+
}
|
|
2513
|
+
function coverage(records) {
|
|
2514
|
+
let at = null;
|
|
2515
|
+
let tools = /* @__PURE__ */ new Map();
|
|
2516
|
+
const called = /* @__PURE__ */ new Set();
|
|
2517
|
+
for (const { seq, kind, name } of records) {
|
|
2518
|
+
const t = toolNote(kind, name);
|
|
2519
|
+
if (t === "header") {
|
|
2520
|
+
at = seq;
|
|
2521
|
+
tools = /* @__PURE__ */ new Map();
|
|
2522
|
+
} else if (t) {
|
|
2523
|
+
if (at !== null) tools.set(t.name, t.recorded);
|
|
2524
|
+
} else if (kind === "decision" && typeof name === "string" && !name.startsWith(RESERVED_PREFIX)) called.add(declaredName(name));
|
|
2525
|
+
}
|
|
2526
|
+
if (at === null) return { declared: [], undeclared_recorded: [], declared_at_seq: null };
|
|
2527
|
+
return {
|
|
2528
|
+
declared: [...tools.keys()].sort(byCodePoint).map((name) => ({ name, recorded: tools.get(name) })),
|
|
2529
|
+
undeclared_recorded: [...called].filter((n) => !tools.has(n)).sort(byCodePoint),
|
|
2530
|
+
declared_at_seq: at
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
var countIs = (n, one) => `${plural(n, one)} ${n === 1 ? "is" : "are"}`;
|
|
2534
|
+
function coverageLines(agent, c) {
|
|
2535
|
+
if (c.declared_at_seq === null) return [`${agent}: no tool list was declared, so the pack cannot show which of the agent's tools are recorded`];
|
|
2536
|
+
const unrecorded = c.declared.filter((t) => !t.recorded).map((t) => t.name);
|
|
2537
|
+
const out = [];
|
|
2538
|
+
if (!c.declared.length) out.push(`${agent}: the declared tool list is empty`);
|
|
2539
|
+
else if (!unrecorded.length) out.push(`${agent}: ${c.declared.length === 1 ? "the" : "all"} ${countIs(c.declared.length, "declared tool")} recorded`);
|
|
2540
|
+
else out.push(`${agent}: ${countIs(unrecorded.length, "declared tool")} not recorded: ${unrecorded.join(", ")}`);
|
|
2541
|
+
if (c.undeclared_recorded.length) out.push(`${agent}: ${countIs(c.undeclared_recorded.length, "recorded tool")} not in the declared tool list: ${c.undeclared_recorded.join(", ")}`);
|
|
2542
|
+
return out;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
// src/runtime.ts
|
|
2387
2546
|
var BATCH_MAX = 1e3;
|
|
2388
2547
|
var KEY_MAX_AGE_MS = 7 * 864e5 - 36e5;
|
|
2389
2548
|
var LOCAL_CODES = /* @__PURE__ */ new Set(["too_old", "chain_exists"]);
|
|
@@ -2418,6 +2577,9 @@ var Recorder = class {
|
|
|
2418
2577
|
/** Agents this process wrote records for since its last stop note. */
|
|
2419
2578
|
wrote = /* @__PURE__ */ new Set();
|
|
2420
2579
|
toldStrict = false;
|
|
2580
|
+
/** `<agent>\n<source>` -> the tool set this recorder last declared for it (§2.6), and the header's hash. */
|
|
2581
|
+
declared = /* @__PURE__ */ new Map();
|
|
2582
|
+
toolTimer;
|
|
2421
2583
|
/** @internal */
|
|
2422
2584
|
_url;
|
|
2423
2585
|
/** @internal */
|
|
@@ -2639,32 +2801,39 @@ ${scope}`;
|
|
|
2639
2801
|
/** Sign and chain one record locally (synchronous) and queue it for shipping.
|
|
2640
2802
|
* The chain is this API key's `<project>/<env>/<agent>` (§8). */
|
|
2641
2803
|
write(kind, name, content, opts = {}) {
|
|
2804
|
+
return this.writeRun([{ kind, name, content, ref: opts.ref }], opts)[0];
|
|
2805
|
+
}
|
|
2806
|
+
/** `write` for records that must follow each other with nothing between them (a tool
|
|
2807
|
+
* declaration, §2.6): they are signed and appended under one hold of the chain lock. */
|
|
2808
|
+
writeRun(items, opts = {}) {
|
|
2642
2809
|
const why = noWrites.getStore();
|
|
2643
|
-
if (why) throw new SignafaideError(`${why} must not write records: candidate functions must be side-effect free (it tried to record "${name}")`, { code: "no_writes" });
|
|
2810
|
+
if (why) throw new SignafaideError(`${why} must not write records: candidate functions must be side-effect free (it tried to record "${items[0]?.name}")`, { code: "no_writes" });
|
|
2644
2811
|
const agent = opts.agent || this.defaultAgent;
|
|
2645
2812
|
if (!AGENT_RE.test(agent)) throw new Error(`agent "${agent}" is invalid: use lowercase letters, digits, '.', '_' or '-' (max 64)`);
|
|
2646
2813
|
const sc = opts.storeContent ?? this.storeContent;
|
|
2647
2814
|
const apiKey = this.apiKey;
|
|
2648
|
-
let
|
|
2649
|
-
while (!
|
|
2815
|
+
let recs = null;
|
|
2816
|
+
while (!recs) {
|
|
2650
2817
|
const bucket = this.writeBucket(apiKey, agent);
|
|
2651
2818
|
const scope = scopeOf(bucket);
|
|
2652
2819
|
const key = this.key(agent, scope);
|
|
2653
2820
|
const chain = `${bucket}/${agent}`;
|
|
2654
2821
|
if (!fs2.existsSync(this.state.chainPath(chain))) for (const e of Object.values(this.adoptInto(bucket, apiKey))) this.report(`signafaide: ${e.message}`);
|
|
2655
2822
|
const [project, env] = bucket.split("/");
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2823
|
+
const builds = items.map(
|
|
2824
|
+
({ kind, name, content, ref }) => bucket.startsWith(PENDING + "/") ? (seq, prev, prevTime) => {
|
|
2825
|
+
const [time, c] = this.timed(content, prevTime, agent, scope);
|
|
2826
|
+
return makeDraft(key, { kind, agent, seq, prev, name, content: c, ref: ref ?? null, time }, sc);
|
|
2827
|
+
} : (seq, prev, prevTime) => {
|
|
2828
|
+
const [time, c] = this.timed(content, prevTime, agent, scope);
|
|
2829
|
+
return makeRecord(key, { kind, project, env, agent, seq, prev, name, content: c, ref: this.state.translateRef(chain, ref), time });
|
|
2830
|
+
}
|
|
2831
|
+
);
|
|
2832
|
+
recs = this.state.appendRun(chain, builds, (r) => wireRecord(r, sc), () => this.writeBucket(apiKey, agent) === bucket);
|
|
2664
2833
|
}
|
|
2665
|
-
this.pending
|
|
2834
|
+
this.pending += recs.length;
|
|
2666
2835
|
this.exitFlushed = false;
|
|
2667
|
-
if (name !== INSTANCE_STOP) this.wrote.add(agent);
|
|
2836
|
+
if (items.some((i) => i.name !== INSTANCE_STOP)) this.wrote.add(agent);
|
|
2668
2837
|
if (!apiKey && !this.toldLocal && !this.quiet && !this.test) {
|
|
2669
2838
|
this.toldLocal = true;
|
|
2670
2839
|
console.error(
|
|
@@ -2673,7 +2842,76 @@ ${scope}`;
|
|
|
2673
2842
|
}
|
|
2674
2843
|
this.start();
|
|
2675
2844
|
if (this.pending >= this.flushSize) void this.tick();
|
|
2676
|
-
return
|
|
2845
|
+
return recs;
|
|
2846
|
+
}
|
|
2847
|
+
/** Declare the tools one source knows for an agent (§2.6): a `signafaide.tools` note followed
|
|
2848
|
+
* by one note per tool, written as one run. `source` names who declares (the SDK in this
|
|
2849
|
+
* program, or one gateway); the notes list the agent's whole tool list, the union over every
|
|
2850
|
+
* source that declared on this machine (§8 `tools/<agent>.json`), a tool recorded when any
|
|
2851
|
+
* source records it. Written at most once per recorder for the same set: the first time, and
|
|
2852
|
+
* again when the set changes. Returns the header's record hash. */
|
|
2853
|
+
declareTools(tools, opts) {
|
|
2854
|
+
const agent = opts.agent || this.defaultAgent;
|
|
2855
|
+
if (!AGENT_RE.test(agent)) throw new Error(`agent "${agent}" is invalid: use lowercase letters, digits, '.', '_' or '-' (max 64)`);
|
|
2856
|
+
const names = Object.keys(tools).sort(byCodePoint);
|
|
2857
|
+
const bad = names.find((n) => !n || /[\u0000-\u001f\u007f]/.test(n));
|
|
2858
|
+
if (bad !== void 0) throw new TypeError(`a tool name must be a non-empty string without control characters (got ${JSON.stringify(bad)})`);
|
|
2859
|
+
const id = `${agent}
|
|
2860
|
+
${opts.source}`;
|
|
2861
|
+
const sig = JSON.stringify(names.map((n) => [n, tools[n]]));
|
|
2862
|
+
const held = this.declared.get(id);
|
|
2863
|
+
const hash = this.state.declareTools(agent, opts.source, tools, (all, expired) => {
|
|
2864
|
+
if (held?.sig === sig && !expired) return held.hash;
|
|
2865
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2866
|
+
for (const [tool, recorded2] of Object.entries(all)) {
|
|
2867
|
+
const d = declaredName(tool);
|
|
2868
|
+
const prev = byName.get(d);
|
|
2869
|
+
if (!prev || recorded2 && !prev.recorded) byName.set(d, { tool, recorded: recorded2 });
|
|
2870
|
+
}
|
|
2871
|
+
const list = [...byName.keys()].sort(byCodePoint).map((d) => byName.get(d));
|
|
2872
|
+
const recorded = list.filter((t) => t.recorded).length;
|
|
2873
|
+
const items = [
|
|
2874
|
+
{ kind: "note", name: TOOLS, content: { source: opts.origin, tools: list.length, recorded, unrecorded: list.length - recorded } },
|
|
2875
|
+
...list.map((t) => ({ kind: "note", name: toolNoteName(t.tool, t.recorded), content: { tool: t.tool, recorded: t.recorded } }))
|
|
2876
|
+
];
|
|
2877
|
+
return recordHash(this.writeRun(items, { agent, storeContent: opts.storeContent })[0]);
|
|
2878
|
+
});
|
|
2879
|
+
this.declared.set(id, { sig, hash, agent, source: opts.source, origin: opts.origin, tools, storeContent: opts.storeContent });
|
|
2880
|
+
if (!this.toolTimer && !this.closed) {
|
|
2881
|
+
this.toolTimer = setInterval(() => this.refreshToolSources(), TOOL_SOURCE_REFRESH_MS);
|
|
2882
|
+
this.toolTimer.unref?.();
|
|
2883
|
+
}
|
|
2884
|
+
return hash;
|
|
2885
|
+
}
|
|
2886
|
+
/** Refresh this recorder's entries in `tools/<agent>.json` (§8): each gets the current time,
|
|
2887
|
+
* so other sources keep counting its tools, and entries of sources that stopped more than 24
|
|
2888
|
+
* hours ago drop out. Nothing is written to the chain unless that changed the agent's tool
|
|
2889
|
+
* list. Runs by itself once an hour while the process lives. Never throws. */
|
|
2890
|
+
refreshToolSources() {
|
|
2891
|
+
for (const d of [...this.declared.values()]) {
|
|
2892
|
+
try {
|
|
2893
|
+
this.declareTools(d.tools, { agent: d.agent, source: d.source, origin: d.origin, storeContent: d.storeContent });
|
|
2894
|
+
} catch (e) {
|
|
2895
|
+
this.report(`signafaide: could not refresh the tool list of ${d.agent}: ${e.message}`);
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
/** A clean end of this source (§8): its entries leave `tools/<agent>.json`, so the next
|
|
2900
|
+
* declaration by any source no longer counts its tools. Nothing is written to the chain. With
|
|
2901
|
+
* `source`, only that source ends (a gateway run inside another program). Never throws. */
|
|
2902
|
+
releaseToolSources(source) {
|
|
2903
|
+
for (const [id, d] of [...this.declared]) {
|
|
2904
|
+
if (source !== void 0 && d.source !== source) continue;
|
|
2905
|
+
try {
|
|
2906
|
+
this.state.releaseToolSource(d.agent, d.source);
|
|
2907
|
+
} catch {
|
|
2908
|
+
}
|
|
2909
|
+
this.declared.delete(id);
|
|
2910
|
+
}
|
|
2911
|
+
if (!this.declared.size && this.toolTimer) {
|
|
2912
|
+
clearInterval(this.toolTimer);
|
|
2913
|
+
this.toolTimer = void 0;
|
|
2914
|
+
}
|
|
2677
2915
|
}
|
|
2678
2916
|
/** Where `agent`'s next record goes. A known project whose chain for `agent` is not on this
|
|
2679
2917
|
* disk (an empty `SIGNAFAIDE_HOME` on Lambda or Cloud Run) may already have that chain on the
|
|
@@ -3093,10 +3331,22 @@ ${k.keyId}`;
|
|
|
3093
3331
|
/** The program has nothing left to do: one last attempt, held open at most `exitFlushMs`.
|
|
3094
3332
|
* Background requests never keep the process alive, so they are cut off here. */
|
|
3095
3333
|
onBeforeExit = () => {
|
|
3334
|
+
void this.exitAttempt();
|
|
3335
|
+
};
|
|
3336
|
+
/** The last attempt of a program that ends on its own schedule (the gateway, once its server
|
|
3337
|
+
* has exited): what happens by itself when a program runs out of work. Ships what is left and,
|
|
3338
|
+
* when stop notes are on, writes the signed stop note, all within `exitFlushMs`; then stops the
|
|
3339
|
+
* background timer. Never throws. */
|
|
3340
|
+
async shutdown() {
|
|
3341
|
+
await this.exitAttempt();
|
|
3342
|
+
this.releaseToolSources();
|
|
3343
|
+
this.close();
|
|
3344
|
+
}
|
|
3345
|
+
exitAttempt() {
|
|
3096
3346
|
const stop2 = this.instanceStop && this.wrote.size > 0 && !this.test;
|
|
3097
|
-
if (this.exitFlushed || !this.exitFlushMs || !this.apiKey || !this.waiting() && !stop2) return;
|
|
3347
|
+
if (this.exitFlushed || !this.exitFlushMs || !this.apiKey || !this.waiting() && !stop2) return void 0;
|
|
3098
3348
|
this.exitFlushed = true;
|
|
3099
|
-
if (this.halted && this.halted.fp === this.configFp()) return;
|
|
3349
|
+
if (this.halted && this.halted.fp === this.configFp()) return void 0;
|
|
3100
3350
|
this.bgAbort.abort();
|
|
3101
3351
|
this.bgAbort = new AbortController();
|
|
3102
3352
|
const ctrl = new AbortController();
|
|
@@ -3106,15 +3356,16 @@ ${k.keyId}`;
|
|
|
3106
3356
|
clearTimeout(timer);
|
|
3107
3357
|
this.exitFlushed = true;
|
|
3108
3358
|
};
|
|
3109
|
-
if (!stop2)
|
|
3110
|
-
|
|
3359
|
+
if (!stop2) return this.flushLogged(this.exitFlushMs, "exit").then(done, done);
|
|
3360
|
+
return this.stop(this.exitFlushMs, "exit").then((r) => {
|
|
3111
3361
|
const errs = Object.entries(r.errors);
|
|
3112
3362
|
if (errs.length && !ctrl.signal.aborted) this.report("signafaide: " + errs.map(([a, e]) => e.message.includes(a) ? e.message : `${a}: ${e.message}`).join("; "));
|
|
3113
3363
|
}, (e) => {
|
|
3114
3364
|
if (!ctrl.signal.aborted) this.report(`signafaide: ${e.message}`);
|
|
3115
|
-
}).
|
|
3116
|
-
}
|
|
3365
|
+
}).then(done, done);
|
|
3366
|
+
}
|
|
3117
3367
|
onExit = () => {
|
|
3368
|
+
this.releaseToolSources();
|
|
3118
3369
|
const left = this.waiting();
|
|
3119
3370
|
if (left && !this.quiet && this.apiKey) {
|
|
3120
3371
|
console.error(`signafaide: ${plural(left, "record")} kept in ${path2.join(this.state.home, "outbox")}; they ship on the next run or with \`signafaide flush\``);
|
|
@@ -3123,6 +3374,8 @@ ${k.keyId}`;
|
|
|
3123
3374
|
/** Stop the background timer and exit hooks (records stay in the outbox). */
|
|
3124
3375
|
close() {
|
|
3125
3376
|
this.closed = true;
|
|
3377
|
+
if (this.toolTimer) clearInterval(this.toolTimer);
|
|
3378
|
+
this.toolTimer = void 0;
|
|
3126
3379
|
if (this.timer) clearInterval(this.timer);
|
|
3127
3380
|
this.timer = void 0;
|
|
3128
3381
|
this.bgAbort.abort();
|
|
@@ -3651,6 +3904,9 @@ var Report = class {
|
|
|
3651
3904
|
notSent = 0;
|
|
3652
3905
|
/** The project's name from the signed export statement, when the pack has one. */
|
|
3653
3906
|
projectName = null;
|
|
3907
|
+
/** Tool coverage per agent (§7; per chain for local state): the agent's current tool
|
|
3908
|
+
* declaration and the recorded tools it does not list. A note, never a failure. */
|
|
3909
|
+
coverage = {};
|
|
3654
3910
|
/** e.g. `PASS: export of every agent, all records, generated <t>`, or
|
|
3655
3911
|
* `PASS (agent keys taken from the pack, not independent): export of ...` (§7). */
|
|
3656
3912
|
get resultLine() {
|
|
@@ -3674,6 +3930,11 @@ var Report = class {
|
|
|
3674
3930
|
firstFailed() {
|
|
3675
3931
|
return this.checks.find((c) => c.status === "fail")?.id ?? null;
|
|
3676
3932
|
}
|
|
3933
|
+
/** Tool coverage in words (§7), agent by agent: what each agent declared, then the recorded
|
|
3934
|
+
* tools its declaration does not list. */
|
|
3935
|
+
coverageLines() {
|
|
3936
|
+
return Object.entries(this.coverage).flatMap(([agent, c]) => coverageLines(agent, c));
|
|
3937
|
+
}
|
|
3677
3938
|
lines() {
|
|
3678
3939
|
const out = [...this.notes];
|
|
3679
3940
|
if (this.project && this.env) {
|
|
@@ -3689,6 +3950,7 @@ var Report = class {
|
|
|
3689
3950
|
if (c.failures.length > 20) out.push(` ... and ${c.failures.length - 20} more`);
|
|
3690
3951
|
}
|
|
3691
3952
|
if (this.checks.some((c) => c.status === "pending")) out.push(PENDING_MEANS);
|
|
3953
|
+
out.push(...this.coverageLines().map((l) => `coverage: ${l}`));
|
|
3692
3954
|
out.push(...this.after);
|
|
3693
3955
|
out.push(...this.blocked.map((b) => `BLOCKED ${b}`));
|
|
3694
3956
|
out.push(this.resultLine);
|
|
@@ -3711,6 +3973,7 @@ var Report = class {
|
|
|
3711
3973
|
independent: this.independent,
|
|
3712
3974
|
notes: this.notes,
|
|
3713
3975
|
checks: this.checks,
|
|
3976
|
+
coverage: this.coverage,
|
|
3714
3977
|
...this.after.length ? { not_yet: this.after } : {},
|
|
3715
3978
|
...this.blocked.length ? { blocked: this.blocked } : {}
|
|
3716
3979
|
};
|
|
@@ -3839,6 +4102,7 @@ function verifyPack(pack, serviceKeys, opts = {}) {
|
|
|
3839
4102
|
byService
|
|
3840
4103
|
});
|
|
3841
4104
|
report.projectName = typeof pack.project_name === "string" ? pack.project_name : null;
|
|
4105
|
+
report.coverage = v.coverage;
|
|
3842
4106
|
return report;
|
|
3843
4107
|
}
|
|
3844
4108
|
function earliest(a, b) {
|
|
@@ -3852,6 +4116,7 @@ function verifyLocalState(local, serviceKeys, drand = null, serviceKeysPlainHttp
|
|
|
3852
4116
|
const checks = v.run(local.records, [], local.chainIds);
|
|
3853
4117
|
const report = new Report(checks, null, null, local.records.length, [], [], { service_keys: !serviceKeysPlainHttp, agent_keys: true });
|
|
3854
4118
|
report.nothingCosigned = v.nothingCosigned;
|
|
4119
|
+
report.coverage = v.coverage;
|
|
3855
4120
|
return report;
|
|
3856
4121
|
}
|
|
3857
4122
|
var isU64 = (x) => typeof x === "number" && Number.isInteger(x) && x >= 0;
|
|
@@ -3982,6 +4247,8 @@ var Verifier = class {
|
|
|
3982
4247
|
firstKey = /* @__PURE__ */ new Map();
|
|
3983
4248
|
drafts = 0;
|
|
3984
4249
|
nothingCosigned = false;
|
|
4250
|
+
/** Tool coverage per chain (§7), filled by `run`. */
|
|
4251
|
+
coverage = {};
|
|
3985
4252
|
/** Local state: chain id -> the witness's signed head a chain continues after (§8). */
|
|
3986
4253
|
localBases = {};
|
|
3987
4254
|
/** A chain may start after seq 1 only when the export's from is after the agent's first key. */
|
|
@@ -4168,6 +4435,11 @@ var Verifier = class {
|
|
|
4168
4435
|
if (starts.length) acc.notes.push(`${INSTANCE_NOTE}: ${starts.join(", ")}`);
|
|
4169
4436
|
if (clean.length) acc.notes.push(`${CLEAN_RESTART_NOTE}: ${clean.join(", ")}`);
|
|
4170
4437
|
if (based.length) acc.notes.push(`continues after a co-signed record this machine does not hold: ${based.join(", ")} (the witness's signed head, see signed heads)`);
|
|
4438
|
+
for (const agent of [...chains.keys()].sort()) {
|
|
4439
|
+
const chain = chains.get(agent);
|
|
4440
|
+
const seqs = [...chain.keys()].sort((x, y) => x - y);
|
|
4441
|
+
this.coverage[agent] = coverage(seqs.map((seq) => ({ seq, kind: chain.get(seq).r.envelope.kind, name: chain.get(seq).r.envelope.name })));
|
|
4442
|
+
}
|
|
4171
4443
|
return chains;
|
|
4172
4444
|
}
|
|
4173
4445
|
// 5
|
|
@@ -4592,16 +4864,89 @@ init_version();
|
|
|
4592
4864
|
var NotRecorded = class extends SignafaideError {
|
|
4593
4865
|
name = "NotRecorded";
|
|
4594
4866
|
};
|
|
4867
|
+
var WRAPPED = /* @__PURE__ */ new Map();
|
|
4868
|
+
var LISTED = /* @__PURE__ */ new Map();
|
|
4869
|
+
var EXPLICIT = /* @__PURE__ */ new Map();
|
|
4870
|
+
var toolsVersion = 0;
|
|
4871
|
+
var checked = /* @__PURE__ */ new WeakMap();
|
|
4872
|
+
var toldDeclare = false;
|
|
4873
|
+
function autoDeclareTools(env = process.env) {
|
|
4874
|
+
return !/^(0|false|no|off)$/i.test((env.SIGNAFAIDE_DECLARE_TOOLS ?? "").trim());
|
|
4875
|
+
}
|
|
4876
|
+
function remember(map, agent, name) {
|
|
4877
|
+
const set = map.get(agent ?? "") ?? /* @__PURE__ */ new Set();
|
|
4878
|
+
map.set(agent ?? "", set);
|
|
4879
|
+
if (!set.has(name)) {
|
|
4880
|
+
set.add(name);
|
|
4881
|
+
toolsVersion++;
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
function namesFor(map, agent, isDefault) {
|
|
4885
|
+
return /* @__PURE__ */ new Set([...map.get(agent) ?? [], ...isDefault ? map.get("") ?? [] : []]);
|
|
4886
|
+
}
|
|
4887
|
+
function ownTools(rec, agent) {
|
|
4888
|
+
const isDefault = agent === rec.defaultAgent;
|
|
4889
|
+
const explicit = EXPLICIT.get(agent) ?? (isDefault ? EXPLICIT.get("") : void 0);
|
|
4890
|
+
if (explicit) {
|
|
4891
|
+
const wrapped = namesFor(WRAPPED, agent, isDefault);
|
|
4892
|
+
const tools = {};
|
|
4893
|
+
for (const n of explicit.names) tools[n] = wrapped.has(n) && !explicit.unrecorded.has(n);
|
|
4894
|
+
for (const n of explicit.unrecorded) tools[n] = false;
|
|
4895
|
+
return [tools, "declared"];
|
|
4896
|
+
}
|
|
4897
|
+
const listed = namesFor(LISTED, agent, isDefault);
|
|
4898
|
+
if (!listed.size || !autoDeclareTools()) return null;
|
|
4899
|
+
return [Object.fromEntries([...listed].map((n) => [n, true])), "wrap_tools"];
|
|
4900
|
+
}
|
|
4901
|
+
function declareOwn(rec, agentOpt, storeContent) {
|
|
4902
|
+
const agent = agentOpt || rec.defaultAgent;
|
|
4903
|
+
const own = ownTools(rec, agent);
|
|
4904
|
+
if (!own) return "";
|
|
4905
|
+
return rec.declareTools(own[0], { agent, source: "sdk", origin: own[1], storeContent });
|
|
4906
|
+
}
|
|
4907
|
+
function declareBeforeCall(rec, agentOpt) {
|
|
4908
|
+
const agent = agentOpt || rec.defaultAgent;
|
|
4909
|
+
const seen = checked.get(rec) ?? /* @__PURE__ */ new Map();
|
|
4910
|
+
checked.set(rec, seen);
|
|
4911
|
+
if (seen.get(agent) === toolsVersion || noWrites.getStore()) return;
|
|
4912
|
+
try {
|
|
4913
|
+
declareOwn(rec, agentOpt);
|
|
4914
|
+
seen.set(agent, toolsVersion);
|
|
4915
|
+
} catch (e) {
|
|
4916
|
+
seen.set(agent, toolsVersion);
|
|
4917
|
+
if (!toldDeclare) {
|
|
4918
|
+
toldDeclare = true;
|
|
4919
|
+
console.error(`signafaide: could not write the tool declaration for ${agent}: ${e.message}`);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
function declareTools(names, opts = {}) {
|
|
4924
|
+
const list = [...names];
|
|
4925
|
+
const unrecorded = [...opts.unrecorded ?? []];
|
|
4926
|
+
for (const n of [...list, ...unrecorded]) if (typeof n !== "string" || !n) throw new TypeError("declareTools needs tool names: non-empty strings");
|
|
4927
|
+
EXPLICIT.set(opts.agent ?? "", { names: list, unrecorded: new Set(unrecorded) });
|
|
4928
|
+
toolsVersion++;
|
|
4929
|
+
if (recordingDisabled()) return "";
|
|
4930
|
+
const rec = getRecorder();
|
|
4931
|
+
const hash = declareOwn(rec, opts.agent);
|
|
4932
|
+
const seen = checked.get(rec) ?? /* @__PURE__ */ new Map();
|
|
4933
|
+
checked.set(rec, seen);
|
|
4934
|
+
seen.set(opts.agent || rec.defaultAgent, toolsVersion);
|
|
4935
|
+
return hash;
|
|
4936
|
+
}
|
|
4595
4937
|
var Spec = class {
|
|
4596
|
-
constructor(name, opts) {
|
|
4938
|
+
constructor(name, opts, listed = false) {
|
|
4597
4939
|
this.name = name;
|
|
4598
4940
|
this.opts = opts;
|
|
4599
4941
|
if (!name) throw new TypeError('record(fn) needs a name: record(fn, { name: "approve_payout" })');
|
|
4942
|
+
remember(WRAPPED, opts.agent, name);
|
|
4943
|
+
if (listed) remember(LISTED, opts.agent, name);
|
|
4600
4944
|
}
|
|
4601
4945
|
name;
|
|
4602
4946
|
opts;
|
|
4603
4947
|
decide(input) {
|
|
4604
4948
|
const rec = getRecorder();
|
|
4949
|
+
declareBeforeCall(rec, this.opts.agent);
|
|
4605
4950
|
const meta = { sdk: `signafaide-js/${VERSION}`, ...this.opts.meta ?? {} };
|
|
4606
4951
|
const content = { input: toJsonable(input) ?? null, meta: toJsonable(meta) };
|
|
4607
4952
|
return [rec, rec.write("decision", this.name, content, { agent: this.opts.agent, storeContent: this.opts.storeContent })];
|
|
@@ -4655,7 +5000,9 @@ var Spec = class {
|
|
|
4655
5000
|
}
|
|
4656
5001
|
};
|
|
4657
5002
|
function record(fn, opts = {}) {
|
|
4658
|
-
|
|
5003
|
+
return recordWith(new Spec(opts.name ?? fn.name, opts), fn);
|
|
5004
|
+
}
|
|
5005
|
+
function recordWith(spec, fn) {
|
|
4659
5006
|
const wrapped = function(...args) {
|
|
4660
5007
|
return spec.call(args.length === 1 ? args[0] : args, () => fn.apply(this, args));
|
|
4661
5008
|
};
|
|
@@ -4678,7 +5025,8 @@ var isOpenAIAgentsTool = (t) => t?.type === "function" && typeof t.invoke === "f
|
|
|
4678
5025
|
var isLangChainTool = (t) => Array.isArray(t?.lc_namespace) && typeof t._call === "function" && typeof t.invoke === "function";
|
|
4679
5026
|
function wrapTool(tool, opts = {}) {
|
|
4680
5027
|
const t = tool;
|
|
4681
|
-
const
|
|
5028
|
+
const { declareTools: declare = true, ...ropts } = opts;
|
|
5029
|
+
const spec = (name) => new Spec(ropts.name ?? name, ropts, declare);
|
|
4682
5030
|
if (isOpenAIAgentsTool(t)) {
|
|
4683
5031
|
const s = spec(t.name);
|
|
4684
5032
|
ADAPTERS.add("openai-agents");
|
|
@@ -4696,7 +5044,7 @@ function wrapTool(tool, opts = {}) {
|
|
|
4696
5044
|
};
|
|
4697
5045
|
return proxy;
|
|
4698
5046
|
}
|
|
4699
|
-
if (typeof t === "function") return
|
|
5047
|
+
if (typeof t === "function") return recordWith(spec(t.name), t);
|
|
4700
5048
|
if (typeof t?.execute === "function") {
|
|
4701
5049
|
const s = spec(t.name ?? "");
|
|
4702
5050
|
ADAPTERS.add("vercel-ai");
|
|
@@ -5342,6 +5690,7 @@ async function verify3(pack, opts = {}) {
|
|
|
5342
5690
|
HEADER,
|
|
5343
5691
|
JsonNumber,
|
|
5344
5692
|
KeysRequired,
|
|
5693
|
+
MAX_DECLARED_NAME,
|
|
5345
5694
|
MAX_NODES,
|
|
5346
5695
|
MAX_PROOF_BYTES,
|
|
5347
5696
|
MAX_ROUND,
|
|
@@ -5354,11 +5703,17 @@ async function verify3(pack, opts = {}) {
|
|
|
5354
5703
|
SigningKey,
|
|
5355
5704
|
State,
|
|
5356
5705
|
StateError,
|
|
5706
|
+
TOOLS,
|
|
5707
|
+
TOOL_PREFIX,
|
|
5708
|
+
TOOL_SOURCE_MAX_AGE_MS,
|
|
5709
|
+
TOOL_SOURCE_REFRESH_MS,
|
|
5710
|
+
TOOL_UNRECORDED_PREFIX,
|
|
5357
5711
|
VERSION,
|
|
5358
5712
|
VerificationFailed,
|
|
5359
5713
|
agentName,
|
|
5360
5714
|
assertPortableNumbers,
|
|
5361
5715
|
auditPath,
|
|
5716
|
+
autoDeclareTools,
|
|
5362
5717
|
b64url,
|
|
5363
5718
|
b64urlDecode,
|
|
5364
5719
|
blockAttestations,
|
|
@@ -5376,6 +5731,10 @@ async function verify3(pack, opts = {}) {
|
|
|
5376
5731
|
contentFromText,
|
|
5377
5732
|
contentHash,
|
|
5378
5733
|
countsText,
|
|
5734
|
+
coverage,
|
|
5735
|
+
coverageLines,
|
|
5736
|
+
declareTools,
|
|
5737
|
+
declaredName,
|
|
5379
5738
|
detectAgent,
|
|
5380
5739
|
draftErrors,
|
|
5381
5740
|
envelopeErrors,
|
|
@@ -5431,6 +5790,8 @@ async function verify3(pack, opts = {}) {
|
|
|
5431
5790
|
testMode,
|
|
5432
5791
|
timestampHeights,
|
|
5433
5792
|
toJsonable,
|
|
5793
|
+
toolNote,
|
|
5794
|
+
toolNoteName,
|
|
5434
5795
|
validProjectId,
|
|
5435
5796
|
validTime,
|
|
5436
5797
|
verify,
|