sparda-mcp 0.13.1 → 0.13.3

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 CHANGED
@@ -10,27 +10,33 @@
10
10
 
11
11
  ---
12
12
 
13
- **Compile your backend into a deterministic behavior runtime.**
13
+ **A compiler for backend behavior. The LLVM of web applications.**
14
14
 
15
- For twenty years, software communicated via APIs. Then AI agents arrived. The industry's response was simply to expose more endpoints (MCP). But an agent doesn't understand your application; it only sees a list of disconnected tools. That is enough for a human, but not for an autonomous machine.
15
+ For twenty years software communicated through APIs. Then AI agents arrived, and the industry's answer was to expose more endpoints (MCP). But an agent doesn't *understand* an application from a list of disconnected tools and neither can a linter, a debugger, or a deploy gate reason about a codebase they only read line by line.
16
16
 
17
- SPARDA analyzes your Express, FastAPI, or Next.js application, extracts its behavior, and compiles it into a **SPARDA Behavior Graph (SBG)**.
17
+ SPARDA compiles your backend — routes, database queries, state mutations, permissions, side-effects into one language-agnostic, mathematical graph: the **Unified Behavior Graph (UBG)**, serialized as `.sparda/ubg.json` under the **SBIR** specification ([SPARDA Behavior IR](docs/SBIR_SPEC_V1.1.md)). Compile once; then every tool is a simple pass over that graph.
18
18
 
19
- This graph acts as a local, deterministic runtime that powers:
20
- * **MCP Server Integration**: Exposing your endpoints to AI clients dynamically.
21
- * **SPARDA Twin**: An executable, offline simulation clone of your backend for safe agent testing.
22
- * **SPARDA Immune**: Real-time graph-based anomaly defense, latency alerts, and auto-quarantine.
23
- * **SPARDA Evolution**: Statically inferred grammar mapping and in-memory workflow optimization.
19
+ **What the graph unlocks 100% local, deterministic, zero runtime dependencies, zero API key:**
24
20
 
25
- All executed 100% locally, with zero runtime dependencies and zero cloud connection.
21
+ | Command | What it does |
22
+ |---|---|
23
+ | **`ubg`** | Compile the codebase to its behavior graph (Express · FastAPI · Next.js natively; **any** stack via OpenAPI) |
24
+ | **`apocalypse`** | *Prove the deploy* — no guard, invariant, transaction or aggregate boundary can be broken (SARIF + CI gate) |
25
+ | **`timeless`** | *Time-travel* — record a production request, replay it byte-identically, export the bug as a test |
26
+ | **`heal`** | *Self-heal, proven* — bug → fix → the machine proves the fix is correct and breaks nothing |
27
+ | **`mirror`** | *Execute the graph* — serve the compiled behavior over HTTP with no framework and no source |
28
+ | **`openapi`** | *Emit the standard* — produce an OpenAPI 3.1 spec from the graph |
29
+ | **`verify`** | *Prove the compiler's own laws* (determinism, soundness, round-trip) on your app |
30
+ | **`init` / `dev`** | Expose the graph to AI clients as a live MCP server (+ Twin, Immune, Evolution runtime layer) |
26
31
 
27
32
  ```bash
28
- npx sparda-mcp init # Scan your app, compile the UBG, inject the SPARDA Runtime
29
- npx sparda-mcp dev # Connect Claude Desktop / Cursor over stdio
33
+ npx sparda-mcp ubg # compile your backend .sparda/ubg.json
34
+ npx sparda-mcp apocalypse # prove the current tree is safe to deploy
30
35
  ```
31
36
 
32
- No OpenAPI spec to write. No cloud account. No API key. No server to host.
33
- Exposing raw APIs to AI is the old way. SPARDA compiles the whole system's behavior.
37
+ No cloud account. No server to host. Exposing raw APIs to AI is the old way — SPARDA compiles the whole system's behavior, then proves, replays, heals, and serves it.
38
+
39
+ **Nomenclature:** **SBIR** is the specification (the format, like "JSON"); **UBG** is the compiled graph itself (the artifact, `ubg.json`). The MCP server is one *output* of the graph, not the product.
34
40
 
35
41
  ## Quickstart
36
42
 
@@ -68,7 +74,7 @@ To see SPARDA in action instantly without modifying your codebase:
68
74
  ```bash
69
75
  npx sparda-mcp demo
70
76
  ```
71
- This runs the entire lifecycle (detect → parse → generate → inject → remove) on a bundled demo app in a temporary folder, illustrating all six guarantees in 10 seconds.
77
+ This runs the entire MCP lifecycle (detect → parse → generate → inject → remove) on a bundled demo app in a temporary folder, in about 10 seconds. For the compiler itself, run `npx sparda-mcp ubg` then `apocalypse` on any Express/FastAPI app.
72
78
 
