synartesis 0.6.20 → 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 +69 -0
- package/README.md +19 -4
- package/dist/{chunk-SZHVLCJR.js → chunk-JLLAK2QC.js} +16 -2
- package/dist/cli.js +31 -2
- package/dist/proxy.js +18 -2
- package/manifests/memory.yaml +8 -2
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,75 @@
|
|
|
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
|
+
|
|
5
74
|
## 0.6.20 — 2026-09-15
|
|
6
75
|
|
|
7
76
|
### 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,
|
|
@@ -2949,6 +2951,7 @@ async function runCheck(argv) {
|
|
|
2949
2951
|
}
|
|
2950
2952
|
}
|
|
2951
2953
|
const upstreams = [];
|
|
2954
|
+
const offered = /* @__PURE__ */ new Map();
|
|
2952
2955
|
try {
|
|
2953
2956
|
for (const [name, spec] of Object.entries(manifest.servers)) {
|
|
2954
2957
|
upstreams.push(
|
|
@@ -2962,6 +2965,9 @@ async function runCheck(argv) {
|
|
|
2962
2965
|
);
|
|
2963
2966
|
}
|
|
2964
2967
|
await verifyAgainstServers(upstreams, manifest);
|
|
2968
|
+
for (const upstream of upstreams) {
|
|
2969
|
+
offered.set(upstream.name, (await toolShapes(upstream)).map((tool) => tool.name));
|
|
2970
|
+
}
|
|
2965
2971
|
} finally {
|
|
2966
2972
|
for (const upstream of upstreams) {
|
|
2967
2973
|
await upstream.close();
|
|
@@ -2983,6 +2989,11 @@ async function runCheck(argv) {
|
|
|
2983
2989
|
` ${style.quiet(" ")} ${style.strong(entry.server)} ${entry.provenance === "documented" ? style.accent(said) : style.quiet(said)}`
|
|
2984
2990
|
);
|
|
2985
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
|
+
}
|
|
2986
2997
|
out(` ${style.quiet("policies")} ${[...counts].map(([k, v]) => `${String(v)} ${k}`).join(", ")}`);
|
|
2987
2998
|
out(` ${style.quiet("guarded ")} ${style.accent(String(gated))}`);
|
|
2988
2999
|
const pinned = Object.entries(manifest.pins ?? {});
|
|
@@ -2992,7 +3003,25 @@ async function runCheck(argv) {
|
|
|
2992
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(", ")}`))}`
|
|
2993
3004
|
);
|
|
2994
3005
|
out("");
|
|
2995
|
-
|
|
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
|
+
}
|
|
2996
3025
|
out("");
|
|
2997
3026
|
hint(
|
|
2998
3027
|
firstOf(
|
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,
|
|
@@ -1170,12 +1172,26 @@ async function main() {
|
|
|
1170
1172
|
tools: manifest.tools.filter((rule) => rule.match.startsWith(`${String(argv.server)}.`))
|
|
1171
1173
|
}
|
|
1172
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
|
+
}
|
|
1173
1188
|
log.info(
|
|
1174
1189
|
{
|
|
1175
1190
|
manifest: argv.manifest,
|
|
1176
1191
|
journal: argv.journal,
|
|
1177
1192
|
servers: upstreams.map((upstream) => upstream.name),
|
|
1178
|
-
policies: manifest.tools.length
|
|
1193
|
+
policies: manifest.tools.length,
|
|
1194
|
+
ungoverned: uncovered.reduce((sum, entry) => sum + entry.tools.length, 0)
|
|
1179
1195
|
},
|
|
1180
1196
|
"proxy ready"
|
|
1181
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",
|