@remnic/core 9.7.6 → 9.7.7

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 (40) hide show
  1. package/dist/access-boundary.d.ts +2 -0
  2. package/dist/access-boundary.js +1 -1
  3. package/dist/access-cli.js +7 -7
  4. package/dist/access-http.js +5 -5
  5. package/dist/access-mcp.d.ts +2 -0
  6. package/dist/access-mcp.js +4 -4
  7. package/dist/access-operations-batch.js +2 -2
  8. package/dist/access-operations.js +3 -3
  9. package/dist/{chunk-TBQ4CFIP.js → chunk-57INMZ6F.js} +1 -1
  10. package/dist/chunk-57INMZ6F.js.map +1 -0
  11. package/dist/{chunk-BFCQPJ5B.js → chunk-CHVU4RE5.js} +24 -6
  12. package/dist/chunk-CHVU4RE5.js.map +1 -0
  13. package/dist/{chunk-3CRHW42H.js → chunk-FYKIEOG6.js} +2 -2
  14. package/dist/{chunk-ESE55PZJ.js → chunk-NMMKRVUF.js} +10 -6
  15. package/dist/chunk-NMMKRVUF.js.map +1 -0
  16. package/dist/{chunk-3UPBVNBX.js → chunk-Q6JPMCPO.js} +3 -3
  17. package/dist/{chunk-P2UA6XQG.js → chunk-UHGHTOR5.js} +5 -5
  18. package/dist/chunk-UHGHTOR5.js.map +1 -0
  19. package/dist/{chunk-H67QFYUK.js → chunk-UJBESW6X.js} +918 -146
  20. package/dist/chunk-UJBESW6X.js.map +1 -0
  21. package/dist/cli.js +6 -6
  22. package/dist/connectors/index.d.ts +7 -0
  23. package/dist/connectors/index.js +1 -1
  24. package/dist/index.js +7 -7
  25. package/dist/orchestrator.js +7 -7
  26. package/package.json +2 -2
  27. package/src/access-boundary.ts +2 -0
  28. package/src/access-http.ts +15 -2
  29. package/src/access-mcp-cancellation.test.ts +405 -0
  30. package/src/access-mcp.ts +25 -1
  31. package/src/access-operations-batch.ts +3 -3
  32. package/src/connectors/hermes-shim.ts +523 -0
  33. package/src/connectors/index.ts +769 -15
  34. package/dist/chunk-BFCQPJ5B.js.map +0 -1
  35. package/dist/chunk-ESE55PZJ.js.map +0 -1
  36. package/dist/chunk-H67QFYUK.js.map +0 -1
  37. package/dist/chunk-P2UA6XQG.js.map +0 -1
  38. package/dist/chunk-TBQ4CFIP.js.map +0 -1
  39. /package/dist/{chunk-3CRHW42H.js.map → chunk-FYKIEOG6.js.map} +0 -0
  40. /package/dist/{chunk-3UPBVNBX.js.map → chunk-Q6JPMCPO.js.map} +0 -0
@@ -139,6 +139,8 @@ interface OperationContext {
139
139
  readonly service: EngramAccessService;
140
140
  readonly authenticatedPrincipal?: string;
141
141
  readonly operatorPrincipal?: string;
142
+ /** Per-request cancellation supplied by transports that own a request lifecycle. */
143
+ readonly abortSignal?: AbortSignal;
142
144
  /** Server-resolved connector identity (Phase 1 provenance). Set by the HTTP auth boundary from the matched token's connector; flows to write handlers so frontmatter records which connector submitted the memory. Client-supplied values are always overridden. */
143
145
  readonly sourceConnector?: string;
144
146
  readonly hooks?: OperationHooks;
@@ -8,7 +8,7 @@ import {
8
8
  getOperation,
9
9
  listRegisteredOperations,
10
10
  normalizeOptionalPath
11
- } from "./chunk-TBQ4CFIP.js";
11
+ } from "./chunk-57INMZ6F.js";
12
12
  import "./chunk-D7FOKIY5.js";
13
13
  import "./chunk-CNWC274T.js";
14
14
  import "./chunk-GDASG7NC.js";
@@ -22,7 +22,7 @@ import "./chunk-O7BBFJLP.js";
22
22
  import "./chunk-7N4KAIGN.js";
23
23
  import "./chunk-AD2KK5MT.js";
24
24
  import "./chunk-5CJVLG3H.js";
25
- import "./chunk-H67QFYUK.js";
25
+ import "./chunk-UJBESW6X.js";
26
26
  import "./chunk-V5OCT34X.js";
27
27
  import "./chunk-TECVW3JP.js";
