@smartmemory/stratum 0.4.5 → 0.5.0

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.
Files changed (45) hide show
  1. package/dist/cli/flow.js +61 -0
  2. package/dist/cli/flow.js.map +1 -0
  3. package/dist/cli/learn.js +9 -16
  4. package/dist/cli/learn.js.map +1 -1
  5. package/dist/cli/query_gate.js +7 -2
  6. package/dist/cli/query_gate.js.map +1 -1
  7. package/dist/cli/stratum.js +3 -1
  8. package/dist/cli/stratum.js.map +1 -1
  9. package/dist/connectors/background.js +2 -2
  10. package/dist/connectors/background.js.map +1 -1
  11. package/dist/connectors/claude.js +2 -0
  12. package/dist/connectors/claude.js.map +1 -1
  13. package/dist/connectors/codex.js +4 -0
  14. package/dist/connectors/codex.js.map +1 -1
  15. package/dist/connectors/foreground_registry.js +589 -0
  16. package/dist/connectors/foreground_registry.js.map +1 -0
  17. package/dist/connectors/index.js +1 -0
  18. package/dist/connectors/index.js.map +1 -1
  19. package/dist/connectors/proc_identity.js +28 -0
  20. package/dist/connectors/proc_identity.js.map +1 -1
  21. package/dist/connectors/runner.js +2 -0
  22. package/dist/connectors/runner.js.map +1 -1
  23. package/dist/contracts/events.json +27 -1
  24. package/dist/contracts/mcp-surface.json +176 -6
  25. package/dist/engine/checkpoint.js +1 -1
  26. package/dist/engine/checkpoint.js.map +1 -1
  27. package/dist/engine/engine.js +860 -195
  28. package/dist/engine/engine.js.map +1 -1
  29. package/dist/engine/flow_cancel.js +158 -0
  30. package/dist/engine/flow_cancel.js.map +1 -0
  31. package/dist/engine/run_lock.js +628 -0
  32. package/dist/engine/run_lock.js.map +1 -0
  33. package/dist/engine/state.js +43 -2
  34. package/dist/engine/state.js.map +1 -1
  35. package/dist/ir/refs.js +12 -0
  36. package/dist/ir/refs.js.map +1 -1
  37. package/dist/ir/schema.js +9 -0
  38. package/dist/ir/schema.js.map +1 -1
  39. package/dist/ir/validate.js +243 -29
  40. package/dist/ir/validate.js.map +1 -1
  41. package/dist/learn/smartmemory_egress.js +3 -1
  42. package/dist/learn/smartmemory_egress.js.map +1 -1
  43. package/dist/mcp/server.js +193 -4
  44. package/dist/mcp/server.js.map +1 -1
  45. package/package.json +3 -2
