synartesis 0.6.12 → 0.6.15

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/dist/proxy.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ PROXY_FLAGS,
3
4
  canonical,
4
5
  cliCommandFrom,
5
6
  connectStdioUpstream,
@@ -18,8 +19,11 @@ import {
18
19
  refusal,
19
20
  runRead,
20
21
  toPayload,
21
- verifyAgainstServers
22
- } from "./chunk-FUVEHRJP.js";
22
+ untested,
23
+ verifyAgainstServers,
24
+ warnUntested,
25
+ withIdempotencyKey
26
+ } from "./chunk-OODDXM36.js";
23
27
  import {
24
28
  SnapshotError,
25
29
  UpstreamError,
@@ -582,14 +586,21 @@ function createProxyServer(options) {
582
586
  enter();
583
587
  try {
584
588
  const wantsGate = policy.gate === "always" || policy.gate === "on_write" && shouldGateOnWrite(args);
585
- const granted = wantsGate ? journal.findApproval({
589
+ const unresolved = policy.class === "readonly" ? void 0 : journal.findPending({
590
+ runId: activeRun,
591
+ server: route.upstream.name,
592
+ tool: route.tool,
593
+ args
594
+ });
595
+ const mustAsk = wantsGate || unresolved !== void 0;
596
+ const granted = mustAsk ? journal.findApproval({
586
597
  server: route.upstream.name,
587
598
  tool: route.tool,
588
599
  args,
589
600
  notBefore: new Date(Date.now() - APPROVAL_WINDOW_MS).toISOString()
590
601
  }) : void 0;
591
602
  const inherited = granted !== void 0 && granted.runId !== activeRun ? granted : void 0;
592
- const waiting = granted === void 0 && wantsGate ? journal.findGated({
603
+ const waiting = granted === void 0 && mustAsk ? journal.findGated({
593
604
  runId: activeRun,
594
605
  server: route.upstream.name,
595
606
  tool: route.tool,
@@ -673,9 +684,13 @@ function createProxyServer(options) {
673
684
  );
674
685
  }
675
686
  };
676
- const askedAlready = wantsGate;
677
- if (wantsGate && (granted === void 0 || !spent)) {
678
- await decide("this action cannot be undone");
687
+ const askedAlready = mustAsk;
688
+ if (mustAsk && (granted === void 0 || !spent)) {
689
+ const because = [
690
+ unresolved === void 0 ? void 0 : `an earlier attempt at this exact call, action ${String(unresolved.seq)}, went out and its outcome could never be established, so sending it again may apply the change a second time`,
691
+ wantsGate ? "this action cannot be undone" : void 0
692
+ ].filter((one) => one !== void 0);
693
+ await decide(because.join(", and "));
679
694
  }
680
695
  let snapshot;
681
696
  let verify;
@@ -730,7 +745,11 @@ function createProxyServer(options) {
730
745
  }
731
746
  const forwarded = {
732
747
  method: "tools/call",
733
- params: { ...request.params, name: route.tool }
748
+ params: {
749
+ ...request.params,
750
+ name: route.tool,
751
+ _meta: withIdempotencyKey(request.params._meta, pending.idempotencyKey)
752
+ }
734
753
  };
735
754
  try {
736
755
  const result = await route.upstream.client.request(forwarded, PassthroughResult, {
@@ -775,6 +794,13 @@ function createProxyServer(options) {
775
794
  warnings.push(`inverse could not be resolved: ${describe(error)}`);
776
795
  }
777
796
  }
797
+ if (verify === void 0 && policy.verify !== void 0) {
798
+ try {
799
+ verify = planRead(policy.verify, context);
800
+ } catch (error) {
801
+ warnings.push(`the drift check could not be planned: ${describe(error)}`);
802
+ }
803
+ }
778
804
  let postSnapshot;
779
805
  if (verify !== void 0) {
780
806
  try {
@@ -1048,7 +1074,7 @@ function parseArgv(argv) {
1048
1074
  const at = argv.indexOf(flag);
1049
1075
  return at === -1 ? void 0 : argv[at + 1];
1050
1076
  };
1051
- const known = ["--manifest", "--journal", "--server", "--gate-timeout", "--log-level", "--http", "--http-host", "--http-idle", "--token"];
1077
+ const known = PROXY_FLAGS;
1052
1078
  const unknown = argv.find((token) => token.startsWith("--") && !known.includes(token));
1053
1079
  if (unknown !== void 0) {
1054
1080
  throw new Error(`unknown flag ${unknown}; expected one of ${known.join(", ")}`);
@@ -1109,6 +1135,10 @@ async function main() {
1109
1135
  process.stderr.write(mark());
1110
1136
  }
1111
1137
  const manifest = loadManifest(argv.manifest);
1138
+ const unproven = untested(manifest);
1139
+ if (unproven.length > 0) {
1140
+ log.warn({ servers: unproven }, warnUntested(unproven));
1141
+ }
1112
1142
  const journal = openJournal(argv.journal);
1113
1143
  const declared = Object.entries(manifest.servers);
1114
1144
  const wanted = argv.server === void 0 ? declared : declared.filter(([name]) => name === argv.server);
@@ -14,6 +14,7 @@ servers:
14
14
  fs:
15
15
  command: node
16
16
  args: ["node_modules/@modelcontextprotocol/server-filesystem/dist/index.js", "."]
17
+ provenance: live
17
18
 
18
19
  tools:
19
20
  # --- reads -------------------------------------------------------------
@@ -105,3 +106,10 @@ tools:
105
106
  - match: "fs.create_directory"
106
107
  class: irreversible
107
108
  gate: always
109
+
110
+ # This policy is written for the server version named at the top. A later
111
+ # version can keep a tool's name and change what it takes, which would leave
112
+ # the policy above describing something else -- and undo would still produce an
113
+ # answer. `synartesis pin` prints a `pins:` block for the server you actually
114
+ # have; with it pasted in, a tool whose shape has moved stops the proxy at
115
+ # startup instead of being quietly trusted.
@@ -15,6 +15,7 @@ servers:
15
15
  git:
16
16
  command: uvx
17
17
  args: ["--from", "mcp-server-git", "mcp-server-git"]
18
+ provenance: live
18
19
 
19
20
  tools:
20
21
  # --- reads -------------------------------------------------------------
@@ -75,3 +76,10 @@ tools:
75
76
  - match: "git.git_checkout"
76
77
  class: irreversible
77
78
  gate: never
79
+
80
+ # This policy is written for the server version named at the top. A later
81
+ # version can keep a tool's name and change what it takes, which would leave
82
+ # the policy above describing something else -- and undo would still produce an
83
+ # answer. `synartesis pin` prints a `pins:` block for the server you actually
84
+ # have; with it pasted in, a tool whose shape has moved stops the proxy at
85
+ # startup instead of being quietly trusted.
@@ -24,6 +24,9 @@ servers:
24
24
  # Read from your shell; never written into this file.
25
25
  env:
26
26
  GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}"
27
+ # Nothing here has met a real github; every shape below comes from
28
+ # documentation. That is a weaker claim than the others make.
29
+ provenance: documented
27
30
 
28
31
  tools:
29
32
  # --- reads -------------------------------------------------------------
@@ -188,3 +191,10 @@ tools:
188
191
  - match: "github.create_release"
189
192
  class: irreversible
190
193
  gate: always
194
+
195
+ # This policy is written for the server version named at the top. A later
196
+ # version can keep a tool's name and change what it takes, which would leave
197
+ # the policy above describing something else -- and undo would still produce an
198
+ # answer. `synartesis pin` prints a `pins:` block for the server you actually
199
+ # have; with it pasted in, a tool whose shape has moved stops the proxy at
200
+ # startup instead of being quietly trusted.
@@ -18,6 +18,7 @@ servers:
18
18
  # or the agent will remember into a different file than it reads from.
19
19
  env:
20
20
  MEMORY_FILE_PATH: "${MEMORY_FILE_PATH}"
21
+ provenance: live
21
22
 
22
23
  tools:
23
24
  # --- reads -------------------------------------------------------------
@@ -54,6 +55,16 @@ tools:
54
55
  tool: "memory.delete_entities"
55
56
  args:
56
57
  entityNames: "$result.entities[].name"
58
+ # Deleting an entity takes its observations and its relations with it. So
59
+ # the compensating delete is only safe while the entity is still the thing
60
+ # the agent made: if anybody has added to it since, undoing destroys work
61
+ # nobody recorded. There is no pre-read to compare against -- the entity
62
+ # did not exist before the call -- so this reads it back afterwards, and
63
+ # undo halts rather than deleting over somebody.
64
+ verify:
65
+ tool: "memory.open_nodes"
66
+ args:
67
+ names: "$result.entities[].name"
57
68
 
58
69
  - match: "memory.create_relations"
59
70
  class: compensable
@@ -94,3 +105,10 @@ tools:
94
105
  - match: "memory.delete_observations"
95
106
  class: irreversible
96
107
  gate: always
108
+
109
+ # This policy is written for the server version named at the top. A later
110
+ # version can keep a tool's name and change what it takes, which would leave
111
+ # the policy above describing something else -- and undo would still produce an
112
+ # answer. `synartesis pin` prints a `pins:` block for the server you actually
113
+ # have; with it pasted in, a tool whose shape has moved stops the proxy at
114
+ # startup instead of being quietly trusted.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synartesis",
3
- "version": "0.6.12",
3
+ "version": "0.6.15",
4
4
  "description": "An undo layer for AI agents.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -74,7 +74,8 @@
74
74
  "overrides": {
75
75
  "fast-uri": "^3.1.7",
76
76
  "hono": "^4.13.7",
77
- "qs": "^6.16.0"
77
+ "qs": "^6.16.0",
78
+ "esbuild": "^0.28.1"
78
79
  }
79
80
  },
80
81
  "devDependencies": {