synartesis 0.6.11 → 0.6.13
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/CHANGELOG.md +200 -0
- package/README.md +103 -2
- package/dist/{chunk-DRDKIFD3.js → chunk-6X7GPUGJ.js} +248 -46
- package/dist/cli.js +192 -43
- package/dist/proxy.js +21 -3
- package/manifests/filesystem.yaml +8 -0
- package/manifests/git.yaml +8 -0
- package/manifests/github.yaml +10 -0
- package/manifests/memory.yaml +18 -0
- package/package.json +8 -2
|
@@ -240,6 +240,36 @@ CREATE TABLE IF NOT EXISTS actions (
|
|
|
240
240
|
);
|
|
241
241
|
|
|
242
242
|
CREATE INDEX IF NOT EXISTS actions_by_run ON actions(run_id, seq);
|
|
243
|
+
|
|
244
|
+
-- Deliberately not a schema version bump. Adding an index changes no row and
|
|
245
|
+
-- no meaning, IF NOT EXISTS makes it idempotent, and the statement is run on
|
|
246
|
+
-- every open -- so a journal written months ago gains these the next time it
|
|
247
|
+
-- is opened, and an older build opening the same file afterwards neither
|
|
248
|
+
-- notices nor cares. A version bump would have been the opposite: this build
|
|
249
|
+
-- refuses to open a journal from a different schema, and telling somebody to
|
|
250
|
+
-- abandon everything an agent has ever done in order to gain an index would
|
|
251
|
+
-- be a poor trade.
|
|
252
|
+
--
|
|
253
|
+
-- Both of these sit in front of a person waiting. findApproval runs twice on
|
|
254
|
+
-- every gated call and findGated backs the gates command and the console;
|
|
255
|
+
-- without them each is a full scan over rows that carry the snapshots, which
|
|
256
|
+
-- is the largest thing in the table. Measured at fifty thousand actions with
|
|
257
|
+
-- two-kilobyte snapshots: 61ms to 0.01ms, and 56ms to 0.00ms.
|
|
258
|
+
CREATE INDEX IF NOT EXISTS actions_approved ON actions(server, tool, status, approved_at);
|
|
259
|
+
CREATE INDEX IF NOT EXISTS actions_gated ON actions(status, ts);
|
|
260
|
+
|
|
261
|
+
-- Covering, and that is the whole point. listRuns needs a count and three
|
|
262
|
+
-- status tallies per run, and without this the group-by scans the table --
|
|
263
|
+
-- which carries the snapshots, so the cost of listing sessions grew with the
|
|
264
|
+
-- size of the data those sessions touched, not with how many there were.
|
|
265
|
+
-- Adding status to the run index lets sqlite answer entirely from the index.
|
|
266
|
+
-- Measured on forty runs of five hundred actions with two-kilobyte snapshots,
|
|
267
|
+
-- a hundred-megabyte journal: 76ms to 2ms.
|
|
268
|
+
--
|
|
269
|
+
-- Added the same way as the two above and for the same reason: no row changes,
|
|
270
|
+
-- no meaning changes, IF NOT EXISTS makes it idempotent, and an older build
|
|
271
|
+
-- opening the same file afterwards neither notices nor cares.
|
|
272
|
+
CREATE INDEX IF NOT EXISTS actions_run_status ON actions(run_id, status);
|
|
243
273
|
`;
|
|
244
274
|
|
|
245
275
|
// src/journal/journal.ts
|
|
@@ -251,6 +281,13 @@ var runSchema = z.object({
|
|
|
251
281
|
ended_at: z.string().nullable(),
|
|
252
282
|
status: z.enum(["active", "complete", "rolled_back", "partial"])
|
|
253
283
|
});
|
|
284
|
+
var tallySchema = z.object({
|
|
285
|
+
run_id: z.string(),
|
|
286
|
+
actions: z.number(),
|
|
287
|
+
unknown: z.number().nullable(),
|
|
288
|
+
waiting: z.number().nullable(),
|
|
289
|
+
applied: z.number().nullable()
|
|
290
|
+
});
|
|
254
291
|
var actionSchema = z.object({
|
|
255
292
|
id: z.string(),
|
|
256
293
|
run_id: z.string(),
|
|
@@ -682,6 +719,30 @@ var SqliteJournal = class {
|
|
|
682
719
|
() => this.#db.prepare("SELECT * FROM actions WHERE run_id = ? ORDER BY seq").all(runId).map(toAction)
|
|
683
720
|
);
|
|
684
721
|
}
|
|
722
|
+
tallyRuns() {
|
|
723
|
+
return this.#run("tallyRuns", () => {
|
|
724
|
+
const rows = this.#db.prepare(
|
|
725
|
+
`SELECT run_id,
|
|
726
|
+
COUNT(*) AS actions,
|
|
727
|
+
SUM(status = 'pending') AS unknown,
|
|
728
|
+
SUM(status = 'gated') AS waiting,
|
|
729
|
+
SUM(status = 'applied') AS applied
|
|
730
|
+
FROM actions
|
|
731
|
+
GROUP BY run_id`
|
|
732
|
+
).all();
|
|
733
|
+
const tally = /* @__PURE__ */ new Map();
|
|
734
|
+
for (const row of rows) {
|
|
735
|
+
const counts = tallySchema.parse(row);
|
|
736
|
+
tally.set(counts.run_id, {
|
|
737
|
+
actions: counts.actions,
|
|
738
|
+
unknown: counts.unknown ?? 0,
|
|
739
|
+
waiting: counts.waiting ?? 0,
|
|
740
|
+
applied: counts.applied ?? 0
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
return tally;
|
|
744
|
+
});
|
|
745
|
+
}
|
|
685
746
|
recentActions(limit) {
|
|
686
747
|
return this.#run(
|
|
687
748
|
"recentActions",
|
|
@@ -956,17 +1017,20 @@ var toolPolicy = z2.strictObject({
|
|
|
956
1017
|
gate: z2.enum(["always", "on_write", "never"]).optional(),
|
|
957
1018
|
refusal: z2.enum(["uncertain", "clean"]).optional(),
|
|
958
1019
|
snapshot: callTemplate.optional(),
|
|
959
|
-
inverse: callTemplate.optional()
|
|
1020
|
+
inverse: callTemplate.optional(),
|
|
1021
|
+
verify: callTemplate.optional()
|
|
960
1022
|
});
|
|
961
1023
|
var serverSpec = z2.strictObject({
|
|
962
1024
|
command: z2.string().min(1),
|
|
963
1025
|
args: z2.array(z2.string()).default([]),
|
|
964
|
-
env: z2.record(z2.string(), z2.string()).optional()
|
|
1026
|
+
env: z2.record(z2.string(), z2.string()).optional(),
|
|
1027
|
+
provenance: z2.enum(["live", "documented"]).optional()
|
|
965
1028
|
});
|
|
966
1029
|
var manifestSchema = z2.strictObject({
|
|
967
1030
|
version: z2.literal(1),
|
|
968
1031
|
servers: z2.record(z2.string(), serverSpec),
|
|
969
|
-
tools: z2.array(toolPolicy).default([])
|
|
1032
|
+
tools: z2.array(toolPolicy).default([]),
|
|
1033
|
+
pins: z2.record(z2.string(), z2.record(z2.string(), z2.string().min(1))).optional()
|
|
970
1034
|
});
|
|
971
1035
|
var Source = class {
|
|
972
1036
|
constructor(doc, lines, file) {
|
|
@@ -1049,6 +1113,11 @@ function validate(source, manifest) {
|
|
|
1049
1113
|
if (servers.length === 0) {
|
|
1050
1114
|
source.fail(["servers"], "at least one server must be declared");
|
|
1051
1115
|
}
|
|
1116
|
+
for (const name of Object.keys(manifest.pins ?? {})) {
|
|
1117
|
+
if (!servers.includes(name)) {
|
|
1118
|
+
source.fail(["pins", name], `pins name server ${name}, which is not declared`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1052
1121
|
const seen = /* @__PURE__ */ new Map();
|
|
1053
1122
|
manifest.tools.forEach((policy, index) => {
|
|
1054
1123
|
const path = ["tools", index];
|
|
@@ -1070,6 +1139,9 @@ function validate(source, manifest) {
|
|
|
1070
1139
|
`${policy.match} names server ${segment}, which is not declared`
|
|
1071
1140
|
);
|
|
1072
1141
|
}
|
|
1142
|
+
if (policy.verify !== void 0 && policy.class === "readonly") {
|
|
1143
|
+
source.fail([...path, "verify"], "a readonly tool has no post-state to check for drift");
|
|
1144
|
+
}
|
|
1073
1145
|
const needsInverse = policy.class === "reversible" || policy.class === "compensable";
|
|
1074
1146
|
if (needsInverse && policy.inverse === void 0) {
|
|
1075
1147
|
source.fail(path, `a ${policy.class} tool must declare an inverse`);
|
|
@@ -1113,7 +1185,8 @@ function withGate(policy) {
|
|
|
1113
1185
|
gate,
|
|
1114
1186
|
refusal: policy.refusal ?? "uncertain",
|
|
1115
1187
|
...policy.snapshot === void 0 ? {} : { snapshot: toCall(policy.snapshot) },
|
|
1116
|
-
...policy.inverse === void 0 ? {} : { inverse: toCall(policy.inverse) }
|
|
1188
|
+
...policy.inverse === void 0 ? {} : { inverse: toCall(policy.inverse) },
|
|
1189
|
+
...policy.verify === void 0 ? {} : { verify: toCall(policy.verify) }
|
|
1117
1190
|
};
|
|
1118
1191
|
}
|
|
1119
1192
|
function parseManifest(text, file) {
|
|
@@ -1149,11 +1222,13 @@ function parseManifest(text, file) {
|
|
|
1149
1222
|
{
|
|
1150
1223
|
command: spec.command,
|
|
1151
1224
|
args: spec.args,
|
|
1152
|
-
...spec.env === void 0 ? {} : { env: expandEnvironment(source, ["servers", name], spec.env) }
|
|
1225
|
+
...spec.env === void 0 ? {} : { env: expandEnvironment(source, ["servers", name], spec.env) },
|
|
1226
|
+
...spec.provenance === void 0 ? {} : { provenance: spec.provenance }
|
|
1153
1227
|
}
|
|
1154
1228
|
])
|
|
1155
1229
|
),
|
|
1156
|
-
tools: parsed.data.tools.map(withGate)
|
|
1230
|
+
tools: parsed.data.tools.map(withGate),
|
|
1231
|
+
...parsed.data.pins === void 0 ? {} : { pins: parsed.data.pins }
|
|
1157
1232
|
};
|
|
1158
1233
|
validate(source, manifest);
|
|
1159
1234
|
return manifest;
|
|
@@ -1176,8 +1251,41 @@ function loadManifest(path) {
|
|
|
1176
1251
|
return parseManifest(text, path);
|
|
1177
1252
|
}
|
|
1178
1253
|
|
|
1179
|
-
// src/manifest/
|
|
1180
|
-
import {
|
|
1254
|
+
// src/manifest/pin.ts
|
|
1255
|
+
import { createHash } from "crypto";
|
|
1256
|
+
|
|
1257
|
+
// src/manifest/match.ts
|
|
1258
|
+
function toRegExp(pattern) {
|
|
1259
|
+
const source = pattern.split("*").map((literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^.]*");
|
|
1260
|
+
return new RegExp(`^${source}$`);
|
|
1261
|
+
}
|
|
1262
|
+
function literalLength(pattern) {
|
|
1263
|
+
return pattern.length - pattern.split("*").length + 1;
|
|
1264
|
+
}
|
|
1265
|
+
function failClosed(qualifiedName) {
|
|
1266
|
+
return { match: qualifiedName, class: "irreversible", gate: "always", refusal: "uncertain" };
|
|
1267
|
+
}
|
|
1268
|
+
function createPolicyResolver(manifest) {
|
|
1269
|
+
const compiled = manifest.tools.map((policy) => ({
|
|
1270
|
+
policy,
|
|
1271
|
+
test: toRegExp(policy.match),
|
|
1272
|
+
specificity: literalLength(policy.match),
|
|
1273
|
+
wildcards: policy.match.split("*").length - 1
|
|
1274
|
+
})).sort((a, b) => b.specificity - a.specificity || a.wildcards - b.wildcards);
|
|
1275
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1276
|
+
return {
|
|
1277
|
+
resolve(qualifiedName) {
|
|
1278
|
+
const cached2 = cache.get(qualifiedName);
|
|
1279
|
+
if (cached2 !== void 0) {
|
|
1280
|
+
return cached2;
|
|
1281
|
+
}
|
|
1282
|
+
const hit = compiled.find((candidate) => candidate.test.test(qualifiedName));
|
|
1283
|
+
const match = hit === void 0 ? { policy: failClosed(qualifiedName), matched: false } : { policy: hit.policy, matched: true };
|
|
1284
|
+
cache.set(qualifiedName, match);
|
|
1285
|
+
return match;
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1181
1289
|
|
|
1182
1290
|
// src/manifest/types.ts
|
|
1183
1291
|
function qualify(server, tool) {
|
|
@@ -1191,13 +1299,77 @@ function splitQualified(qualified) {
|
|
|
1191
1299
|
return { server: qualified.slice(0, dot), tool: qualified.slice(dot + 1) };
|
|
1192
1300
|
}
|
|
1193
1301
|
|
|
1302
|
+
// src/manifest/pin.ts
|
|
1303
|
+
function fingerprint(inputSchema) {
|
|
1304
|
+
return `sha256:${createHash("sha256").update(canonical(inputSchema)).digest("hex")}`;
|
|
1305
|
+
}
|
|
1306
|
+
function auditPins(server, advertised, manifest) {
|
|
1307
|
+
const pins = manifest.pins?.[server];
|
|
1308
|
+
if (pins === void 0) {
|
|
1309
|
+
return [];
|
|
1310
|
+
}
|
|
1311
|
+
const resolver = createPolicyResolver(manifest);
|
|
1312
|
+
const faults = [];
|
|
1313
|
+
const present = /* @__PURE__ */ new Set();
|
|
1314
|
+
for (const tool of advertised) {
|
|
1315
|
+
present.add(tool.name);
|
|
1316
|
+
if (!resolver.resolve(qualify(server, tool.name)).matched) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
const found = fingerprint(tool.inputSchema);
|
|
1320
|
+
const pinned = pins[tool.name];
|
|
1321
|
+
if (pinned === void 0) {
|
|
1322
|
+
faults.push({ kind: "unpinned", tool: tool.name, found });
|
|
1323
|
+
} else if (pinned !== found) {
|
|
1324
|
+
faults.push({ kind: "moved", tool: tool.name, pinned, found });
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
for (const name of Object.keys(pins)) {
|
|
1328
|
+
if (!present.has(name)) {
|
|
1329
|
+
faults.push({ kind: "gone", tool: name });
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return faults;
|
|
1333
|
+
}
|
|
1334
|
+
function pinBlock(shapes, manifest) {
|
|
1335
|
+
const resolver = createPolicyResolver(manifest);
|
|
1336
|
+
const lines = ["pins:"];
|
|
1337
|
+
for (const server of [...shapes.keys()].sort()) {
|
|
1338
|
+
const governed = (shapes.get(server) ?? []).filter((tool) => resolver.resolve(qualify(server, tool.name)).matched).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1339
|
+
if (governed.length === 0) {
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
lines.push(` ${server}:`);
|
|
1343
|
+
for (const tool of governed) {
|
|
1344
|
+
lines.push(` ${tool.name}: "${fingerprint(tool.inputSchema)}"`);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return lines.join("\n");
|
|
1348
|
+
}
|
|
1349
|
+
function explainPins(server, faults) {
|
|
1350
|
+
return faults.map((fault) => {
|
|
1351
|
+
switch (fault.kind) {
|
|
1352
|
+
case "moved":
|
|
1353
|
+
return `${server}.${fault.tool} no longer has the shape it was pinned at. Its policy was written for the old one, so the snapshot and inverse it carries may no longer describe this tool.
|
|
1354
|
+
pinned ${fault.pinned}
|
|
1355
|
+
now ${fault.found}`;
|
|
1356
|
+
case "unpinned":
|
|
1357
|
+
return `${server}.${fault.tool} is governed by a policy and has no pin, on a server where everything else is pinned.
|
|
1358
|
+
add ${fault.tool}: "${fault.found}"`;
|
|
1359
|
+
case "gone":
|
|
1360
|
+
return `${server}.${fault.tool} is pinned but the server does not expose it, so the pin vouches for nothing. Remove it, or connect the server that has it.`;
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1194
1365
|
// src/manifest/verify.ts
|
|
1366
|
+
import { z as z3 } from "zod";
|
|
1195
1367
|
var listSchema = z3.looseObject({
|
|
1196
|
-
tools: z3.array(z3.looseObject({ name: z3.string() })),
|
|
1368
|
+
tools: z3.array(z3.looseObject({ name: z3.string(), inputSchema: z3.unknown() })),
|
|
1197
1369
|
nextCursor: z3.string().optional()
|
|
1198
1370
|
});
|
|
1199
|
-
async function
|
|
1200
|
-
const
|
|
1371
|
+
async function toolShapes(upstream) {
|
|
1372
|
+
const shapes = [];
|
|
1201
1373
|
let cursor;
|
|
1202
1374
|
do {
|
|
1203
1375
|
const page = listSchema.parse(
|
|
@@ -1207,16 +1379,19 @@ async function toolNames(upstream) {
|
|
|
1207
1379
|
)
|
|
1208
1380
|
);
|
|
1209
1381
|
for (const tool of page.tools) {
|
|
1210
|
-
|
|
1382
|
+
shapes.push({ name: tool.name, inputSchema: tool.inputSchema });
|
|
1211
1383
|
}
|
|
1212
1384
|
cursor = page.nextCursor;
|
|
1213
1385
|
} while (cursor !== void 0);
|
|
1214
|
-
return
|
|
1386
|
+
return shapes;
|
|
1215
1387
|
}
|
|
1216
1388
|
async function verifyAgainstServers(upstreams, manifest) {
|
|
1389
|
+
const shapes = /* @__PURE__ */ new Map();
|
|
1217
1390
|
const available = /* @__PURE__ */ new Map();
|
|
1218
1391
|
for (const upstream of upstreams) {
|
|
1219
|
-
|
|
1392
|
+
const advertised = await toolShapes(upstream);
|
|
1393
|
+
shapes.set(upstream.name, advertised);
|
|
1394
|
+
available.set(upstream.name, new Set(advertised.map((tool) => tool.name)));
|
|
1220
1395
|
}
|
|
1221
1396
|
const problems = [];
|
|
1222
1397
|
const check = (qualified, role, match) => {
|
|
@@ -1242,11 +1417,50 @@ async function verifyAgainstServers(upstreams, manifest) {
|
|
|
1242
1417
|
if (policy.inverse !== void 0) {
|
|
1243
1418
|
check(policy.inverse.tool, "inverse", policy.match);
|
|
1244
1419
|
}
|
|
1420
|
+
if (policy.verify !== void 0) {
|
|
1421
|
+
check(policy.verify.tool, "verify", policy.match);
|
|
1422
|
+
}
|
|
1245
1423
|
}
|
|
1246
1424
|
if (problems.length > 0) {
|
|
1247
1425
|
throw new ManifestError(`the manifest calls tools that do not exist:
|
|
1248
1426
|
${problems.join("\n ")}`);
|
|
1249
1427
|
}
|
|
1428
|
+
const drifted = [];
|
|
1429
|
+
for (const upstream of upstreams) {
|
|
1430
|
+
const faults = auditPins(upstream.name, shapes.get(upstream.name) ?? [], manifest);
|
|
1431
|
+
drifted.push(...explainPins(upstream.name, faults));
|
|
1432
|
+
}
|
|
1433
|
+
if (drifted.length > 0) {
|
|
1434
|
+
throw new ManifestError(
|
|
1435
|
+
"a pinned tool no longer matches the policy written for it:\n " + drifted.join("\n ") + "\n\n Review what changed before trusting undo on these tools. `synartesis pin` prints the block for the servers you have now."
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/manifest/standing.ts
|
|
1441
|
+
function standing(manifest) {
|
|
1442
|
+
return Object.entries(manifest.servers).map(([server, spec]) => ({
|
|
1443
|
+
server,
|
|
1444
|
+
provenance: spec.provenance ?? "unstated"
|
|
1445
|
+
}));
|
|
1446
|
+
}
|
|
1447
|
+
function untested(manifest) {
|
|
1448
|
+
return standing(manifest).filter((entry) => entry.provenance === "documented").map((entry) => entry.server);
|
|
1449
|
+
}
|
|
1450
|
+
function describeStanding(entry) {
|
|
1451
|
+
switch (entry.provenance) {
|
|
1452
|
+
case "live":
|
|
1453
|
+
return "checked against the real server";
|
|
1454
|
+
case "documented":
|
|
1455
|
+
return "written from documentation, never run against the real server";
|
|
1456
|
+
case "unstated":
|
|
1457
|
+
return "no claim either way";
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function warnUntested(servers) {
|
|
1461
|
+
const names = servers.join(", ");
|
|
1462
|
+
const these = servers.length === 1 ? "this policy has" : "these policies have";
|
|
1463
|
+
return `${names}: ${these} never been run against the real server. The classes and inverses here come from documentation, so undo may not work where it says it will. Run \`synartesis check\` against your own credentials, and expect to correct something.`;
|
|
1250
1464
|
}
|
|
1251
1465
|
|
|
1252
1466
|
// src/proxy/routing.ts
|
|
@@ -1373,37 +1587,17 @@ async function start(spec) {
|
|
|
1373
1587
|
return { client };
|
|
1374
1588
|
}
|
|
1375
1589
|
|
|
1376
|
-
// src/
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
}
|
|
1384
|
-
function failClosed(qualifiedName) {
|
|
1385
|
-
return { match: qualifiedName, class: "irreversible", gate: "always", refusal: "uncertain" };
|
|
1386
|
-
}
|
|
1387
|
-
function createPolicyResolver(manifest) {
|
|
1388
|
-
const compiled = manifest.tools.map((policy) => ({
|
|
1389
|
-
policy,
|
|
1390
|
-
test: toRegExp(policy.match),
|
|
1391
|
-
specificity: literalLength(policy.match),
|
|
1392
|
-
wildcards: policy.match.split("*").length - 1
|
|
1393
|
-
})).sort((a, b) => b.specificity - a.specificity || a.wildcards - b.wildcards);
|
|
1394
|
-
const cache = /* @__PURE__ */ new Map();
|
|
1395
|
-
return {
|
|
1396
|
-
resolve(qualifiedName) {
|
|
1397
|
-
const cached2 = cache.get(qualifiedName);
|
|
1398
|
-
if (cached2 !== void 0) {
|
|
1399
|
-
return cached2;
|
|
1400
|
-
}
|
|
1401
|
-
const hit = compiled.find((candidate) => candidate.test.test(qualifiedName));
|
|
1402
|
-
const match = hit === void 0 ? { policy: failClosed(qualifiedName), matched: false } : { policy: hit.policy, matched: true };
|
|
1403
|
-
cache.set(qualifiedName, match);
|
|
1404
|
-
return match;
|
|
1590
|
+
// src/idempotency.ts
|
|
1591
|
+
var IDEMPOTENCY_META_KEY = "synartesis.dev/idempotency-key";
|
|
1592
|
+
function withIdempotencyKey(meta, key) {
|
|
1593
|
+
const merged = {};
|
|
1594
|
+
if (typeof meta === "object" && meta !== null) {
|
|
1595
|
+
for (const [name, value] of Object.entries(meta)) {
|
|
1596
|
+
merged[name] = value;
|
|
1405
1597
|
}
|
|
1406
|
-
}
|
|
1598
|
+
}
|
|
1599
|
+
merged[IDEMPOTENCY_META_KEY] = key;
|
|
1600
|
+
return merged;
|
|
1407
1601
|
}
|
|
1408
1602
|
|
|
1409
1603
|
// src/proxy/snapshot.ts
|
|
@@ -1565,9 +1759,17 @@ export {
|
|
|
1565
1759
|
wasRefused,
|
|
1566
1760
|
parseManifest,
|
|
1567
1761
|
loadManifest,
|
|
1762
|
+
createPolicyResolver,
|
|
1568
1763
|
qualify,
|
|
1764
|
+
pinBlock,
|
|
1765
|
+
toolShapes,
|
|
1569
1766
|
verifyAgainstServers,
|
|
1570
|
-
|
|
1767
|
+
standing,
|
|
1768
|
+
untested,
|
|
1769
|
+
describeStanding,
|
|
1770
|
+
warnUntested,
|
|
1771
|
+
IDEMPOTENCY_META_KEY,
|
|
1772
|
+
withIdempotencyKey,
|
|
1571
1773
|
createRouter,
|
|
1572
1774
|
refusal,
|
|
1573
1775
|
toPayload,
|
|
@@ -1580,4 +1782,4 @@ export {
|
|
|
1580
1782
|
observeState,
|
|
1581
1783
|
connectStdioUpstream
|
|
1582
1784
|
};
|
|
1583
|
-
//# sourceMappingURL=chunk-
|
|
1785
|
+
//# sourceMappingURL=chunk-6X7GPUGJ.js.map
|