28
28
  import "./chunk-LXOM6IQU.js";
@@ -81,7 +81,7 @@ import "./chunk-Q6IP2ARR.js";
81
81
  import "./chunk-6HEM6HTQ.js";
82
82
  import "./chunk-ZQ7UBUZ6.js";
83
83
  import "./chunk-HXDKC3BI.js";
84
- import "./chunk-3UPBVNBX.js";
84
+ import "./chunk-Q6JPMCPO.js";
85
85
  import "./chunk-MMACM5G2.js";
86
86
  import "./chunk-54BGIBCR.js";
87
87
  import "./chunk-H4CKS5B2.js";
@@ -177,7 +177,7 @@ import "./chunk-G6NKALRN.js";
177
177
  import "./chunk-OO42R444.js";
178
178
  import "./chunk-K6MV3TRQ.js";
179
179
  import "./chunk-OADWQ5CR.js";
180
- import "./chunk-ESE55PZJ.js";
180
+ import "./chunk-NMMKRVUF.js";
181
181
  import "./chunk-V7IZKJI3.js";
182
182
  import "./chunk-SEDEKFYQ.js";
183
183
  import "./chunk-RKNJBZ55.js";
@@ -186,13 +186,13 @@ import "./chunk-RSUYKGGZ.js";
186
186
  import "./chunk-42NQ7AVG.js";
187
187
  import "./chunk-TMSXWOBZ.js";
188
188
  import "./chunk-7RXCMVFQ.js";
189
- import "./chunk-BFCQPJ5B.js";
189
+ import "./chunk-CHVU4RE5.js";
190
190
  import "./chunk-IC2TCZJ2.js";
191
191
  import "./chunk-D24OXEPB.js";
192
192
  import "./chunk-5RUE3GHQ.js";
193
- import "./chunk-3CRHW42H.js";
193
+ import "./chunk-FYKIEOG6.js";
194
194
  import "./chunk-2MR3MFQB.js";
195
- import "./chunk-P2UA6XQG.js";
195
+ import "./chunk-UHGHTOR5.js";
196
196
  import "./chunk-HLTAPHZZ.js";
197
197
  import "./chunk-YF6FMLBT.js";
198
198
  import "./chunk-W4PGTAS2.js";
@@ -202,7 +202,7 @@ import "./chunk-FTYDWKO6.js";
202
202
  import "./chunk-T4WDJPEZ.js";
203
203
  import {
204
204
  getOperation
205
- } from "./chunk-TBQ4CFIP.js";
205
+ } from "./chunk-57INMZ6F.js";
206
206
  import "./chunk-D7FOKIY5.js";
207
207
  import {
208
208
  EngramAccessService
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  EngramAccessHttpServer
3
- } from "./chunk-ESE55PZJ.js";
3
+ } from "./chunk-NMMKRVUF.js";
4
4
  import "./chunk-V7IZKJI3.js";
5
5
  import "./chunk-SEDEKFYQ.js";
6
6
  import "./chunk-RKNJBZ55.js";
@@ -9,13 +9,13 @@ import "./chunk-RSUYKGGZ.js";
9
9
  import "./chunk-42NQ7AVG.js";
10
10
  import "./chunk-TMSXWOBZ.js";
11
11
  import "./chunk-7RXCMVFQ.js";
12
- import "./chunk-BFCQPJ5B.js";
12
+ import "./chunk-CHVU4RE5.js";
13
13
  import "./chunk-IC2TCZJ2.js";
14
14
  import "./chunk-D24OXEPB.js";
15
15
  import "./chunk-5RUE3GHQ.js";
16
- import "./chunk-3CRHW42H.js";
16
+ import "./chunk-FYKIEOG6.js";
17
17
  import "./chunk-2MR3MFQB.js";
18
- import "./chunk-P2UA6XQG.js";
18
+ import "./chunk-UHGHTOR5.js";
19
19
  import "./chunk-HLTAPHZZ.js";
20
20
  import "./chunk-YF6FMLBT.js";
21
21
  import "./chunk-W4PGTAS2.js";
@@ -23,7 +23,7 @@ import "./chunk-KQDNAXOM.js";
23
23
  import "./chunk-35NN3ZFA.js";
24
24
  import "./chunk-FTYDWKO6.js";
25
25
  import "./chunk-T4WDJPEZ.js";
26
- import "./chunk-TBQ4CFIP.js";
26
+ import "./chunk-57INMZ6F.js";
27
27
  import "./chunk-D7FOKIY5.js";
28
28
  import "./chunk-CNWC274T.js";
29
29
  import "./chunk-GDASG7NC.js";