@@ -0,0 +1,158 @@
1
+ import { reapFlowAgents, signalFlowAgents } from "../connectors/foreground_registry.js";
2
+ export const EMPTY_AGENTS = Object.freeze({
3
+ signalled: 0, reaped: 0, gone: 0, unreachable: 0, alreadySettled: 0, unresolved: 0, unsettled: 0, unreaped: 0,
4
+ });
5
+ function emptyAgents() { return { ...EMPTY_AGENTS }; }
6
+ function cancelError(code, fields, message) {
7
+ const text = message ?? (fields.reason === "run_lock_held"
8
+ ? `run ${fields.runId} lock is held${fields.holderPid !== undefined ? ` by pid ${fields.holderPid}` : ""}; cancel was not applied`
9
+ : `cancel of run ${fields.runId} was not acknowledged`);
10
+ return Object.assign(new Error(text), { code }, fields);
11
+ }
12
+ function codeOf(error) {
13
+ return typeof error === "object" && error !== null && "code" in error
14
+ ? String(error.code) : undefined;
15
+ }
16
+ function pidOf(error) {
17
+ const pid = error?.holderPid;
18
+ return typeof pid === "number" ? pid : undefined;
19
+ }
20
+ function reasonOf(error) {
21
+ const reason = error?.reason;
22
+ return reason === "run_lock_held" || reason === "engine_dispatch_active" || reason === "local_teardown_timeout"
23
+ ? reason : undefined;
24
+ }
25
+ /** F6. `Number("soon")` is NaN, `Date.now() + NaN` is NaN, and every `Date.now() >= deadline`
26
+ * test against it is false — so a mistyped budget did not shorten the teardown, it removed the
27
+ * deadline entirely and let the reap loop run forever. A bad value fails here instead. */
28
+ function timeoutBudgetMs(options) {
29
+ const value = options.timeoutMs ?? Number(process.env.STRATUM_CANCEL_TIMEOUT_MS ?? 15000);
30
+ if (!Number.isFinite(value) || value < 0) {
31
+ throw new Error(`${options.timeoutMs !== undefined ? "timeoutMs" : "STRATUM_CANCEL_TIMEOUT_MS"} must be a nonnegative finite number`);
32
+ }
33
+ return value;
34
+ }
35
+ /** F2. `graceMs` is validated HERE rather than deep in the registry, for the same reason as the
36
+ * budget: the registry only sees it after the run has been settled. */
37
+ function requireGraceMs(options) {
38
+ if (options.graceMs === undefined)
39
+ return;
40
+ if (!Number.isFinite(options.graceMs) || options.graceMs < 0) {
41
+ throw new Error("graceMs must be a nonnegative finite number");
42
+ }
43
+ }
44
+ /** The phases of a foreground cancel, shared by the MCP tool and the CLI. Both surfaces call
45
+ * this and NOTHING else (R1-4) — a phase implemented at one call site is a phase the other
46
+ * surface silently lacks.
47
+ *
48
+ * Order (R2-5): SETTLE → signal every recorded group → await the local abort's settlement →
49
+ * await the reap and the registry settlement → compute `acknowledged`.
50
+ *
51
+ * Settling first (D4) means the acknowledgement is never lost to a hung teardown: the run is
52
+ * durably cancelled before a single signal is sent, and a teardown failure is reported ON TOP
53
+ * of a completed settle. Signalling before awaiting the local abort matters because the two
54
+ * teardowns overlap — SIGTERM to a group and an AbortController on the same run are the same
55
+ * child from two directions — and serialising them would add a full grace window per agent.
56
+ * Awaiting the local settlement BEFORE the reap pass is what stops the sweep from reading
57
+ * `running` entries whose teardown this very call started (R2-5).
58
+ *
59
+ * A terminal run that was not cancelled skips the agent phases entirely (R2-4): there are no
60
+ * agents of ours to reap, and sweeping would manufacture a teardown verdict about work that
61
+ * was never ours. An ALREADY-cancelled run does not skip them — re-running the sweep is how a
62
+ * caller recovers from an earlier CANCELLATION_TEARDOWN_TIMEOUT. */
63
+ export async function cancelFlow(engine, runId, options = {}) {
64
+ // Phase 1: settle, and SETTLE FIRST IS ABSOLUTE. A refusal here means the flow is still
65
+ // running, so NO agent is signalled or reaped: killing a live driver's agents without
66
+ // settling the flow leaves a live flow with dead agents, which is the hazard R1-4 forbids.
67
+ // The refusal returns an all-zero agents summary because no teardown was attempted.
68
+ //
69
+ // A RUN_LOCK_TIMEOUT or a live driver lease here means nothing was mutated
70
+ // (§2.1b) — normalise both rather than letting a raw engine error reach a surface that has
71
+ // no envelope for it (R3-5). Every other error (ENOENT for an unknown flow above all)
72
+ // propagates untouched: the CLI maps it, and inventing a cancellation code for it would
73
+ // report a teardown verdict about a run we never found.
74
+ // EVERY budget is validated BEFORE the engine is touched (F2). Validating them after the
75
+ // settle meant a mistyped timeout produced the worst possible outcome: the run was already
76
+ // durably cancelled, no agent had been signalled, and the caller got an argument error that
77
+ // reads as "nothing happened". The clock only starts once the run is settled, further down.
78
+ const budgetMs = timeoutBudgetMs(options);
79
+ requireGraceMs(options);
80
+ let settled;
81
+ try {
82
+ settled = await engine.flowCancel(runId, options.reason);
83
+ }
84
+ catch (error) {
85
+ const code = codeOf(error);
86
+ if (code !== "RUN_LOCK_TIMEOUT" && code !== "CANCELLATION_UNCONFIRMED")
87
+ throw error;
88
+ const holderPid = pidOf(error);
89
+ throw cancelError("CANCELLATION_UNCONFIRMED", {
90
+ runId,
91
+ status: "running",
92
+ flowSettled: false,
93
+ agents: emptyAgents(),
94
+ reason: code === "RUN_LOCK_TIMEOUT" ? "run_lock_held" : (reasonOf(error) ?? "engine_dispatch_active"),
95
+ ...(holderPid !== undefined ? { holderPid } : {}),
96
+ }, error instanceof Error ? error.message : undefined);
97
+ }
98
+ if (!settled.flowSettled) {
99
+ // completed / failed / budget_exhausted: nothing of ours is running (R2-4).
100
+ return { ...settled, acknowledged: false, agents: emptyAgents() };
101
+ }
102
+ // R3-5: ONE absolute teardown deadline, computed here — after settlement, so the lock wait
103
+ // never eats into it — and shared by every phase. Three independent per-phase timeouts is how
104
+ // a caller ends up waiting 3x the budget it configured, and how a slow signal pass silently
105
+ // leaves no time to reap.
106
+ const deadline = Date.now() + budgetMs;
107
+ const remaining = () => Math.max(0, deadline - Date.now());
108
+ const registryOptions = {
109
+ ...(options.registryRoot !== undefined ? { registryRoot: options.registryRoot } : {}),
110
+ ...(options.graceMs !== undefined ? { graceMs: options.graceMs } : {}),
111
+ ...(options.identity !== undefined ? { identity: options.identity } : {}),
112
+ ...(options.probes !== undefined ? { probes: options.probes } : {}),
113
+ deadlineAt: deadline,
114
+ };
115
+ // The signal pass gets the SAME absolute deadline as the reap. A signal pass with no deadline
116
+ // can spend the whole budget probing and then hand the reap nothing, and can keep sending
117
+ // SIGTERMs after the caller has stopped waiting for an answer to any of them.
118
+ const signalled = await signalFlowAgents(runId, registryOptions);
119
+ // An abortLocal that times out does NOT abort the cancel: the groups were already signalled
120
+ // and the reap is the authority on whether they died. Record it and keep going (R3-5).
121
+ let localTimedOut = false;
122
+ try {
123
+ await options.abortLocal?.(runId, remaining());
124
+ }
125
+ catch (error) {
126
+ if (codeOf(error) !== "CANCELLATION_TEARDOWN_TIMEOUT")
127
+ throw error;
128
+ localTimedOut = true;
129
+ }
130
+ const agents = await reapFlowAgents(runId, signalled, registryOptions);
131
+ // R1-5: acknowledged is a GUARANTEE, not a summary. Anything unresolved is an error.
132
+ // R3-6: unsettled is the strongest of the three and subsumes "groups reaped but the owning
133
+ // dispatcher is still unwinding". All four must be clear.
134
+ // Acknowledgement is "every group reached a final resolved state and nothing is outstanding",
135
+ // NOT the old `signalled === reaped` equality. That equality failed both ways: a group that
136
+ // had already exited before our SIGTERM is never counted `signalled`, so a flow whose agent
137
+ // died on its own could never be acknowledged; and a group signalled and later found
138
+ // unreachable satisfied it by accident.
139
+ const acknowledged = settled.flowSettled
140
+ && agents.unsettled === 0
141
+ && agents.unresolved === 0
142
+ && agents.unreachable === 0
143
+ && agents.unreaped === 0;
144
+ if (!acknowledged) {
145
+ const code = agents.unreachable > 0 && agents.unresolved === 0 && agents.unreaped === 0
146
+ ? "CANCELLATION_UNCONFIRMED"
147
+ : "CANCELLATION_TEARDOWN_TIMEOUT";
148
+ // R3-5: every failure leaves this function as ONE structured error with the same fields,
149
+ // so the MCP and CLI projections have exactly one shape to render. `status` and
150
+ // `flowSettled` come from the engine, never from an assumption (R1-5).
151
+ throw cancelError(code, {
152
+ runId, status: settled.status, flowSettled: settled.flowSettled, agents,
153
+ ...(localTimedOut ? { reason: "local_teardown_timeout" } : {}),
154
+ });
155
+ }
156
+ return { ...settled, acknowledged, agents };
157
+ }
158
+ //# sourceMappingURL=flow_cancel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow_cancel.js","sourceRoot":"","sources":["../../src/engine/flow_cancel.ts"],"names":[],"mappings":"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"}