73
79
  ## Black Box Report
74
80
 
@@ -139,6 +145,28 @@ const db = box.wrapClient(pgPool); // your query client, tapped
139
145
 
140
146
  The closed loop nobody else has: **production bug → recorded flight → failing test → AI writes the fix → `apocalypse` proves the fix breaks no guard, invariant or transaction → deploy.** Replay is per-request (concurrent-race capture is out of scope for v1 — stated, not hidden).
141
147
 
148
+ ## Self-Healing, Proven: `sparda heal`
149
+
150
+ The loop above, as **one gesture** — and the machine judges the fix, whoever wrote it:
151
+
152
+ ```bash
153
+ npx sparda-mcp heal <flightId> # diagnose + write the fix brief
154
+ # ...apply the fix (a human, or --agent "your-ai-cli")...
155
+ npx sparda-mcp heal <flightId> --check --expect '{"status":404}'
156
+ ```
157
+
158
+ The brief is built from the graph itself — it hands the fixer the handler's `file:line`, the capabilities the fix must not grow, and the guards it must not remove. Then the **gate** — the actual product — proves the fix on three axes at once:
159
+
160
+ 1. **Behavior** — lenient replay of the recorded flight (same deterministic inputs) now produces the *expected* response, not the recorded bug. The fix may reformulate a query (the tap is relabeled, allowed); it may **not** change the effect order or kinds.
161
+ 2. **Compiler laws** — `verify` still passes: the graph is still sound and deterministic.
162
+ 3. **No regression** — `apocalypse` diff against the frozen pre-fix graph: zero new critical/high findings, no guard removed, no blast radius grown.
163
+
164
+ ```
165
+ ✓ HEALED & PROVEN — same recorded inputs, correct output, zero law broken, zero protection lost. Ship it.
166
+ ```
167
+
168
+ The gate is honest in both directions: an unfixed bug, or a "fix" that silently drops a guard, keeps it **closed** (exit 1). This is the difference between an AI that writes plausible code and a system that *proves* the code is correct — the trust layer the agent era is missing.
169
+
142
170
  ## Any Backend On Earth: OpenAPI Lowering
143
171
 
144
172
  SPARDA parses Express, FastAPI and Next.js natively — and **every other stack through the format the industry already agreed on**. Go, Java, Rails, Laravel, .NET: if it has an OpenAPI spec, it compiles.
package/SKILL.md CHANGED
@@ -2,8 +2,8 @@
2
2
  name: sparda-mcp
3
3
  description: >-
4
4
  Drive a SPARDA compiled Behavior Runtime to its full potential. Use this whenever
5
- you are connected to a backend running the SPARDA Runtime (compiled into a SPARDA
6
- Behavior Graph - SBG) — recognizable by the tools `sparda_get_context`, `sparda_info`,
5
+ you are connected to a backend running the SPARDA Runtime (compiled into a Unified
6
+ Behavior Graph - UBG) — recognizable by the tools `sparda_get_context`, `sparda_info`,
7
7
  `sparda_confirm`, or composite tools. It teaches the graph-context-first workflow,
8
8
  how to exploit the response-recycling flywheel, the circuit-breaker, crystallized
9
9
  circuits, offline Twin simulations, and the two-phase confirm protocol for writes.
@@ -11,7 +11,9 @@ description: >-
11
11
 
12
12
  # Driving a SPARDA Behavior Runtime
13
13
 
14
- A SPARDA server is driven by a compiled **SPARDA Behavior Graph (SBG)**. Instead of exposing raw, disconnected endpoints, SPARDA has compiled the host application's states, transitions, permissions, and side-effects into a deterministic behavioral model. The local **SPARDA Runtime** dynamically executes this graph inside the live host process, powering the MCP interface, the Twin simulation clone, and the Immune system offline.
14
+ A SPARDA server is driven by a compiled **Unified Behavior Graph (UBG)** the graph SPARDA's compiler produces from the host application's states, transitions, permissions, and side-effects, serialized under the **SBIR** specification. Instead of exposing raw, disconnected endpoints, SPARDA compiles the app into a deterministic behavioral model. The local **SPARDA Runtime** dynamically executes this graph inside the live host process, powering the MCP interface, the Twin simulation clone, and the Immune system offline.
15
+
16
+ > This skill covers the **runtime** (driving a live MCP server). The same graph also powers dev-time compiler commands you run in the app's repo — `sparda ubg` (compile), `apocalypse` (prove the deploy), `timeless` (record/replay a request), `heal` (prove a fix), `mirror` (serve the graph), `verify` (prove the compiler's laws). Those are CLI, not MCP tools; see the project README.
15
17
 
