synartesis 0.6.19 → 0.6.22
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 +101 -0
- package/README.md +19 -4
- package/dist/{chunk-SZHVLCJR.js → chunk-JLLAK2QC.js} +16 -2
- package/dist/cli.js +68 -7
- package/dist/proxy.js +23 -2
- package/manifests/memory.yaml +8 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,107 @@
|
|
|
2
2
|
|
|
3
3
|
What changed, and why it mattered. Dates are release dates.
|
|
4
4
|
|
|
5
|
+
## 0.6.22 — 2026-09-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **The memory policy's undo is now proven, not just plausible.** It has always
|
|
10
|
+
declared `provenance: live` -- the tools were checked against the real server
|
|
11
|
+
and the shapes read off its own answers -- while the README said its recovery
|
|
12
|
+
guarantees were unproven. Both were true, and the gap between them is where a
|
|
13
|
+
policy can name every tool correctly, take exactly the arguments the server
|
|
14
|
+
wants, and still resolve an inverse that puts nothing back. That failure
|
|
15
|
+
looks like success: the drift check passes and the report says `rolled_back`.
|
|
16
|
+
|
|
17
|
+
`tests/adapter-memory.test.ts` makes each change against a real knowledge
|
|
18
|
+
graph, undoes it, and compares the file. What the agent added is gone and
|
|
19
|
+
what was already there is untouched; an entity the agent only *tried* to
|
|
20
|
+
create -- this server ignores a duplicate name -- is left alone rather than
|
|
21
|
+
deleted out from under its owner; a relation drawn or removed goes back; and
|
|
22
|
+
a delete of an entity is held for a person rather than approximated, because
|
|
23
|
+
one inverse cannot put back both the entity and the relations that went with
|
|
24
|
+
it. Every one of those was confirmed by breaking the policy and watching the
|
|
25
|
+
matching test fail.
|
|
26
|
+
|
|
27
|
+
Two of the four shipped policies are now round-tripped end to end.
|
|
28
|
+
`synartesis check`, the README and both guides say which, and `git` is still
|
|
29
|
+
named plainly as untested.
|
|
30
|
+
|
|
31
|
+
- `check` also confirms the memory policy covers every tool that server offers,
|
|
32
|
+
so a server that grows one fails here rather than in front of somebody's
|
|
33
|
+
agent.
|
|
34
|
+
|
|
35
|
+
## 0.6.21 — 2026-09-16
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`live` claimed more than it meant, and the docs contradicted each other
|
|
40
|
+
about it.** `provenance: live` says a policy has met its server and that the
|
|
41
|
+
tools take the arguments it passes them. `check` printed that as "checked
|
|
42
|
+
against the real server", which reads as a claim that undo works -- and it is
|
|
43
|
+
not one: a policy can be right about every tool name and still record an
|
|
44
|
+
inverse that restores nothing. Meanwhile the README said memory's recovery
|
|
45
|
+
guarantees were unproven while the user guide listed memory among the
|
|
46
|
+
policies that say `live`, so a reader who saw one came away with the opposite
|
|
47
|
+
of what the other meant. `check` now says "shapes read from the real server"
|
|
48
|
+
and adds, once under the server list, that `live` is not a recovery claim and
|
|
49
|
+
that only filesystem has been round-tripped. README and both guides say the
|
|
50
|
+
same thing.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`check` names the tools no policy covers, instead of describing the rule.**
|
|
55
|
+
An unmatched tool has always been fail-closed -- irreversible, held for a
|
|
56
|
+
person the first time it is called -- and `check` said so as a sentence about
|
|
57
|
+
tools in general while the actual list sat one round trip away. It already
|
|
58
|
+
connects to every server and reads the whole tool list to verify the
|
|
59
|
+
policies, so the answer was in hand and thrown away, and the first anybody
|
|
60
|
+
learned a tool was ungoverned was an agent stopping on it mid-task. Now
|
|
61
|
+
named, per server, with a count. The proxy warns at startup for the same
|
|
62
|
+
reason, and `proxy ready` carries an `ungoverned` count so a policy with no
|
|
63
|
+
gaps and a build that forgot to look do not read alike.
|
|
64
|
+
|
|
65
|
+
For what almost everybody runs, the answer is none: the shipped filesystem
|
|
66
|
+
policy covers every tool that server offers, and there is a test that will
|
|
67
|
+
fail if that stops being true.
|
|
68
|
+
|
|
69
|
+
- The build targeted `node20` while the package requires `>=22`, which is the
|
|
70
|
+
floor better-sqlite3 sets -- on Node 20 it segfaults the moment a database
|
|
71
|
+
opens. Targeting lower was harmless but said the wrong thing about what this
|
|
72
|
+
supports, in the one place a reader could check. Now `node22`.
|
|
73
|
+
|
|
74
|
+
## 0.6.20 — 2026-09-15
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **The undo preview showed a write nobody confirmed as one that was
|
|
79
|
+
confirmed.** A tool call can land and then fail to say so -- the server
|
|
80
|
+
writes the record and times out, or answers with an error on its way out.
|
|
81
|
+
Synartesis already handled this correctly: it refuses to read `isError` as a
|
|
82
|
+
promise that nothing happened, reads the resource back, and records the
|
|
83
|
+
change so it stays recoverable. What it did not do was say so afterwards.
|
|
84
|
+
The row that came out of that path carried no trace of it at all -- the
|
|
85
|
+
refusal was written to the log, which nobody reads, at the moment it
|
|
86
|
+
happens, which is not the moment it matters -- so `undo --dry-run` printed
|
|
87
|
+
`revert ... state matches; applying inverse`, character for character what a
|
|
88
|
+
confirmed write prints. The row now records how it was established, and the
|
|
89
|
+
preview prints it under the step as `caveat`. Same in the app's
|
|
90
|
+
`preview_undo`, so a model cannot describe an inferred write to somebody as
|
|
91
|
+
a confirmed one.
|
|
92
|
+
|
|
93
|
+
- **A halt that named the consequence and hid the cause.** Where the transport
|
|
94
|
+
failed and the read-back proved the write had landed, undo correctly stopped
|
|
95
|
+
rather than reverting on an unverified assumption -- but said only "the
|
|
96
|
+
post-state was never captured, so drift could not be ruled out", which reads
|
|
97
|
+
as a missed reading. The reason there was no reading, which the row knew all
|
|
98
|
+
along, is now printed with it.
|
|
99
|
+
|
|
100
|
+
- An action whose outcome is unknown was told it got that way because "the
|
|
101
|
+
process died mid-call". A timeout, or an error the read-back could not
|
|
102
|
+
settle, arrives in exactly the same state and was told the same wrong story.
|
|
103
|
+
It now says what is actually known, and the row's own error says which of
|
|
104
|
+
them it was.
|
|
105
|
+
|
|
5
106
|
## 0.6.19 — 2026-09-15
|
|
6
107
|
|
|
7
108
|
### Fixed
|
package/README.md
CHANGED
|
@@ -333,10 +333,25 @@ cannot start a process:** see the [user guide](docs/synartesis-user-guide.md).
|
|
|
333
333
|
resolved when the call happens, so a mistake in a manifest is baked into every
|
|
334
334
|
run made under it. `undo --replan` rebuilds them from a corrected one.
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
byte-for-byte restoration, drift refusal,
|
|
338
|
-
|
|
339
|
-
|
|
336
|
+
Two bundled policies are tested against the real server, by making the change
|
|
337
|
+
and undoing it. **filesystem**: exact byte-for-byte restoration, drift refusal,
|
|
338
|
+
and absence told apart from a read that failed. **memory**: the graph is put
|
|
339
|
+
back as it was, entities the agent only tried to create are left alone, and a
|
|
340
|
+
delete of an entity is held rather than approximated. **git and github** are
|
|
341
|
+
checked only for tool existence — their recovery guarantees are not yet proven.
|
|
342
|
+
|
|
343
|
+
All three of filesystem, memory and git declare `provenance: live`, and that
|
|
344
|
+
word is narrower than it looks: it says the policy has met its server and the
|
|
345
|
+
tools take the arguments it passes them, not that undo has been round-tripped.
|
|
346
|
+
`synartesis check` says so under the server list rather than leaving `live` to
|
|
347
|
+
stand for both.
|
|
348
|
+
|
|
349
|
+
**A tool no policy mentions is irreversible and held for a person** the first
|
|
350
|
+
time it is called. That is the safe end of the trade, and it means a server
|
|
351
|
+
that gains a tool in an update does not quietly get a free pass — but it also
|
|
352
|
+
means an agent stopping mid-task on a call nobody expected. `synartesis check`
|
|
353
|
+
names every such tool, and the proxy warns about them at startup, so you can
|
|
354
|
+
write a policy before meeting one rather than after.
|
|
340
355
|
|
|
341
356
|
## Trust
|
|
342
357
|
|
|
@@ -1554,18 +1554,30 @@ function untested(manifest) {
|
|
|
1554
1554
|
function describeStanding(entry) {
|
|
1555
1555
|
switch (entry.provenance) {
|
|
1556
1556
|
case "live":
|
|
1557
|
-
return "
|
|
1557
|
+
return "shapes read from the real server";
|
|
1558
1558
|
case "documented":
|
|
1559
1559
|
return "written from documentation, never run against the real server";
|
|
1560
1560
|
case "unstated":
|
|
1561
1561
|
return "no claim either way";
|
|
1562
1562
|
}
|
|
1563
1563
|
}
|
|
1564
|
+
var LIVE_IS_NOT_RECOVERY = "`live` means the policy has met its server, not that undo has been round-tripped against it. Of the policies that ship, filesystem and memory have been; git and github have not.";
|
|
1564
1565
|
function warnUntested(servers) {
|
|
1565
1566
|
const names = servers.join(", ");
|
|
1566
1567
|
const these = servers.length === 1 ? "this policy has" : "these policies have";
|
|
1567
1568
|
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.`;
|
|
1568
1569
|
}
|
|
1570
|
+
function ungoverned(manifest, advertised) {
|
|
1571
|
+
const resolver = createPolicyResolver(manifest);
|
|
1572
|
+
const found = [];
|
|
1573
|
+
for (const server of [...advertised.keys()].sort()) {
|
|
1574
|
+
const tools = (advertised.get(server) ?? []).filter((tool) => !resolver.resolve(qualify(server, tool)).matched).sort();
|
|
1575
|
+
if (tools.length > 0) {
|
|
1576
|
+
found.push({ server, tools });
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
return found;
|
|
1580
|
+
}
|
|
1569
1581
|
|
|
1570
1582
|
// src/proxy/routing.ts
|
|
1571
1583
|
var SEPARATOR = "__";
|
|
@@ -1915,7 +1927,9 @@ export {
|
|
|
1915
1927
|
standing,
|
|
1916
1928
|
untested,
|
|
1917
1929
|
describeStanding,
|
|
1930
|
+
LIVE_IS_NOT_RECOVERY,
|
|
1918
1931
|
warnUntested,
|
|
1932
|
+
ungoverned,
|
|
1919
1933
|
IDEMPOTENCY_META_KEY,
|
|
1920
1934
|
withIdempotencyKey,
|
|
1921
1935
|
createRouter,
|
|
@@ -1930,4 +1944,4 @@ export {
|
|
|
1930
1944
|
observeState,
|
|
1931
1945
|
connectStdioUpstream
|
|
1932
1946
|
};
|
|
1933
|
-
//# sourceMappingURL=chunk-
|
|
1947
|
+
//# sourceMappingURL=chunk-JLLAK2QC.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IDEMPOTENCY_META_KEY,
|
|
4
|
+
LIVE_IS_NOT_RECOVERY,
|
|
4
5
|
NOTHING_RECORDED_YET,
|
|
5
6
|
PROXY_FLAGS,
|
|
6
7
|
WORDMARK,
|
|
@@ -32,11 +33,12 @@ import {
|
|
|
32
33
|
toPayload,
|
|
33
34
|
toResolvedRead,
|
|
34
35
|
toolShapes,
|
|
36
|
+
ungoverned,
|
|
35
37
|
untested,
|
|
36
38
|
verifyAgainstServers,
|
|
37
39
|
warnUntested,
|
|
38
40
|
wasRefused
|
|
39
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-JLLAK2QC.js";
|
|
40
42
|
import {
|
|
41
43
|
DriftConflict,
|
|
42
44
|
ManifestError,
|
|
@@ -375,7 +377,12 @@ function classify(action, replanning, goAhead) {
|
|
|
375
377
|
case "pending":
|
|
376
378
|
return {
|
|
377
379
|
kind: "halt",
|
|
378
|
-
|
|
380
|
+
// Not "the process died mid-call". That is one of the ways a row ends
|
|
381
|
+
// up here; a server that timed out, or answered with an error the
|
|
382
|
+
// read-back could not settle, arrives in exactly the same state and is
|
|
383
|
+
// told the same wrong story. The row's own error says which it was,
|
|
384
|
+
// and the halt prints it directly underneath.
|
|
385
|
+
reason: "outcome unknown: the call went out and its outcome was never established, so whether this applied cannot be determined",
|
|
379
386
|
verified: false
|
|
380
387
|
};
|
|
381
388
|
case "gated":
|
|
@@ -452,7 +459,14 @@ ${seen}` : seen;
|
|
|
452
459
|
if (!parsedPlan.success) {
|
|
453
460
|
const approved = action.approvedBy === void 0 ? "" : `, approved by ${action.approvedBy}`;
|
|
454
461
|
const reason = action.class === "irreversible" ? `cannot be undone${approved}; left in place` : `no usable inverse was recorded${action.error === void 0 ? "" : `: ${action.error}`}; left in place`;
|
|
455
|
-
|
|
462
|
+
const unconfirmed = action.class === "irreversible" ? caveat(action) : void 0;
|
|
463
|
+
steps.push({
|
|
464
|
+
...describeStep(action),
|
|
465
|
+
kind: "permanent",
|
|
466
|
+
reason,
|
|
467
|
+
verified: false,
|
|
468
|
+
...unconfirmed === void 0 ? {} : { note: unconfirmed }
|
|
469
|
+
});
|
|
456
470
|
leftInPlace = true;
|
|
457
471
|
continue;
|
|
458
472
|
}
|
|
@@ -517,13 +531,23 @@ ${seen}` : seen;
|
|
|
517
531
|
}
|
|
518
532
|
if (!verified && action.class === "reversible" && !force) {
|
|
519
533
|
const reason = unverifiedBecause(action);
|
|
534
|
+
const because = caveat(action);
|
|
520
535
|
halted = {
|
|
521
536
|
seq: action.seq,
|
|
522
537
|
reason,
|
|
523
|
-
detail:
|
|
538
|
+
detail: (because === void 0 ? "" : `${because}
|
|
539
|
+
|
|
540
|
+
`) + "Without it there is no way to tell this resource from one somebody has edited since, so the recorded value was not written.",
|
|
524
541
|
conflict: true
|
|
525
542
|
};
|
|
526
|
-
steps.push({
|
|
543
|
+
steps.push({
|
|
544
|
+
...describeStep(action),
|
|
545
|
+
kind: "halt",
|
|
546
|
+
reason,
|
|
547
|
+
verified: false,
|
|
548
|
+
plan,
|
|
549
|
+
...because === void 0 ? {} : { note: because }
|
|
550
|
+
});
|
|
527
551
|
break;
|
|
528
552
|
}
|
|
529
553
|
if (!verified && action.status === "rolling_back") {
|
|
@@ -539,13 +563,15 @@ ${seen}` : seen;
|
|
|
539
563
|
const after = intendedAfterInverse(action);
|
|
540
564
|
projected.set(resourceKey(toResolvedRead(verifyRead.data)), after ?? UNFORESEEABLE);
|
|
541
565
|
}
|
|
566
|
+
const recordedWith = caveat(action);
|
|
542
567
|
steps.push({
|
|
543
568
|
...describeStep(action),
|
|
544
569
|
kind: "revert",
|
|
545
570
|
reason: verified ? "state matches; applying inverse" : forcedOver ?? unverifiedBecause(action),
|
|
546
571
|
verified,
|
|
547
572
|
plan,
|
|
548
|
-
...rebuilt.inverse === void 0 ? {} : { replanned: true }
|
|
573
|
+
...rebuilt.inverse === void 0 ? {} : { replanned: true },
|
|
574
|
+
...recordedWith === void 0 ? {} : { note: recordedWith }
|
|
549
575
|
});
|
|
550
576
|
if (dryRun) {
|
|
551
577
|
continue;
|
|
@@ -613,6 +639,9 @@ ${seen}` : seen;
|
|
|
613
639
|
function unverifiedBecause(action) {
|
|
614
640
|
return action.verify === void 0 ? "no read declared for this tool, so drift could not be ruled out -- a `verify` read in its policy would give it one" : "the post-state was never captured, so drift could not be ruled out";
|
|
615
641
|
}
|
|
642
|
+
function caveat(action) {
|
|
643
|
+
return action.status === "applied" && action.error !== void 0 && action.error !== "" ? action.error : void 0;
|
|
644
|
+
}
|
|
616
645
|
function describeStep(action) {
|
|
617
646
|
return { seq: action.seq, server: action.server, tool: action.tool };
|
|
618
647
|
}
|
|
@@ -2922,6 +2951,7 @@ async function runCheck(argv) {
|
|
|
2922
2951
|
}
|
|
2923
2952
|
}
|
|
2924
2953
|
const upstreams = [];
|
|
2954
|
+
const offered = /* @__PURE__ */ new Map();
|
|
2925
2955
|
try {
|
|
2926
2956
|
for (const [name, spec] of Object.entries(manifest.servers)) {
|
|
2927
2957
|
upstreams.push(
|
|
@@ -2935,6 +2965,9 @@ async function runCheck(argv) {
|
|
|
2935
2965
|
);
|
|
2936
2966
|
}
|
|
2937
2967
|
await verifyAgainstServers(upstreams, manifest);
|
|
2968
|
+
for (const upstream of upstreams) {
|
|
2969
|
+
offered.set(upstream.name, (await toolShapes(upstream)).map((tool) => tool.name));
|
|
2970
|
+
}
|
|
2938
2971
|
} finally {
|
|
2939
2972
|
for (const upstream of upstreams) {
|
|
2940
2973
|
await upstream.close();
|
|
@@ -2956,6 +2989,11 @@ async function runCheck(argv) {
|
|
|
2956
2989
|
` ${style.quiet(" ")} ${style.strong(entry.server)} ${entry.provenance === "documented" ? style.accent(said) : style.quiet(said)}`
|
|
2957
2990
|
);
|
|
2958
2991
|
}
|
|
2992
|
+
if (standing(manifest).some((entry) => entry.provenance === "live")) {
|
|
2993
|
+
for (const line2 of wrapped(LIVE_IS_NOT_RECOVERY, 66)) {
|
|
2994
|
+
out(` ${style.quiet(" ")} ${style.quiet(line2)}`);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2959
2997
|
out(` ${style.quiet("policies")} ${[...counts].map(([k, v]) => `${String(v)} ${k}`).join(", ")}`);
|
|
2960
2998
|
out(` ${style.quiet("guarded ")} ${style.accent(String(gated))}`);
|
|
2961
2999
|
const pinned = Object.entries(manifest.pins ?? {});
|
|
@@ -2965,7 +3003,25 @@ async function runCheck(argv) {
|
|
|
2965
3003
|
` ${style.quiet("pinned ")} ${pinned.length === 0 ? style.quiet("nothing -- run `synartesis pin`") : pinned.map(([name, tools]) => `${name} (${String(Object.keys(tools).length)})`).join(", ") + (unpinned.length === 0 ? "" : style.quiet(`; not ${unpinned.join(", ")}`))}`
|
|
2966
3004
|
);
|
|
2967
3005
|
out("");
|
|
2968
|
-
|
|
3006
|
+
const uncovered = ungoverned(manifest, offered);
|
|
3007
|
+
if (uncovered.length === 0) {
|
|
3008
|
+
out(` ${style.quiet("Every tool these servers offer has a policy.")}`);
|
|
3009
|
+
} else {
|
|
3010
|
+
const total = uncovered.reduce((sum, entry) => sum + entry.tools.length, 0);
|
|
3011
|
+
out(
|
|
3012
|
+
` ${style.accent("guarded by default")} ${style.quiet(
|
|
3013
|
+
`${String(total)} tool${total === 1 ? "" : "s"} here ${total === 1 ? "has" : "have"} no policy, so ${total === 1 ? "it is" : "they are"} treated as`
|
|
3014
|
+
)}`
|
|
3015
|
+
);
|
|
3016
|
+
out(` ${style.quiet("irreversible and held for a person the first time an agent calls")}`);
|
|
3017
|
+
out(` ${style.quiet(`${total === 1 ? "it" : "one"}. Write a policy for any you would rather it got on with.`)}`);
|
|
3018
|
+
out("");
|
|
3019
|
+
for (const entry of uncovered) {
|
|
3020
|
+
for (const line2 of wrapped(entry.tools.join(", "), 60)) {
|
|
3021
|
+
out(` ${style.quiet(entry.server.padEnd(8))} ${style.strong(line2)}`);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
2969
3025
|
out("");
|
|
2970
3026
|
hint(
|
|
2971
3027
|
firstOf(
|
|
@@ -3783,6 +3839,11 @@ function report(result, alreadyForcing = false, as = "") {
|
|
|
3783
3839
|
out(
|
|
3784
3840
|
` ${style.quiet(String(step.seq).padStart(3))} ${kind} ${style.strong(`${step.server}.${step.tool}`)} ${style.quiet(step.reason)}${unverified}`
|
|
3785
3841
|
);
|
|
3842
|
+
if (step.note !== void 0 && step.kind !== "halt") {
|
|
3843
|
+
for (const [at, line2] of wrapped(step.note, 62).entries()) {
|
|
3844
|
+
out(` ${at === 0 ? style.accent("caveat") : " "} ${style.quiet(line2)}`);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3786
3847
|
if (step.plan !== void 0 && step.kind === "revert") {
|
|
3787
3848
|
const verb = `${step.replanned === true ? "replanned, " : ""}${result.dryRun ? "would call" : "called"}`;
|
|
3788
3849
|
out(
|
package/dist/proxy.js
CHANGED
|
@@ -19,11 +19,13 @@ import {
|
|
|
19
19
|
refusal,
|
|
20
20
|
runRead,
|
|
21
21
|
toPayload,
|
|
22
|
+
toolShapes,
|
|
23
|
+
ungoverned,
|
|
22
24
|
untested,
|
|
23
25
|
verifyAgainstServers,
|
|
24
26
|
warnUntested,
|
|
25
27
|
withIdempotencyKey
|
|
26
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-JLLAK2QC.js";
|
|
27
29
|
import {
|
|
28
30
|
SnapshotError,
|
|
29
31
|
UpstreamError,
|
|
@@ -755,6 +757,7 @@ function createProxyServer(options) {
|
|
|
755
757
|
const result = await route.upstream.client.request(forwarded, PassthroughResult, {
|
|
756
758
|
signal: extra.signal
|
|
757
759
|
});
|
|
760
|
+
let inferred;
|
|
758
761
|
const refused = refusal(result);
|
|
759
762
|
if (refused !== void 0) {
|
|
760
763
|
const settled = policy.refusal === "clean" ? "none" : await whatHappened(router, probe, priorState, extra.signal);
|
|
@@ -774,6 +777,7 @@ function createProxyServer(options) {
|
|
|
774
777
|
);
|
|
775
778
|
return result;
|
|
776
779
|
}
|
|
780
|
+
inferred = `the upstream answered with an error and the change was established by reading the resource back, so nothing confirmed it: ${refused}`;
|
|
777
781
|
log?.warn(
|
|
778
782
|
{ seq: pending.seq, tool: route.tool, reason: refused },
|
|
779
783
|
"the upstream reported an error after changing the resource"
|
|
@@ -781,6 +785,9 @@ function createProxyServer(options) {
|
|
|
781
785
|
}
|
|
782
786
|
const context = { args, snapshot, result: toPayload(result) };
|
|
783
787
|
const warnings = [];
|
|
788
|
+
if (inferred !== void 0) {
|
|
789
|
+
warnings.push(inferred);
|
|
790
|
+
}
|
|
784
791
|
if (missingPriorState !== void 0) {
|
|
785
792
|
warnings.push(
|
|
786
793
|
`no prior state existed, so there is nothing to restore: ${missingPriorState}`
|
|
@@ -1165,12 +1172,26 @@ async function main() {
|
|
|
1165
1172
|
tools: manifest.tools.filter((rule) => rule.match.startsWith(`${String(argv.server)}.`))
|
|
1166
1173
|
}
|
|
1167
1174
|
);
|
|
1175
|
+
const uncovered = ungoverned(
|
|
1176
|
+
manifest,
|
|
1177
|
+
new Map(await Promise.all(upstreams.map(async (upstream) => [
|
|
1178
|
+
upstream.name,
|
|
1179
|
+
(await toolShapes(upstream)).map((tool) => tool.name)
|
|
1180
|
+
])))
|
|
1181
|
+
);
|
|
1182
|
+
for (const entry of uncovered) {
|
|
1183
|
+
log.warn(
|
|
1184
|
+
{ server: entry.server, tools: entry.tools },
|
|
1185
|
+
"no policy covers these tools; they will be held for approval when called"
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1168
1188
|
log.info(
|
|
1169
1189
|
{
|
|
1170
1190
|
manifest: argv.manifest,
|
|
1171
1191
|
journal: argv.journal,
|
|
1172
1192
|
servers: upstreams.map((upstream) => upstream.name),
|
|
1173
|
-
policies: manifest.tools.length
|
|
1193
|
+
policies: manifest.tools.length,
|
|
1194
|
+
ungoverned: uncovered.reduce((sum, entry) => sum + entry.tools.length, 0)
|
|
1174
1195
|
},
|
|
1175
1196
|
"proxy ready"
|
|
1176
1197
|
);
|
package/manifests/memory.yaml
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Policy for @modelcontextprotocol/server-memory, the knowledge graph an agent
|
|
2
2
|
# keeps about you between sessions.
|
|
3
3
|
#
|
|
4
|
-
# Verified against the real server: every tool named here was listed
|
|
5
|
-
# the shapes below were read off its own responses rather than from
|
|
4
|
+
# Verified against the real server twice over: every tool named here was listed
|
|
5
|
+
# by it and the shapes below were read off its own responses rather than from
|
|
6
|
+
# docs -- and then the undo itself was run. tests/adapter-memory.test.ts makes
|
|
7
|
+
# each change against a real graph, undoes it, and compares the file: what the
|
|
8
|
+
# agent added is gone, what was already there is untouched, an entity it only
|
|
9
|
+
# tried to create is left alone, and a delete of an entity is held rather than
|
|
10
|
+
# approximated. Every one of those was checked by breaking this policy and
|
|
11
|
+
# watching the test fail.
|
|
6
12
|
#
|
|
7
13
|
# This is the server the taxonomy fits best. Almost everything it does has an
|
|
8
14
|
# exact opposite sitting next to it in the same toolbox, which is what
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "synartesis",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.22",
|
|
4
4
|
"description": "An undo layer for AI agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
"@fontsource/ibm-plex-sans": "^5.3.0",
|
|
87
87
|
"@google/genai": "^2.22.0",
|
|
88
88
|
"@modelcontextprotocol/server-filesystem": "^2026.7.10",
|
|
89
|
+
"@modelcontextprotocol/server-memory": "^2026.8.31",
|
|
89
90
|
"@types/better-sqlite3": "^9.6.0",
|
|
90
91
|
"@types/node": "^26.2.0",
|
|
91
92
|
"@types/react": "^19.3.0",
|