@@ -118,6 +118,8 @@ type McpRequestOptions = {
118
118
  * the operation context so write handlers stamp it onto frontmatter.
119
119
  */
120
120
  sourceConnector?: string;
121
+ /** HTTP request lifetime; absent for the standalone stdio transport. */
122
+ abortSignal?: AbortSignal;
121
123
  };
122
124
  /**
123
125
  * MCP protocol versions this server understands, ordered newest → oldest.
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  EngramMcpServer,
3
3
  MCP_SUPPORTED_PROTOCOL_VERSIONS
4
- } from "./chunk-BFCQPJ5B.js";
4
+ } from "./chunk-CHVU4RE5.js";
5
5
  import "./chunk-IC2TCZJ2.js";
6
6
  import "./chunk-D24OXEPB.js";
7
7
  import "./chunk-5RUE3GHQ.js";
8
- import "./chunk-3CRHW42H.js";
8
+ import "./chunk-FYKIEOG6.js";
9
9
  import "./chunk-2MR3MFQB.js";
10
- import "./chunk-P2UA6XQG.js";
10
+ import "./chunk-UHGHTOR5.js";
11
11
  import "./chunk-HLTAPHZZ.js";
12
12
  import "./chunk-YF6FMLBT.js";
13
13
  import "./chunk-W4PGTAS2.js";
@@ -15,7 +15,7 @@ import "./chunk-KQDNAXOM.js";
15
15
  import "./chunk-35NN3ZFA.js";
16
16
  import "./chunk-FTYDWKO6.js";
17
17
  import "./chunk-T4WDJPEZ.js";
18
- import "./chunk-TBQ4CFIP.js";
18
+ import "./chunk-57INMZ6F.js";
19
19
  import "./chunk-D7FOKIY5.js";
20
20
  import "./chunk-CNWC274T.js";
21
21
  import "./chunk-GDASG7NC.js";
@@ -1,4 +1,4 @@
1
- import "./chunk-P2UA6XQG.js";
1
+ import "./chunk-UHGHTOR5.js";
2
2
  import "./chunk-HLTAPHZZ.js";
3
3
  import "./chunk-YF6FMLBT.js";
4
4
  import "./chunk-W4PGTAS2.js";
@@ -6,7 +6,7 @@ import "./chunk-KQDNAXOM.js";
6
6
  import "./chunk-35NN3ZFA.js";
7
7
  import "./chunk-FTYDWKO6.js";
8
8
  import "./chunk-T4WDJPEZ.js";
9
- import "./chunk-TBQ4CFIP.js";
9
+ import "./chunk-57INMZ6F.js";
10
10
  import "./chunk-D7FOKIY5.js";
11
11
  import "./chunk-CNWC274T.js";
12
12
  import "./chunk-GDASG7NC.js";
@@ -22,9 +22,9 @@ import {
22
22
  memoryGetOperation,
23
23
  memorySearchOperation,
24
24
  memoryStoreOperation
25
- } from "./chunk-3CRHW42H.js";
25
+ } from "./chunk-FYKIEOG6.js";
26
26
  import "./chunk-2MR3MFQB.js";
27
- import "./chunk-P2UA6XQG.js";
27
+ import "./chunk-UHGHTOR5.js";
28
28
  import "./chunk-HLTAPHZZ.js";
29
29
  import "./chunk-YF6FMLBT.js";
30
30
  import "./chunk-W4PGTAS2.js";
@@ -32,7 +32,7 @@ import "./chunk-KQDNAXOM.js";
32
32
  import "./chunk-35NN3ZFA.js";
33
33
  import "./chunk-FTYDWKO6.js";
34
34
  import "./chunk-T4WDJPEZ.js";
35
- import "./chunk-TBQ4CFIP.js";
35
+ import "./chunk-57INMZ6F.js";
36
36
  import "./chunk-D7FOKIY5.js";
37
37
  import "./chunk-CNWC274T.js";
38
38
  import "./chunk-GDASG7NC.js";
@@ -274,4 +274,4 @@ export {
274
274
  listRegisteredOperations,
275
275
  __resetRegistryForTest
276
276
  };
277
- //# sourceMappingURL=chunk-TBQ4CFIP.js.map
277
+ //# sourceMappingURL=chunk-57INMZ6F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/access-boundary.ts"],"sourcesContent":["/**\n * Single input-validation and error boundary for the CLI/MCP/HTTP access\n * surfaces (issue #1525, epic #1520 Phase 1).\n *\n * Every operation that crosses the access-service facade passes through ONE\n * registry entry: a zod-validated request envelope, a shared error mapper,\n * and the \"reject invalid input and list valid options\" behavior that\n * CLAUDE.md rules 14/17/24/28/36/48/51 previously had to be re-implemented\n * per handler. The three surfaces become thin adapters — one operation\n * definition, three transports — so a validation fix lands everywhere at\n * once.\n *\n * Host-agnostic (rule 31): operation names carry no `openclaw-*`/`engram-*`\n * prefix. Session/namespace tenancy stays in the handler layer (resolved via\n * ScopePlan #1521); the boundary validates SHAPE, not tenancy.\n */\n\nimport { z } from \"zod\";\nimport { EngramAccessInputError, type EngramAccessService } from \"./access-service.js\";\nimport { assertFleetWideOperationAllowed, assertOperationAllowed, tokenCapabilityStore } from \"./access-token-capabilities.js\";\nimport { expandTildePath } from \"./utils/path.js\";\n\n// ---------------------------------------------------------------------------\n// Canonical operation names — host-agnostic (rule 31)\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical operation ids. One id is shared by the MCP tool, the HTTP route,\n * and the CLI command that expose the same operation. Add to this union as\n * each domain-group migration PR (memory ops → connectors → namespaces …)\n * lands; the fitness test in `access-surface-catalog.test.ts` treats the\n * registered set as the migration state.\n */\nexport const OPERATION_NAMES = [\n \"memory_get\",\n \"memory_search\",\n \"memory_store\",\n \"coding_decision\",\n \"coding_architecture\",\n // codegraph parity tools (issue #1554) -- each maps to one codegraph_*\n // boundary operation that delegates to the surface handler in\n // coding/codegraph-surfaces.ts. Names match the external tool suffixes.\n \"codegraph_index\",\n \"codegraph_list_projects\",\n \"codegraph_delete_project\",\n \"codegraph_index_status\",\n \"codegraph_search_graph\",\n \"codegraph_trace_path\",\n \"codegraph_detect_changes\",\n \"codegraph_query_graph\",\n \"codegraph_get_schema\",\n \"codegraph_get_snippet\",\n \"codegraph_get_architecture\",\n \"codegraph_search_code\",\n \"codegraph_manage_adr\",\n \"codegraph_ingest_traces\",\n \"memory_correct_plan\",\n \"memory_correct_apply\",\n \"coding_delta\",\n // Remaining MCP/HTTP handlers migrated through the boundary (issue #1525).\n \"recall\",\n \"recall_explain\",\n \"set_coding_context\",\n \"recall_tier_explain\",\n \"recall_xray\",\n \"wearables_status\",\n \"wearables_sync\",\n \"transcript_day\",\n \"transcript_search\",\n \"transcript_memories\",\n \"action_confidence\",\n \"chatgpt_memory_inspector\",\n \"day_summary\",\n \"capsule_export\",\n \"capsule_import\",\n \"capsule_list\",\n \"memory_governance_run\",\n \"procedure_mining_run\",\n \"pattern_reinforcement_run\",\n \"procedural_stats\",\n \"memory_timeline\",\n \"suggestion_submit\",\n \"entity_get\",\n \"review_queue_list\",\n \"correction_pending\",\n \"observe\",\n \"lcm_search\",\n \"lcm_compaction_flush\",\n \"lcm_compaction_record\",\n \"continuity_audit_generate\",\n \"continuity_incident_open\",\n \"continuity_incident_close\",\n \"continuity_incident_list\",\n \"continuity_loop_add_or_update\",\n \"continuity_loop_review\",\n \"identity_anchor_get\",\n \"identity_anchor_update\",\n \"memory_identity\",\n \"work_task\",\n \"work_project\",\n \"work_board\",\n \"shared_context_write_output\",\n \"shared_feedback_record\",\n \"shared_priorities_append\",\n \"shared_context_cross_signals_run\",\n \"shared_context_curate_daily\",\n \"compounding_weekly_synthesize\",\n \"compounding_promote_candidate\",\n \"compression_guidelines_optimize\",\n \"compression_guidelines_activate\",\n \"memory_profile\",\n \"memory_entities_list\",\n \"memory_questions\",\n \"memory_last_recall\",\n \"memory_intent_debug\",\n \"memory_qmd_debug\",\n \"memory_graph_explain\",\n \"graph_snapshot\",\n \"memory_feedback\",\n \"memory_promote\",\n \"memory_outcome\",\n \"memory_action_apply\",\n \"context_checkpoint\",\n \"briefing\",\n \"review_list\",\n \"review_resolve\",\n \"contradiction_scan_run\",\n \"memory_summarize_hourly\",\n \"conversation_index_update\",\n \"profiling_report\",\n \"graph_edge_decay_run\",\n \"live_connectors_run\",\n \"peer_list\",\n \"peer_get\",\n \"peer_set\",\n \"peer_delete\",\n \"peer_profile_get\",\n \"peer_forget\",\n \"console_state\",\n \"dreams_status\",\n \"dreams_run\",\n // HTTP-only routes (no direct MCP tool equivalent).\n \"adapters_status\",\n \"offline_sync_snapshot\",\n \"offline_sync_snapshot_stream\",\n \"offline_sync_files\",\n \"offline_sync_file_content\",\n \"offline_sync_apply_file_content\",\n \"offline_sync_apply\",\n \"lcm_status\",\n \"memory_list\",\n \"recall_timings\",\n \"entity_list\",\n \"maintenance_status\",\n \"quality_status\",\n \"trust_zones_status\",\n \"trust_zones_records\",\n \"review_disposition\",\n \"trust_zones_promote\",\n \"trust_zones_demo_seed\",\n \"citations_observed\",\n \"contradiction_detail\",\n \"graph_events\",\n // Chat surface — conversational memory inspection/correction (issue #1583).\n \"chat_message\",\n \"chat_events\",\n] as const;\n\n/**\n * Canonical operation ids. One id is shared by the MCP tool, the HTTP route,\n * and the CLI command that expose the same operation. Derived from\n * {@link OPERATION_NAMES} (single source of truth) so the runtime catalog\n * used for token-capability validation cannot drift from the type. The\n * fitness test in `access-surface-catalog.test.ts` treats the registered set\n * as the migration state.\n */\nexport type OperationName = (typeof OPERATION_NAMES)[number];\n\n// ---------------------------------------------------------------------------\n// Operation context — what every handler receives\n// ---------------------------------------------------------------------------\n\n/**\n * Per-call context. `service` is the facade the handler delegates to; the\n * boundary never reaches past it. `authenticatedPrincipal` is resolved by\n * the SURFACE (MCP header / HTTP identity / CLI flag) before the boundary\n * runs, so handlers stay principal-source-agnostic. `hooks` carries\n * transport-level callbacks (e.g. HTTP write-quota enforcement) that must\n * fire atomically inside the service call; surfaces that have no such hook\n * leave it undefined.\n */\nexport interface OperationContext {\n readonly service: EngramAccessService;\n readonly authenticatedPrincipal?: string;\n readonly operatorPrincipal?: string;\n /** Per-request cancellation supplied by transports that own a request lifecycle. */\n readonly abortSignal?: AbortSignal;\n /** Server-resolved connector identity (Phase 1 provenance). Set by the HTTP auth boundary from the matched token's connector; flows to write handlers so frontmatter records which connector submitted the memory. Client-supplied values are always overridden. */\n readonly sourceConnector?: string;\n readonly hooks?: OperationHooks;\n}\n\n/**\n * Transport-level callbacks a handler forwards into the service call. Kept\n * narrow on purpose: the boundary owns validation + dispatch shape, not\n * transport policy. Add fields here only when a surface genuinely needs a\n * callback the service itself consumes.\n */\nexport interface OperationHooks {\n /** HTTP write-quota gate; throws to reject the write when exhausted. */\n readonly enforceWriteQuota?: () => void | Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Operation spec + bound operation\n// ---------------------------------------------------------------------------\n\nexport interface OperationSpec<In, Out> {\n /** Canonical operation id; matches an {@link OperationName}. */\n readonly name: OperationName;\n readonly description: string;\n /** Zod schema validating the raw request envelope. */\n readonly schema: z.ZodType<In>;\n /** Handler invoked with the parsed input; throws EngramAccessInputError for domain faults. */\n readonly handler: (input: In, ctx: OperationContext) => Promise<Out>;\n /**\n * Marks an operation as inherently FLEET-WIDE / global — it runs across ALL\n * namespaces (or against a single non-namespaced global layer) and carries no\n * `namespace` argument, so the MCP `tools/call` effective-namespace chokepoint\n * never applies. When set, {@link defineOperation}'s run wrapper rejects any\n * namespace-SCOPED token (fail closed) via assertFleetWideOperationAllowed\n * BEFORE the handler — no side effect on denial. Unrestricted/legacy tokens\n * (cron, internal callers, no capability record) are unaffected (issue #1850).\n */\n readonly fleetWide?: boolean;\n}\n\nexport interface BoundOperation<In = unknown, Out = unknown> {\n readonly spec: OperationSpec<In, Out>;\n /** Validate the raw envelope, then invoke the handler. Throws EngramAccessInputError on any validation failure. */\n readonly run: (rawInput: unknown, ctx: OperationContext) => Promise<Out>;\n}\n\n// ---------------------------------------------------------------------------\n// Shared normalizers the boundary owns (rules 17, 28, 36, 48, 51)\n// ---------------------------------------------------------------------------\n\n/**\n * Coerce boolean-like strings at the edge (rule 36). Accepts actual booleans\n * and the string spellings clients send (\"true\"/\"false\"/\"1\"/\"0\"/\"yes\"/\"no\"/\n * \"on\"/\"off\", case-insensitive). Rejects anything else loudly — `Boolean(\"false\")`\n * would silently be `true`, which is the bug rule 36 exists to prevent.\n *\n * `undefined`/`null`/`\"\"` → `undefined`, so callers can keep treating an\n * absent flag as \"use the default\" without a separate presence check.\n */\nexport function coerceBooleanLike(value: unknown): boolean | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const lower = value.trim().toLowerCase();\n if (lower === \"true\" || lower === \"1\" || lower === \"yes\" || lower === \"on\") return true;\n if (lower === \"false\" || lower === \"0\" || lower === \"no\" || lower === \"off\") return false;\n }\n throw new EngramAccessInputError(\n `expected a boolean-like value (true|false|1|0|yes|no|on|off); got ${JSON.stringify(value)}`,\n );\n}\n\n/**\n * Coerce + validate a positive integer from a numeric string or number\n * (rule 28). Loosely-typed MCP/CLI clients send `\"5\"`; `typeof saved === \"number\"`\n * on read-back would reject it later, so we coerce at the edge and reject\n * booleans/objects loudly (`Number(true) === 1` would silently pass otherwise).\n *\n * `undefined`/`null`/`\"\"` → `undefined`.\n */\nexport function coercePositiveInteger(value: unknown, label: string): number | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value === \"number\") {\n if (!Number.isFinite(value) || value <= 0 || !Number.isInteger(value)) {\n throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);\n }\n return value;\n }\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!/^[+-]?\\d+$/.test(trimmed)) {\n throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);\n }\n const parsed = Number(trimmed);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);\n }\n return parsed;\n }\n throw new EngramAccessInputError(`${label} expects a positive integer; got ${JSON.stringify(value)}`);\n}\n\n/**\n * Expand `~` in a path-shaped input (rule 17). Node `fs` does NOT expand `~`;\n * ad-hoc regex drifts. `undefined`/`null`/`\"\"` → `undefined`.\n */\nexport function normalizeOptionalPath(value: unknown): string | undefined {\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value !== \"string\") {\n throw new EngramAccessInputError(`expected a path string; got ${JSON.stringify(value)}`);\n }\n return expandTildePath(value);\n}\n\n// ---------------------------------------------------------------------------\n// Error formatting — rule 51: list valid options, never silently default\n// ---------------------------------------------------------------------------\n\n/**\n * Turn a zod failure into an {@link EngramAccessInputError} whose message\n * names the offending field and — for enum/union issues — lists the valid\n * options, so the caller can correct rather than guess (rule 51).\n */\nexport function formatZodIssues(error: z.ZodError): string {\n const parts: string[] = [];\n for (const issue of error.issues) {\n // Collapse array-index paths (e.g. [\"currentContextScopes\", 1]) to\n // the field name so the message reads \"field: must be ...\" not\n // \"field.1: must be ...\".\n const hasNumeric = issue.path.some((p) => typeof p === \"number\");\n const path = hasNumeric\n ? issue.path.filter((p) => typeof p === \"string\").join(\".\") || \"(root)\"\n : issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\";\n const options = enumOptionsFromIssue(issue);\n const suffix = options ? `. Valid: ${options.join(\", \")}` : \"\";\n parts.push(`${path}: ${issue.message}${suffix}`);\n }\n return parts.length > 0\n ? `request validation failed: ${parts.join(\"; \")}`\n : \"request validation failed\";\n}\n\nfunction enumOptionsFromIssue(issue: z.ZodIssue): readonly string[] | undefined {\n // zod exposes accepted enum values on the issue for ZodEnum / ZodNativeEnum\n // and on the options of invalid_union discriminators. Reading them here\n // keeps \"list valid options\" in ONE place rather than per handler.\n if (issue.code === z.ZodIssueCode.invalid_enum_value) {\n const rawOptions = (issue as { options?: unknown }).options;\n if (Array.isArray(rawOptions)) {\n return rawOptions.map((opt) => String(opt));\n }\n }\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Registry\n// ---------------------------------------------------------------------------\n\nconst registry = new Map<OperationName, BoundOperation>();\n\n/**\n * Register an operation. Throws if the name is already registered — duplicate\n * registration is a programming error, not a runtime input fault, so it throws\n * a plain Error (not the input-error class surfaces translate for clients).\n */\n/**\n * Error map that converts Zod's generic type-mismatch messages into\n * human-readable \"must be a <type>\" messages (rule 51). Applied at the\n * boundary chokepoint so every operation gets consistent messaging\n * without per-handler boilerplate.\n */\nconst humanReadableErrorMap: z.ZodErrorMap = (issue, ctx) => {\n if (issue.code === z.ZodIssueCode.invalid_type) {\n if (issue.received === \"undefined\") return { message: ctx.defaultError };\n // Array-element type errors — collapse to field-level message.\n if (issue.path.some((p) => typeof p === \"number\")) {\n const arrField = issue.path.filter((p) => typeof p === \"string\").join(\".\");\n return { message: `${arrField} must be an array of ${issue.expected}s` };\n }\n const fieldName = issue.path.length > 0 ? String(issue.path[issue.path.length - 1]) : \"value\";\n switch (issue.expected) {\n case \"string\": return { message: `${fieldName} must be a string` };\n case \"boolean\": return { message: `${fieldName} must be a boolean` };\n case \"number\": return { message: `${fieldName} must be a number` };\n case \"array\": return { message: `${fieldName} must be an array` };\n case \"object\": return { message: `${fieldName} must be an object` };\n default: return { message: ctx.defaultError };\n }\n }\n return { message: ctx.defaultError };\n};\n\nexport function defineOperation<In, Out>(spec: OperationSpec<In, Out>): BoundOperation<In, Out> {\n if (registry.has(spec.name)) {\n throw new Error(`access-boundary: operation already registered: ${spec.name}`);\n }\n const bound: BoundOperation<In, Out> = {\n spec,\n run: async (rawInput, ctx) => {\n const parseResult = spec.schema.safeParse(rawInput, { errorMap: humanReadableErrorMap });\n if (!parseResult.success) {\n throw new EngramAccessInputError(formatZodIssues(parseResult.error));\n }\n // Per-token capability enforcement (issue #1837): when the presenting\n // token carries an ops allow-list, reject operations not in it. The\n // capability record is carried via AsyncLocalStorage, set once per\n // authorized HTTP/MCP request; absent (CLI/tests/direct callers) ⇒\n // unrestricted. Default-deny ONLY when an ops axis is present — legacy\n // tokens (absent record) and explicit-unrestricted new tokens are\n // unaffected.\n assertOperationAllowed(tokenCapabilityStore.getStore(), spec.name);\n // Fleet-wide / global maintenance ops (issue #1850 round 10): these run\n // across all namespaces with no `namespace` arg, so the tools/call\n // effective-namespace gate never fires. A namespace-SCOPED token must not\n // trigger cross-namespace maintenance — fail closed BEFORE the handler so\n // a denial never leaks a partial mutation. No-op for unrestricted/legacy.\n if (spec.fleetWide) {\n assertFleetWideOperationAllowed(tokenCapabilityStore.getStore());\n }\n return spec.handler(parseResult.data, ctx);\n },\n };\n // Store under the canonical name; the cast is safe because In/Out are\n // erased at the registry boundary and recovered by callers via getOperation.\n registry.set(spec.name, bound as unknown as BoundOperation);\n return bound;\n}\n\n/** Look up a registered operation by canonical name. */\nexport function getOperation(name: OperationName): BoundOperation | undefined {\n return registry.get(name);\n}\n\n/** All registered operation names. */\nexport function listRegisteredOperations(): readonly OperationName[] {\n return [...registry.keys()];\n}\n\n/** Test-only: clear the registry so pilot definitions can be re-registered. */\nexport function __resetRegistryForTest(): void {\n registry.clear();\n}\n"],"mappings":"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","names":[]}
@@ -18,7 +18,7 @@ import {
18
18
  } from "./chunk-T4WDJPEZ.js";