16
18
  ## Rule 0 — call `sparda_get_context` first, every session
17
19
 
@@ -137,10 +139,12 @@ Writes are **disabled by default**. The protocol is not optional:
137
139
  manifest validity, the semantic/immune cache, host reachability, and quarantine;
138
140
  it exits non-zero so it can gate CI.
139
141
  - **Formal Deployment Proof** → `sparda apocalypse` reads the compiled graph (`ubg.json`) and proves five correctness obligations: catches unguarded mutations, non-atomic aggregate writes, unvalidated writes to constrained tables, uncompensated observable effects, and aggregate root bypasses. Run `sparda apocalypse --save-baseline` to store the reference graph; subsequent runs diff against the baseline to catch dropped guards, dropped SQL invariants, or grown blast radiuses.
140
- - **OpenAPI Ingestion** → Run `sparda ubg --openapi <openapi_spec.json_or_yaml>` to compile any non-JS/Python backend (Go, Java, Rails, Laravel, .NET) into a SPARDA Behavior Graph by mapping security schemes into guards and request/response structures.
142
+ - **OpenAPI Ingestion** → Run `sparda ubg --openapi <openapi_spec.json>` to compile any non-JS/Python backend (Go, Java, Rails, Laravel, .NET) into a Unified Behavior Graph by mapping security schemes into guards and request/response structures. (JSON specs only — convert YAML once with `npx -y js-yaml spec.yaml > spec.json`.)
141
143
  - **Executing the Graph (No code mock)** → Run `sparda mirror` to host a mock HTTP simulation server directly from `ubg.json` without any backend code. Enforces authentication guards, returns typed responses, and acts as a contract sandbox.
142
144
  - **Exporting OpenAPI 3.1 Spec** → Run `sparda openapi` to generate a valid, deterministic OpenAPI 3.1 spec dynamically from the compiled behavior graph.
143
145
  - **Self-Verification** → Run `sparda verify` to test the compiler's own invariants (byte-determinism, soundness, and spec round-trip) to guarantee trust.
146
+ - **Time-Travel Debugging** → `sparda timeless` records a production request's nondeterminism (db, http, clock, random, uuid) and replays it byte-identically against current code. `sparda timeless replay <id>` re-flies it; `sparda timeless export <id>` turns the bug into a vitest test. Recording is opt-in in the app (deterministic sampling + GDPR redaction built in).
147
+ - **Self-Healing, Proven** → `sparda heal <id>` builds a fix brief from the graph, then `--check --expect '{"status":200}'` gates a candidate fix on three axes at once: the recorded flight now returns the expected response (not the bug), `verify` still passes, and `apocalypse` finds no new critical/high and no removed guard. The gate is honest both ways — an unfixed bug keeps it closed (exit 1).
144
148
  - **Clone learning / Transfer sémantique** → Use `sparda seed export` to package your app's descriptions, workflows, and antibodies. Then `sparda seed import --germinate` in another clone to import the structure and germinate simulated twin instances.
145
149
  - **Learn exemplars** → Start your live app and run `sparda twin --learn` to fetch actual response data and construct `.sparda/twin.json` locally.
146
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparda-mcp",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "mcpName": "io.github.zyx77550/sparda-mcp",
5
5
  "description": "Compile backends to behavior graphs. Statically prove security, simulate APIs, and replay bugs.",
6
6
  "type": "module",
package/src/flight/box.js CHANGED
@@ -29,8 +29,11 @@ export function createFlightBox() {
29
29
  const als = new AsyncLocalStorage();
30
30
  const originals = {};
31
31
  let armed = false;
32
- let insideUUID = false;
33
- let insideFetch = false;
32
+ // Node 18: fetch (undici) and crypto.randomUUID call Date.now()/Math.random()
33
+ // internally those must NOT be recorded as entropy taps. The suppression
34
+ // flag lives ON THE STORE (per-request), not on the box: a global flag would
35
+ // let one request's fetch window swallow a concurrent request's entropy taps
36
+ // and silently corrupt its flight.
34
37
 
35
38
  function arm() {
36
39
  if (armed) return;
@@ -42,7 +45,7 @@ export function createFlightBox() {
42
45
 
43
46
  Date.now = function spardaDateNow() {
44
47
  const store = als.getStore();
45
- if (!store || insideUUID || insideFetch) return originals.dateNow();
48
+ if (!store || store.suppressEntropy) return originals.dateNow();
46
49
  if (store.mode === 'record') {
47
50
  const v = originals.dateNow();
48
51
  tapOut(store, 'time', 'Date.now', v);
@@ -53,7 +56,7 @@ export function createFlightBox() {
53
56
 
54
57
  Math.random = function spardaRandom() {
55
58
  const store = als.getStore();
56
- if (!store || insideUUID || insideFetch) return originals.random();
59
+ if (!store || store.suppressEntropy) return originals.random();
57
60
  if (store.mode === 'record') {
58
61
  const v = originals.random();
59
62
  tapOut(store, 'random', 'Math.random', v);
@@ -69,13 +72,13 @@ export function createFlightBox() {
69
72
  const store = als.getStore();
70
73
  if (!store) return originals.randomUUID();
71
74
  if (store.mode === 'record') {
72
- insideUUID = true;
75
+ store.suppressEntropy = true;
73
76
  try {
74
77
  const v = originals.randomUUID();
75
78
  tapOut(store, 'uuid', 'crypto.randomUUID', v);
76
79
  return v;
77
80
  } finally {
78
- insideUUID = false;
81
+ store.suppressEntropy = false;
79
82
  }
80
83
  }
81
84
  return takeTap(store, 'uuid', 'crypto.randomUUID');
@@ -90,7 +93,7 @@ export function createFlightBox() {
90
93
  const method = (init?.method ?? 'GET').toUpperCase();
91
94
  const label = `${method} ${url}`;
92
95
  if (store.mode === 'record') {
93
- insideFetch = true;
96
+ store.suppressEntropy = true;
94
97
  try {
95
98
  const res = await originals.fetch(input, init);
96
99
  const text = (await res.text()).slice(0, MAX_BODY_BYTES);
@@ -98,7 +101,7 @@ export function createFlightBox() {
98
101
  tapOut(store, 'http', label, { status: res.status, headers, body: text });
99
102
  return new Response(text, { status: res.status, headers });
100
103
  } finally {
101
- insideFetch = false;
104
+ store.suppressEntropy = false;
102
105
  }
103
106
  }
104
107
  const rec = takeTap(store, 'http', label);
@@ -102,7 +102,12 @@ export function extractOpenAPI(cwd, specPath) {
102
102
  function opSecurity(op, globalSecurity, known) {
103
103
  const local = op.security ? op.security.flatMap((s) => Object.keys(s)) : null;
104
104
  const active = local ?? globalSecurity;
105
- return [...new Set(active.filter((s) => known.includes(s) || active.length))].sort();
105
+ // keep referenced schemes that are actually declared in components; if a
106
+ // spec references a scheme it never defined it is malformed, but the intent
107
+ // to protect the endpoint is clear — fall back to the raw references then.
108
+ // (The previous `|| active.length` made the filter a no-op — a real bug.)
109
+ const declared = active.filter((s) => known.includes(s));
110
+ return [...new Set(declared.length ? declared : active)].sort();
106
111
  }
107
112
 
108
113
  function paramsOf(op, item, rawPath) {
package/src/ubg/verify.js CHANGED
@@ -36,10 +36,17 @@ export function verifyProject(cwd, { openapi = null } = {}) {
36
36
  /^[0-9a-f]{64}$/.test(a.graph.meta.sourceHash),
37
37
  );
38
38
 
39
- // Canonical form is idempotent: canonicalize(canonicalize(g)) == canonicalize(g)
40
- const canon1 = a.json;
41
- const canon2 = JSON.stringify(canonicalizeGraph(reviveGraph(a.graph)), null, 2) + '\n';
42
- record('Determinism (§3.3)', 'canonical form is a fixed point', canon1 === canon2);
39
+ // Canonical form is a fixed point: re-canonicalizing an ALREADY-canonical
40
+ // graph must be a no-op. This is a real idempotence check — the previous
41
+ // version compared canonicalize(g) with canonicalize(g) (trivially equal)
42
+ // and proved nothing.
43
+ const once = canonicalizeGraph(a.graph);
44
+ const twice = canonicalizeGraph(once);
45
+ record(
46
+ 'Determinism (§3.3)',
47
+ 'canonical form is a fixed point',
48
+ JSON.stringify(once) === JSON.stringify(twice),
49
+ );
43
50
 
44
51
  // Structural integrity — every edge lands on a real node, every kind known.
45
52
  let structural = true;
@@ -144,9 +151,3 @@ function openApiRoundTrip(graph, cwd) {
144
151
  return { pass: false, detail: err.message.split('\n')[0] };
145
152
  }
146
153
  }
147
-
148
- // canonicalizeGraph wants a live graph (Map nodes); rebuild one from the
149
- // canonical arrays so we can re-canonicalize and check the fixed point
150
- function reviveGraph(liveGraph) {
151
- return liveGraph; // compileUBG returns the live graph; canonicalize accepts it
152
- }