19
19
  import {
20
20
  getOperation
21
- } from "./chunk-TBQ4CFIP.js";
21
+ } from "./chunk-57INMZ6F.js";
22
22
  import {
23
23
  assertOperationAllowed,
24
24
  capabilityAllowsOp,
@@ -31,6 +31,10 @@ import {
31
31
  import {
32
32
  readEnvVar
33
33
  } from "./chunk-JUC24CTX.js";
34
+ import {
35
+ abortError,
36
+ isAbortError
37
+ } from "./chunk-PVGDJXVK.js";
34
38
 
35
39
  // src/access-mcp.ts
36
40
  import { randomUUID as randomUUID2 } from "crypto";
@@ -1136,6 +1140,11 @@ async function processChatMessage(opts) {
1136
1140
  }
1137
1141
 
1138
1142
  // src/access-mcp.ts
1143
+ function throwMcpAbort(signal, message) {
1144
+ if (!signal?.aborted) return;
1145
+ if (isAbortError(signal.reason)) throw signal.reason;
1146
+ throw abortError(message);
1147
+ }
1139
1148
  var MCP_SUPPORTED_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
1140
1149
  var MCP_DEFAULT_PROTOCOL_VERSION = MCP_SUPPORTED_PROTOCOL_VERSIONS[0] ?? "2025-06-18";
1141
1150
  var LEGACY_MCP_PREFIX = "engram.";
@@ -3478,6 +3487,7 @@ var EngramMcpServer = class {
3478
3487
  ...options?.namespaceOverride ? { namespace: options.namespaceOverride } : {},
3479
3488
  ...options?.sessionKeyOverride ? { sessionKey: options.sessionKeyOverride } : {}
3480
3489
  };
3490
+ throwMcpAbort(options?.abortSignal, "MCP request aborted before operation start");
3481
3491
  const result = await this.callTool(
3482
3492
  name,
3483
3493
  argumentsObject,
@@ -3485,8 +3495,12 @@ var EngramMcpServer = class {
3485
3495
  options?.sessionId,
3486
3496
  mcpScope,
3487
3497
  options?.enforceWriteQuota,
3488
- options?.sourceConnector
3498
+ options?.sourceConnector,
3499
+ options?.abortSignal
3489
3500
  );
3501
+ if (isReadOnlyToolName(name)) {
3502
+ throwMcpAbort(options?.abortSignal, "MCP request aborted before response");
3503
+ }
3490
3504
  return {
3491
3505
  jsonrpc: "2.0",
3492
3506
  id,
@@ -3497,6 +3511,7 @@ var EngramMcpServer = class {
3497
3511
  }
3498
3512
  };
3499
3513
  } catch (err) {
3514
+ if (isAbortError(err)) throw err;
3500
3515
  const message = err instanceof Error ? err.message : String(err);
3501
3516
  return {
3502
3517
  jsonrpc: "2.0",
@@ -3663,7 +3678,7 @@ ${body}`;
3663
3678
  noteDefault: r.preview?.slice(0, 60) || r.id
3664
3679
  }));
3665
3680
  }
3666
- async callTool(name, args, effectivePrincipal, mcpSessionId, scope, enforceWriteQuota, sourceConnector) {
3681
+ async callTool(name, args, effectivePrincipal, mcpSessionId, scope, enforceWriteQuota, sourceConnector, abortSignal) {
3667
3682
  const migrated = MCP_MIGRATED_OPERATIONS[toLegacyToolName(name)];
3668
3683
  if (!migrated) {
3669
3684
  throw new Error(`unknown tool: ${name}`);
@@ -3730,8 +3745,10 @@ ${body}`;
3730
3745
  if (migrated === "recall") {
3731
3746
  const result = await op.run(envelope, {
3732
3747
  service: this.service,
3733
- authenticatedPrincipal: effectivePrincipal
3748
+ authenticatedPrincipal: effectivePrincipal,
3749
+ ...abortSignal ? { abortSignal } : {}
3734
3750
  });
3751
+ throwMcpAbort(abortSignal, "MCP recall aborted before postprocessing");
3735
3752
  const response = result.result;
3736
3753
  if (this.shouldEmitCitations(mcpSessionId)) {
3737
3754
  const citations = this.buildRecallCitations(response);
@@ -3750,7 +3767,8 @@ ${body}`;
3750
3767
  service: this.service,
3751
3768
  authenticatedPrincipal: effectivePrincipal,
3752
3769
  ...enforceWriteQuota ? { hooks: { enforceWriteQuota } } : {},
3753
- ...sourceConnector ? { sourceConnector } : {}
3770
+ ...sourceConnector ? { sourceConnector } : {},
3771
+ ...abortSignal ? { abortSignal } : {}
3754
3772
  });
3755
3773
  return output.result;
3756
3774
  }
@@ -3771,4 +3789,4 @@ export {
3771
3789
  MCP_SUPPORTED_PROTOCOL_VERSIONS,
3772
3790
  EngramMcpServer
3773
3791
  };
3774
- //# sourceMappingURL=chunk-BFCQPJ5B.js.map
3792
+ //# sourceMappingURL=chunk-CHVU4RE5.js.map