@sanctuary-framework/mcp-server 1.2.5 → 1.2.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.
- package/dist/cli.cjs +1607 -45
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1607 -45
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1580 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +543 -5
- package/dist/index.d.ts +543 -5
- package/dist/index.js +1580 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5315,6 +5315,212 @@ declare class ConciergeMemoryStore {
|
|
|
5315
5315
|
private withLock;
|
|
5316
5316
|
}
|
|
5317
5317
|
|
|
5318
|
+
/**
|
|
5319
|
+
* Sanctuary MCP Server, Operator Chat, Concierge Query Grammar
|
|
5320
|
+
*
|
|
5321
|
+
* WP-V1.3-9 Tau-4 (criterion (d) of the v1.3 scope-lock revision). Builds
|
|
5322
|
+
* on Tau-3 (PR #171, dynamic context router). The grammar layer parses
|
|
5323
|
+
* operator queries into structured parameters: time ranges, agent names,
|
|
5324
|
+
* event types, and a free-form intent residual. Tau-3 fetchers receive
|
|
5325
|
+
* the parsed hints alongside the query so the substrate sees the most
|
|
5326
|
+
* relevant data slice.
|
|
5327
|
+
*
|
|
5328
|
+
* The module is pure: rule-based extraction, no I/O, no clock side
|
|
5329
|
+
* effects beyond the optional `now` parameter for deterministic tests.
|
|
5330
|
+
* The optional LLM-assist fallback is invoked by the caller when the
|
|
5331
|
+
* rule-based parser produces low confidence.
|
|
5332
|
+
*
|
|
5333
|
+
* Castle-walking discipline:
|
|
5334
|
+
* - No new outbound surface; the LLM-assist fallback routes through the
|
|
5335
|
+
* substrate selector at the same `concierge` surface Tau-3 already
|
|
5336
|
+
* uses.
|
|
5337
|
+
* - Encryption boundaries hold: this module receives plain strings
|
|
5338
|
+
* already produced inside the fortress.
|
|
5339
|
+
* - Failure-mode handling is fail-soft: low-confidence parses surface a
|
|
5340
|
+
* structured fallback menu so the operator sees what the concierge
|
|
5341
|
+
* can answer, never a silent drop.
|
|
5342
|
+
*
|
|
5343
|
+
* Privacy invariant:
|
|
5344
|
+
* The runtime `ParsedQuery` carries `intent_phrase` (free-form residual
|
|
5345
|
+
* pulled from the operator's query) for the router's own consumption.
|
|
5346
|
+
* The audit emission MUST NOT include `intent_phrase`; consumers serialize
|
|
5347
|
+
* via `auditSafeSummary()` which strips it. See `OperatorConciergeChatPayload`
|
|
5348
|
+
* safe-metadata invariant in `contracts/v1.2/operator-chat-events.ts`.
|
|
5349
|
+
*
|
|
5350
|
+
* Out of scope here (Tau-5 + later):
|
|
5351
|
+
* - Agent-context awareness (proactive parsing without an explicit
|
|
5352
|
+
* query). Tau-4 parses off explicit query content only.
|
|
5353
|
+
* - Voice input or natural-language-as-API surface (defer to v1.5+).
|
|
5354
|
+
* - Cross-fortress concierge query (defer to v1.4 cross-tenant scope).
|
|
5355
|
+
* - Window markers like "around the time of <event>" that require an
|
|
5356
|
+
* audit-log lookup to resolve. Tau-4 ships static patterns only.
|
|
5357
|
+
*/
|
|
5358
|
+
|
|
5359
|
+
/**
|
|
5360
|
+
* A resolved time range. `relative_label` carries the original token
|
|
5361
|
+
* ("yesterday", "past hour", etc.) when the range was derived from a
|
|
5362
|
+
* relative pattern, so consumers can render operator-readable copy.
|
|
5363
|
+
*/
|
|
5364
|
+
interface ParsedTimeRange {
|
|
5365
|
+
start: Date;
|
|
5366
|
+
end: Date;
|
|
5367
|
+
relative_label?: string;
|
|
5368
|
+
}
|
|
5369
|
+
/**
|
|
5370
|
+
* Stable structured-failure flags surfaced when the parser cannot
|
|
5371
|
+
* resolve a token confidently. The strings are enum-like (no free text)
|
|
5372
|
+
* so the audit emission stays safe-metadata-only and the dashboard can
|
|
5373
|
+
* group by cause.
|
|
5374
|
+
*/
|
|
5375
|
+
type AmbiguityFlag = "unknown_time_token" | "unknown_agent_token" | "unknown_event_token" | "ambiguous_time_range" | "no_signal_extracted";
|
|
5376
|
+
/**
|
|
5377
|
+
* Runtime parsed-query shape. Used by the router to thread parameters
|
|
5378
|
+
* into context fetchers, and by the structured-fallback menu builder.
|
|
5379
|
+
*
|
|
5380
|
+
* `intent_phrase` is the residual after time/agent/event tokens are
|
|
5381
|
+
* stripped, useful for router-level intent routing. It carries operator
|
|
5382
|
+
* query content and MUST NOT be serialized into the audit log.
|
|
5383
|
+
*/
|
|
5384
|
+
interface ParsedQuery {
|
|
5385
|
+
time_range: ParsedTimeRange | null;
|
|
5386
|
+
agent_names: string[];
|
|
5387
|
+
event_types: string[];
|
|
5388
|
+
intent_phrase: string;
|
|
5389
|
+
ambiguity_flags: AmbiguityFlag[];
|
|
5390
|
+
/** 0..1; 1.0 = every component resolved cleanly. */
|
|
5391
|
+
parse_confidence: number;
|
|
5392
|
+
}
|
|
5393
|
+
/**
|
|
5394
|
+
* Read-only registry view consumed by `parseQuery` for agent-name
|
|
5395
|
+
* extraction. The grammar matches operator query tokens against the
|
|
5396
|
+
* `agent_id` field of each record (case-insensitive, prefix-or-exact).
|
|
5397
|
+
*
|
|
5398
|
+
* Wired from `HubAgentRegistrySource.list()` at the service layer; tests
|
|
5399
|
+
* can hand a literal array. Both shapes are accepted so callers do not
|
|
5400
|
+
* have to wrap a hub registry in an adapter just to feed the parser.
|
|
5401
|
+
*/
|
|
5402
|
+
type AgentRegistryView = readonly {
|
|
5403
|
+
agent_id: string;
|
|
5404
|
+
}[] | {
|
|
5405
|
+
list: () => readonly {
|
|
5406
|
+
agent_id: string;
|
|
5407
|
+
}[];
|
|
5408
|
+
};
|
|
5409
|
+
/**
|
|
5410
|
+
* Optional LLM-assist completion. Invoked by `parseQueryWithLlmAssist`
|
|
5411
|
+
* when the rule-based parse confidence is below `LLM_ASSIST_THRESHOLD`.
|
|
5412
|
+
* The classifier sees the query plus the partial parse and may return
|
|
5413
|
+
* `null` (no completion) or a partial-shape patch the caller merges.
|
|
5414
|
+
*
|
|
5415
|
+
* The runtime caller (operator-chat-service) MUST route this through
|
|
5416
|
+
* the substrate selector at the `concierge` surface so no new outbound
|
|
5417
|
+
* channel opens. See Castle-walking discipline at the top of the file.
|
|
5418
|
+
*/
|
|
5419
|
+
type LlmAssistGrammarCompletion = (query: string, partial: ParsedQuery) => Promise<Partial<{
|
|
5420
|
+
time_range: ParsedTimeRange;
|
|
5421
|
+
agent_names: string[];
|
|
5422
|
+
event_types: string[];
|
|
5423
|
+
}> | null>;
|
|
5424
|
+
|
|
5425
|
+
/**
|
|
5426
|
+
* Sanctuary MCP Server, Operator Chat, Concierge Dynamic Context Router
|
|
5427
|
+
*
|
|
5428
|
+
* WP-V1.3-9 Tau-3 (criterion (c) of the v1.3 scope-lock revision). Builds
|
|
5429
|
+
* on Tau-1 (PR #164, concierge memory store) and Tau-2 (PR #166, multi-turn
|
|
5430
|
+
* coherence read-fold path). At each operator turn the concierge fetches
|
|
5431
|
+
* live data relevant to the operator's query and folds it into context
|
|
5432
|
+
* between the static Sanctuary reference and the prior-turns fold.
|
|
5433
|
+
*
|
|
5434
|
+
* Tau-4 (PR #_, criterion (d)) layers an operator-query grammar on top:
|
|
5435
|
+
* the caller (`operator-chat-service`) parses the query into a
|
|
5436
|
+
* `ParsedQuery` via `concierge-query-grammar`, threads the result into
|
|
5437
|
+
* `classifyQuery` + `foldContext`, and the matches carry the parsed
|
|
5438
|
+
* grammar through to fetchers as `FetcherHints` (time range + agent
|
|
5439
|
+
* names + event types). Existing fetcher implementations that do not
|
|
5440
|
+
* read the second argument continue to work unchanged.
|
|
5441
|
+
*
|
|
5442
|
+
* The module is intentionally pure: keyword classification + a small
|
|
5443
|
+
* amount of hint extraction + a fold orchestrator that calls caller-
|
|
5444
|
+
* provided fetchers. The wiring layer assembles the concrete fetchers
|
|
5445
|
+
* (templates registry, agent registry, audit log, sentinel store, etc.)
|
|
5446
|
+
* so this module stays test-isolatable and composable.
|
|
5447
|
+
*
|
|
5448
|
+
* Castle-walking discipline:
|
|
5449
|
+
* - No new outbound surface; the optional auxiliary classifier is invoked
|
|
5450
|
+
* through the substrate selector by the caller (the service threads the
|
|
5451
|
+
* classifier callback through, this module never owns the channel).
|
|
5452
|
+
* - Encryption boundaries hold for the underlying data sources; this
|
|
5453
|
+
* module receives plain strings already produced inside the fortress.
|
|
5454
|
+
* - Failure-mode handling is fail-soft: a fetcher failure omits its
|
|
5455
|
+
* category from the fold and surfaces an audit event through the
|
|
5456
|
+
* caller's onFetcherFailure hook. The user-facing concierge query is
|
|
5457
|
+
* never broken by a context-assembly failure.
|
|
5458
|
+
*
|
|
5459
|
+
* Out of scope here (Tau-5):
|
|
5460
|
+
* - Agent-context awareness (proactive folding without an explicit
|
|
5461
|
+
* query). The router still routes off explicit query content only.
|
|
5462
|
+
*/
|
|
5463
|
+
|
|
5464
|
+
/**
|
|
5465
|
+
* Closed enum of categories the router may classify a query into. Adding
|
|
5466
|
+
* a category requires updating: this enum, the keyword table, the
|
|
5467
|
+
* fetcher interface, every wiring fetcher, and the test surface.
|
|
5468
|
+
*/
|
|
5469
|
+
type ContextCategory = "templates" | "agent_state" | "agent_activity" | "audit_log" | "sentinel_findings" | "anomaly_alerts" | "recent_receipts" | "verascore_deltas";
|
|
5470
|
+
/**
|
|
5471
|
+
* Structured hints passed to fetchers as the optional second argument
|
|
5472
|
+
* (Tau-4). Derived from `ParsedQuery` by the router; fetchers are free
|
|
5473
|
+
* to read or ignore individual fields. Tau-3 fetchers that take no
|
|
5474
|
+
* second argument continue to work because the slot is optional.
|
|
5475
|
+
*/
|
|
5476
|
+
interface FetcherHints {
|
|
5477
|
+
/** Time range derived from the operator's query, when one resolved. */
|
|
5478
|
+
time_range?: {
|
|
5479
|
+
start: Date;
|
|
5480
|
+
end: Date;
|
|
5481
|
+
relative_label?: string;
|
|
5482
|
+
};
|
|
5483
|
+
/**
|
|
5484
|
+
* All agent names the parser matched against the registry, in the
|
|
5485
|
+
* order they surfaced. Distinct from the `agentNameHint` first arg
|
|
5486
|
+
* (which carries only the first match for back-compat).
|
|
5487
|
+
*/
|
|
5488
|
+
agent_names?: readonly string[];
|
|
5489
|
+
/**
|
|
5490
|
+
* Event-type names the parser matched against the canonical audit-
|
|
5491
|
+
* event-class enumeration. Already filtered to the closed set.
|
|
5492
|
+
*/
|
|
5493
|
+
event_types?: readonly string[];
|
|
5494
|
+
}
|
|
5495
|
+
/**
|
|
5496
|
+
* Caller-supplied data sources. Each fetcher returns plain text the
|
|
5497
|
+
* router stitches into the rendered section. Returning the empty string
|
|
5498
|
+
* (or whitespace only) is treated as "nothing to fold for this category"
|
|
5499
|
+
* and the category drops out of the final categoriesIncluded list.
|
|
5500
|
+
*
|
|
5501
|
+
* Tau-4 extends every fetcher with an optional `hints?: FetcherHints`
|
|
5502
|
+
* argument carrying the parsed-query attachments (time range, agent
|
|
5503
|
+
* names, event types). Fetchers that ignore the argument continue to
|
|
5504
|
+
* compile; fetchers that consume it can scope queries against the
|
|
5505
|
+
* audit log, activity feed, etc., to the operator's stated window.
|
|
5506
|
+
*/
|
|
5507
|
+
interface ContextFetchers {
|
|
5508
|
+
templates: (hints?: FetcherHints) => Promise<string>;
|
|
5509
|
+
agent_state: (agentNameHint: string | null, hints?: FetcherHints) => Promise<string>;
|
|
5510
|
+
agent_activity: (agentNameHint: string | null, hints?: FetcherHints) => Promise<string>;
|
|
5511
|
+
audit_log: (hints?: FetcherHints) => Promise<string>;
|
|
5512
|
+
sentinel_findings: (hints?: FetcherHints) => Promise<string>;
|
|
5513
|
+
anomaly_alerts: (hints?: FetcherHints) => Promise<string>;
|
|
5514
|
+
recent_receipts: (hints?: FetcherHints) => Promise<string>;
|
|
5515
|
+
verascore_deltas: (hints?: FetcherHints) => Promise<string>;
|
|
5516
|
+
}
|
|
5517
|
+
/**
|
|
5518
|
+
* Auxiliary classifier the caller may supply for the LLM-assist fallback
|
|
5519
|
+
* path. Invoked only when keyword classification returns zero matches
|
|
5520
|
+
* AND the query is non-trivial (>= 8 chars, not a clear greeting).
|
|
5521
|
+
*/
|
|
5522
|
+
type LlmAssistClassifier = (query: string, categories: readonly ContextCategory[]) => Promise<ContextCategory | "none">;
|
|
5523
|
+
|
|
5318
5524
|
/**
|
|
5319
5525
|
* Sanctuary MCP Server — Operator Chat Service
|
|
5320
5526
|
*
|
|
@@ -5461,6 +5667,50 @@ interface OperatorChatServiceDeps {
|
|
|
5461
5667
|
* implementation run.
|
|
5462
5668
|
*/
|
|
5463
5669
|
conciergeClock?: () => number;
|
|
5670
|
+
/**
|
|
5671
|
+
* WP-V1.3-9 Tau-3: caller-supplied fetchers for dynamic context
|
|
5672
|
+
* injection. When wired alongside the substrate selector, every
|
|
5673
|
+
* `sendConcierge` round-trip routes the operator's query through the
|
|
5674
|
+
* concierge-context-router and folds matching live data into the
|
|
5675
|
+
* substrate prompt between the static Sanctuary reference and the
|
|
5676
|
+
* prior-turns fold. Omit to disable dynamic context (the static
|
|
5677
|
+
* reference + prior-turns paths keep working unchanged).
|
|
5678
|
+
*/
|
|
5679
|
+
conciergeContextFetchers?: ContextFetchers;
|
|
5680
|
+
/**
|
|
5681
|
+
* WP-V1.3-9 Tau-3: optional LLM-assist classifier for queries that
|
|
5682
|
+
* fail keyword classification. Coordinator-CTO bake: when wired, the
|
|
5683
|
+
* service routes the auxiliary classifier call through the substrate
|
|
5684
|
+
* selector at the same `concierge` surface, so the auxiliary call
|
|
5685
|
+
* shares the operator's substrate choice and never opens a new
|
|
5686
|
+
* outbound surface.
|
|
5687
|
+
*/
|
|
5688
|
+
conciergeContextLlmAssist?: LlmAssistClassifier;
|
|
5689
|
+
/**
|
|
5690
|
+
* WP-V1.3-9 Tau-3: rough token budget for the dynamic-context fold.
|
|
5691
|
+
* Defaults to `DEFAULT_CONCIERGE_DYNAMIC_CONTEXT_BUDGET` (2000).
|
|
5692
|
+
*/
|
|
5693
|
+
conciergeDynamicContextBudget?: number;
|
|
5694
|
+
/**
|
|
5695
|
+
* WP-V1.3-9 Tau-4: read-only registry view consumed by the operator-
|
|
5696
|
+
* query grammar to extract agent-name parameters. Wired from
|
|
5697
|
+
* `HubAgentRegistrySource.list()` at the hub-service layer; absent
|
|
5698
|
+
* services skip agent-name extraction (the grammar still resolves
|
|
5699
|
+
* time ranges and event types). The registry is read at every
|
|
5700
|
+
* `sendConcierge` call so newly-wrapped agents are visible without
|
|
5701
|
+
* service reconstruction.
|
|
5702
|
+
*/
|
|
5703
|
+
conciergeAgentRegistry?: AgentRegistryView;
|
|
5704
|
+
/**
|
|
5705
|
+
* WP-V1.3-9 Tau-4: optional LLM-assist completion for low-confidence
|
|
5706
|
+
* grammar parses. When wired alongside the substrate selector, the
|
|
5707
|
+
* service routes the call through the substrate selector at the
|
|
5708
|
+
* `concierge` surface, so the grammar fallback shares the operator's
|
|
5709
|
+
* substrate choice and never opens a new outbound surface. Distinct
|
|
5710
|
+
* from `conciergeContextLlmAssist`, which classifies queries into
|
|
5711
|
+
* fetcher categories; this hook completes structured grammar fields.
|
|
5712
|
+
*/
|
|
5713
|
+
conciergeGrammarLlmAssist?: LlmAssistGrammarCompletion;
|
|
5464
5714
|
}
|
|
5465
5715
|
declare class OperatorChatService {
|
|
5466
5716
|
private store;
|
|
@@ -5476,6 +5726,11 @@ declare class OperatorChatService {
|
|
|
5476
5726
|
private historyTokenBudget;
|
|
5477
5727
|
private sessionTtlMs;
|
|
5478
5728
|
private clock;
|
|
5729
|
+
private contextFetchers?;
|
|
5730
|
+
private contextLlmAssist?;
|
|
5731
|
+
private dynamicContextBudget;
|
|
5732
|
+
private agentRegistry?;
|
|
5733
|
+
private grammarLlmAssist?;
|
|
5479
5734
|
/**
|
|
5480
5735
|
* In-memory thread_id assigned to the active concierge session.
|
|
5481
5736
|
* The first sendConcierge call after construction allocates a fresh
|
|
@@ -5556,10 +5811,13 @@ declare class OperatorChatService {
|
|
|
5556
5811
|
* ## Sanctuary reference
|
|
5557
5812
|
* <static domain reference block>
|
|
5558
5813
|
*
|
|
5814
|
+
* ## Live fortress context ← WP-V1.3-9 Tau-3, when present
|
|
5815
|
+
* ### <Category>
|
|
5816
|
+
* <fetcher payload>
|
|
5817
|
+
*
|
|
5559
5818
|
* ## Prior conversation ← WP-V1.3-9 Tau-2, when present
|
|
5560
5819
|
* OPERATOR: ...
|
|
5561
5820
|
* CONCIERGE: ...
|
|
5562
|
-
* ---
|
|
5563
5821
|
*
|
|
5564
5822
|
* ## Recent activity
|
|
5565
5823
|
* <recentActivity output>
|
|
@@ -5579,6 +5837,35 @@ declare class OperatorChatService {
|
|
|
5579
5837
|
* serialization is the canonical path for v1.3.
|
|
5580
5838
|
*/
|
|
5581
5839
|
private assembleConciergeContext;
|
|
5840
|
+
/**
|
|
5841
|
+
* Run the WP-V1.3-9 Tau-3 dynamic-context fold for a single round-
|
|
5842
|
+
* trip. Fail-soft on every axis: missing fetchers short-circuit to
|
|
5843
|
+
* an empty fold, fetcher failures emit a per-category audit event
|
|
5844
|
+
* and are omitted from the rendered section, an LLM-assist failure
|
|
5845
|
+
* proceeds with no fold. Returns the rendered section + the list of
|
|
5846
|
+
* categories whose data made it into the section (used for the
|
|
5847
|
+
* round-trip audit emission).
|
|
5848
|
+
*
|
|
5849
|
+
* Tau-4: receives the pre-parsed `ParsedQuery` and forwards it as the
|
|
5850
|
+
* `parsed` opt to `foldContext`, so fetchers see the structured
|
|
5851
|
+
* `FetcherHints` derived from it.
|
|
5852
|
+
*/
|
|
5853
|
+
private runDynamicContextFold;
|
|
5854
|
+
/**
|
|
5855
|
+
* WP-V1.3-9 Tau-4: parse the (PII-filtered) operator query into a
|
|
5856
|
+
* `ParsedQuery`. Routes through the LLM-assist completion hook when
|
|
5857
|
+
* configured and the rule-based parse is below
|
|
5858
|
+
* `LLM_ASSIST_THRESHOLD`. Always returns a parse object (never
|
|
5859
|
+
* throws) so the audit emission can carry the result unconditionally.
|
|
5860
|
+
*/
|
|
5861
|
+
private runGrammarParse;
|
|
5862
|
+
/**
|
|
5863
|
+
* Emit the WP-V1.3-9 Tau-3 fetcher-failure audit event. Pulled out
|
|
5864
|
+
* of the fold path so the dynamic-context handler stays readable.
|
|
5865
|
+
* Emits with `result: "failure"` since the named category dropped
|
|
5866
|
+
* from the rendered section for this round-trip.
|
|
5867
|
+
*/
|
|
5868
|
+
private emitContextFetcherFailed;
|
|
5582
5869
|
/**
|
|
5583
5870
|
* Render the prior-conversation section with token-budget enforcement
|
|
5584
5871
|
* (WP-V1.3-9 Tau-2). Drops oldest turns first when the rendered
|
|
@@ -6069,6 +6356,87 @@ interface V11Bindings {
|
|
|
6069
6356
|
operatorChatService?: OperatorChatService;
|
|
6070
6357
|
}
|
|
6071
6358
|
|
|
6359
|
+
/**
|
|
6360
|
+
* Sanctuary v1.3 WP-V1.3-10 Cross-Harness Approval Inbox Upsilon-3
|
|
6361
|
+
*
|
|
6362
|
+
* At-rest encrypted persistence for full request payloads tied to
|
|
6363
|
+
* AggregatedApproval entries. Sibling store to ApprovalAggregator: the
|
|
6364
|
+
* aggregator's own entry record (status, provenance, hashes) is persisted
|
|
6365
|
+
* by the aggregator class under `_approval_aggregator`; this store holds
|
|
6366
|
+
* the original request payload separately under
|
|
6367
|
+
* `_approval_aggregator_payloads` so the operator can replay the full
|
|
6368
|
+
* payload after a server restart.
|
|
6369
|
+
*
|
|
6370
|
+
* Storage layout:
|
|
6371
|
+
* namespace: `_approval_aggregator_payloads`
|
|
6372
|
+
* key: `payload.{aggregator_id}` (one record per approval)
|
|
6373
|
+
* payload: AES-256-GCM ciphertext of the JSON-serialised bundle.
|
|
6374
|
+
* key: `l2-approval-aggregator-payload-v1` HKDF subkey of fortress
|
|
6375
|
+
* master.
|
|
6376
|
+
* AAD: UTF-8 bytes of `aggregator_id`. Swapping records across
|
|
6377
|
+
* aggregator_ids breaks the auth tag. Castle-walking
|
|
6378
|
+
* discipline: the encryption boundary holds even against
|
|
6379
|
+
* on-disk shuffle.
|
|
6380
|
+
*
|
|
6381
|
+
* Retention:
|
|
6382
|
+
* Each bundle carries an ISO-8601 `retention_until`. `pruneExpired()`
|
|
6383
|
+
* iterates payloads and drops entries whose retention_until is in the
|
|
6384
|
+
* past. Default retention is 30 days, mirroring the audit-log retention
|
|
6385
|
+
* envelope. Operator override flows through the aggregator's deps.
|
|
6386
|
+
*
|
|
6387
|
+
* Multi-fortress isolation:
|
|
6388
|
+
* The HKDF subkey is derived from the fortress master key. Two
|
|
6389
|
+
* fortresses never produce identical encryption keys for identical
|
|
6390
|
+
* aggregator_ids. Isolation is enforced cryptographically; the AAD
|
|
6391
|
+
* binds the ciphertext to a specific aggregator_id within a fortress.
|
|
6392
|
+
*/
|
|
6393
|
+
|
|
6394
|
+
interface AggregatorPayloadStoreOptions {
|
|
6395
|
+
/** Storage backend that persists the encrypted bundles. */
|
|
6396
|
+
storage: StorageBackend;
|
|
6397
|
+
/** 32-byte fortress master key. */
|
|
6398
|
+
masterKey: Uint8Array;
|
|
6399
|
+
/** Stable fortress id stamped on every bundle for audit clarity. */
|
|
6400
|
+
fortressId: string;
|
|
6401
|
+
/** Operator-tunable retention window. Default 30 days. */
|
|
6402
|
+
retentionDays?: number;
|
|
6403
|
+
}
|
|
6404
|
+
/**
|
|
6405
|
+
* Encrypted, AAD-bound, retention-aware payload persistence for the
|
|
6406
|
+
* cross-harness approval inbox.
|
|
6407
|
+
*/
|
|
6408
|
+
declare class AggregatorPayloadStore {
|
|
6409
|
+
private readonly storage;
|
|
6410
|
+
private readonly encryptionKey;
|
|
6411
|
+
private readonly fortressId;
|
|
6412
|
+
private readonly retentionDays;
|
|
6413
|
+
constructor(opts: AggregatorPayloadStoreOptions);
|
|
6414
|
+
/**
|
|
6415
|
+
* Persist `payload` under the given aggregator_id. Idempotent; calling
|
|
6416
|
+
* twice with the same id rewrites the bundle (retention_until is
|
|
6417
|
+
* recomputed). Returns the bundle's retention_until ISO-8601 timestamp
|
|
6418
|
+
* so callers can log it.
|
|
6419
|
+
*/
|
|
6420
|
+
savePayload(aggregatorId: string, payload: unknown): Promise<string>;
|
|
6421
|
+
/**
|
|
6422
|
+
* Read the persisted payload for the aggregator_id. Returns null if no
|
|
6423
|
+
* bundle exists, the bundle is corrupted, or AAD binding fails.
|
|
6424
|
+
*/
|
|
6425
|
+
loadPayload(aggregatorId: string): Promise<unknown>;
|
|
6426
|
+
/**
|
|
6427
|
+
* Delete the persisted payload. Returns true when a bundle was removed,
|
|
6428
|
+
* false when none existed.
|
|
6429
|
+
*/
|
|
6430
|
+
deletePayload(aggregatorId: string): Promise<boolean>;
|
|
6431
|
+
/**
|
|
6432
|
+
* Drop expired payload bundles. Returns the count of bundles pruned.
|
|
6433
|
+
* Caller wires this into the cocoon-unlock initialization path.
|
|
6434
|
+
*/
|
|
6435
|
+
pruneExpired(now?: Date): Promise<{
|
|
6436
|
+
pruned: number;
|
|
6437
|
+
}>;
|
|
6438
|
+
}
|
|
6439
|
+
|
|
6072
6440
|
/**
|
|
6073
6441
|
* Sanctuary v1.3 WP-V1.3-10 Cross-Harness Approval Inbox Upsilon-1
|
|
6074
6442
|
*
|
|
@@ -6106,6 +6474,19 @@ interface V11Bindings {
|
|
|
6106
6474
|
* - `expired`: pending past TTL on a `list()` poll without a resolution.
|
|
6107
6475
|
*/
|
|
6108
6476
|
type AggregatedApprovalStatus = "pending" | "approved" | "denied" | "timeout" | "expired";
|
|
6477
|
+
/**
|
|
6478
|
+
* One step in the Castle Architecture enforcement chain that led to this
|
|
6479
|
+
* approval. Layers are: l1 (Castle Wall, OS-level egress), l2 (Sentinel +
|
|
6480
|
+
* cooperative MCP gate), l3 (selective disclosure), l4 (reputation).
|
|
6481
|
+
* v1.3 Upsilon-3 ships the schema; default resolver populates a single
|
|
6482
|
+
* `l2` entry. Future Castle Wall wiring will extend the chain when a
|
|
6483
|
+
* payload's egress was first observed by the kernel filter.
|
|
6484
|
+
*/
|
|
6485
|
+
interface EnforcementLayerEvent {
|
|
6486
|
+
layer: "l1" | "l2" | "l3" | "l4";
|
|
6487
|
+
event: string;
|
|
6488
|
+
timestamp: string;
|
|
6489
|
+
}
|
|
6109
6490
|
/**
|
|
6110
6491
|
* Normalized record the aggregator stores per approval. Field set is
|
|
6111
6492
|
* additive-stable; new fields go behind `?` so existing dashboards keep
|
|
@@ -6150,6 +6531,31 @@ interface AggregatedApproval {
|
|
|
6150
6531
|
* (the cross-link signal lives on the aggregator side only).
|
|
6151
6532
|
*/
|
|
6152
6533
|
hub_inbox_item_id?: string;
|
|
6534
|
+
/**
|
|
6535
|
+
* Castle Architecture enforcement-layer chain that led to this
|
|
6536
|
+
* approval. Populated by the optional `resolveEnforcementChain` deps
|
|
6537
|
+
* hook; default returns a single `l2` step (cooperative MCP gate
|
|
6538
|
+
* fired). Persisted with the entry so the operator-replay surface can
|
|
6539
|
+
* render the enforcement context after a server restart. v1.3
|
|
6540
|
+
* Upsilon-3.
|
|
6541
|
+
*/
|
|
6542
|
+
enforcement_chain?: EnforcementLayerEvent[];
|
|
6543
|
+
/**
|
|
6544
|
+
* Aggregator revision number stamped at entry creation. Stable across
|
|
6545
|
+
* status transitions. Used by the v1.4 mobile companion sync API to
|
|
6546
|
+
* separate "added" from "changed" deltas (created_at_revision >
|
|
6547
|
+
* sinceRevision means added; otherwise it is a status change).
|
|
6548
|
+
* v1.3 Upsilon-4.
|
|
6549
|
+
*/
|
|
6550
|
+
created_at_revision?: number;
|
|
6551
|
+
/**
|
|
6552
|
+
* Aggregator revision number stamped at the last mutation (creation,
|
|
6553
|
+
* resolution, expiration). Sync-API consumers compare against the
|
|
6554
|
+
* revision they last saw to compute the delta. Monotonically
|
|
6555
|
+
* increasing per fortress; gaps are normal (one bump per mutation).
|
|
6556
|
+
* v1.3 Upsilon-4.
|
|
6557
|
+
*/
|
|
6558
|
+
last_modified_revision?: number;
|
|
6153
6559
|
}
|
|
6154
6560
|
/**
|
|
6155
6561
|
* Source-context resolver. Called once per ingest to map gate context to
|
|
@@ -6206,11 +6612,35 @@ type ApprovalAggregatorUnsubscribe = () => void;
|
|
|
6206
6612
|
interface ApprovalAggregatorEmit {
|
|
6207
6613
|
/**
|
|
6208
6614
|
* `aggregated` on first ingest, `resolved` on a status leaving pending,
|
|
6209
|
-
* `deduped` when an ingest dropped because the same dedup key was seen
|
|
6615
|
+
* `deduped` when an ingest dropped because the same dedup key was seen,
|
|
6616
|
+
* `removed` when an entry was pruned (v1.3 Upsilon-4).
|
|
6210
6617
|
*/
|
|
6211
|
-
type: "aggregated" | "resolved" | "deduped";
|
|
6618
|
+
type: "aggregated" | "resolved" | "deduped" | "removed";
|
|
6212
6619
|
entry: AggregatedApproval;
|
|
6213
6620
|
}
|
|
6621
|
+
/**
|
|
6622
|
+
* Sync-API delta returned by `getSync()`. v1.3 Upsilon-4. Mobile
|
|
6623
|
+
* companions poll this for cheap state-sync without re-fetching the
|
|
6624
|
+
* full inbox. The `revision` field on the response is the aggregator's
|
|
6625
|
+
* current revision; pass it back as `sinceRevision` on the next call.
|
|
6626
|
+
*
|
|
6627
|
+
* `added` carries entries created after `sinceRevision`. `changed`
|
|
6628
|
+
* carries entries that existed at `sinceRevision` but have transitioned
|
|
6629
|
+
* status (resolved, expired) since. `removed` carries the
|
|
6630
|
+
* aggregator_ids of entries pruned after `sinceRevision`.
|
|
6631
|
+
*
|
|
6632
|
+
* Tombstone caveat: removal tombstones live in-process memory. Server
|
|
6633
|
+
* restart clears them. Mobile clients reconnecting after the server
|
|
6634
|
+
* restarted MUST re-bootstrap from `list()` rather than rely on the
|
|
6635
|
+
* sync delta. The v1.4 mobile companion build wires the bootstrap-on-
|
|
6636
|
+
* reconnect flow; v1.3 documents the constraint.
|
|
6637
|
+
*/
|
|
6638
|
+
interface ApprovalAggregatorSyncDelta {
|
|
6639
|
+
revision: number;
|
|
6640
|
+
added: AggregatedApproval[];
|
|
6641
|
+
changed: AggregatedApproval[];
|
|
6642
|
+
removed: string[];
|
|
6643
|
+
}
|
|
6214
6644
|
/**
|
|
6215
6645
|
* Constructor dependencies. `pendingTtlMs` and `maxListLimit` default to
|
|
6216
6646
|
* coordinator-CTO defaults; tests pass overrides for deterministic timing.
|
|
@@ -6238,6 +6668,23 @@ interface ApprovalAggregatorDeps {
|
|
|
6238
6668
|
* hub inbox store.
|
|
6239
6669
|
*/
|
|
6240
6670
|
resolveHubInboxItemId?: (event: ApprovalGateEvent) => string | undefined;
|
|
6671
|
+
/**
|
|
6672
|
+
* Optional at-rest payload store. When provided, the aggregator
|
|
6673
|
+
* persists each request payload via `savePayload` on ingest, and
|
|
6674
|
+
* rehydrates payloads on `getFullPayload` if the in-memory map lost
|
|
6675
|
+
* them (e.g. after a server restart). Upsilon-3 surface; absent in
|
|
6676
|
+
* Upsilon-1 / Upsilon-2 deployments, where payloads remain in-memory
|
|
6677
|
+
* only.
|
|
6678
|
+
*/
|
|
6679
|
+
payloadStore?: AggregatorPayloadStore;
|
|
6680
|
+
/**
|
|
6681
|
+
* Optional resolver for the Castle Architecture enforcement chain
|
|
6682
|
+
* leading to this approval. Default returns a single `l2` step
|
|
6683
|
+
* (cooperative MCP gate fired). When the Castle Wall (Layer 1) ships,
|
|
6684
|
+
* its kernel-filter observer can populate richer chains by passing a
|
|
6685
|
+
* resolver here.
|
|
6686
|
+
*/
|
|
6687
|
+
resolveEnforcementChain?: (event: ApprovalGateEvent) => EnforcementLayerEvent[];
|
|
6241
6688
|
}
|
|
6242
6689
|
/**
|
|
6243
6690
|
* Aggregator state. The map is hydrated lazily from the encrypted
|
|
@@ -6254,6 +6701,8 @@ declare class ApprovalAggregator {
|
|
|
6254
6701
|
private readonly now;
|
|
6255
6702
|
private readonly resolveSourceContext;
|
|
6256
6703
|
private readonly resolveHubInboxItemId;
|
|
6704
|
+
private readonly payloadStore;
|
|
6705
|
+
private readonly resolveEnforcementChain;
|
|
6257
6706
|
/** Cached entries by `aggregator_id`. */
|
|
6258
6707
|
private readonly entries;
|
|
6259
6708
|
/** Dedup index: `${harness}|${agent}|${audit_id}` -> aggregator_id. */
|
|
@@ -6266,12 +6715,63 @@ declare class ApprovalAggregator {
|
|
|
6266
6715
|
private hydrated;
|
|
6267
6716
|
/** Active SSE listeners. */
|
|
6268
6717
|
private readonly listeners;
|
|
6718
|
+
/**
|
|
6719
|
+
* Monotonic revision counter, bumped on every mutation (ingest of new
|
|
6720
|
+
* entry, resolve, expire, delete). Hydrated from max(last_modified_revision)
|
|
6721
|
+
* across persisted entries on first read; in-memory after that. v1.3
|
|
6722
|
+
* Upsilon-4.
|
|
6723
|
+
*/
|
|
6724
|
+
private currentRevision;
|
|
6725
|
+
/**
|
|
6726
|
+
* Removal tombstones: aggregator_id -> revision at removal. Used by the
|
|
6727
|
+
* sync API to surface "removed" entries to mobile consumers between
|
|
6728
|
+
* polls. In-memory only; server restart clears tombstones (mobile
|
|
6729
|
+
* bootstraps via `list()` on reconnect). v1.3 Upsilon-4.
|
|
6730
|
+
*/
|
|
6731
|
+
private readonly removedTombstones;
|
|
6269
6732
|
constructor(deps: ApprovalAggregatorDeps);
|
|
6270
6733
|
/**
|
|
6271
6734
|
* Subscribe an event listener. Returns an unsubscribe fn. SSE handlers
|
|
6272
6735
|
* use this to forward aggregator emissions to the dashboard.
|
|
6273
6736
|
*/
|
|
6274
6737
|
onEvent(listener: (event: ApprovalAggregatorEmit) => void): ApprovalAggregatorUnsubscribe;
|
|
6738
|
+
/**
|
|
6739
|
+
* Current aggregator revision. v1.3 Upsilon-4. Mobile companions
|
|
6740
|
+
* poll the lightweight `/revision` route to detect that something
|
|
6741
|
+
* changed before fetching a full sync delta.
|
|
6742
|
+
*/
|
|
6743
|
+
getRevision(): Promise<number>;
|
|
6744
|
+
/**
|
|
6745
|
+
* Compute a delta since `sinceRevision`. v1.3 Upsilon-4. Mobile
|
|
6746
|
+
* clients poll this for cheap state-sync. Behavior:
|
|
6747
|
+
* - `added`: entries whose `created_at_revision > sinceRevision`.
|
|
6748
|
+
* - `changed`: entries that existed at `sinceRevision` but had a
|
|
6749
|
+
* status transition (resolve, expire) since.
|
|
6750
|
+
* - `removed`: aggregator_ids deleted after `sinceRevision`.
|
|
6751
|
+
* - `revision`: current aggregator revision; pass this back as
|
|
6752
|
+
* `sinceRevision` on the next call.
|
|
6753
|
+
*
|
|
6754
|
+
* `limit` caps the total count returned across all three lists,
|
|
6755
|
+
* prioritized as added -> changed -> removed (newer-state first).
|
|
6756
|
+
* When more changes exist than fit, the next call with the returned
|
|
6757
|
+
* revision will pick up the rest because each entry's
|
|
6758
|
+
* last_modified_revision is unchanged by truncation.
|
|
6759
|
+
*/
|
|
6760
|
+
getSync(opts?: {
|
|
6761
|
+
sinceRevision?: number;
|
|
6762
|
+
limit?: number;
|
|
6763
|
+
}): Promise<ApprovalAggregatorSyncDelta>;
|
|
6764
|
+
/**
|
|
6765
|
+
* Delete an entry. Drops the in-memory record, the persisted bundle,
|
|
6766
|
+
* and the at-rest payload (if a payload store is wired). Records a
|
|
6767
|
+
* tombstone with the new revision so sync-API consumers see a
|
|
6768
|
+
* `removed` delta. Returns true when an entry was deleted, false on
|
|
6769
|
+
* unknown id. v1.3 Upsilon-4. Reserved for v1.4+ retention housekeeping;
|
|
6770
|
+
* Upsilon-4 ships the surface so mobile sync-API tests can exercise the
|
|
6771
|
+
* removal path.
|
|
6772
|
+
*/
|
|
6773
|
+
deleteEntry(aggregatorId: string): Promise<boolean>;
|
|
6774
|
+
private nextRevision;
|
|
6275
6775
|
/**
|
|
6276
6776
|
* Ingest a gate event. Returns the aggregator entry on first sight,
|
|
6277
6777
|
* `null` when deduped. Resolution events update the existing record;
|
|
@@ -6292,10 +6792,48 @@ declare class ApprovalAggregator {
|
|
|
6292
6792
|
}): Promise<AggregatedApproval[]>;
|
|
6293
6793
|
/**
|
|
6294
6794
|
* Return the original (unhashed) request payload for the entry. Returns
|
|
6295
|
-
* `null` when the entry is unknown
|
|
6296
|
-
*
|
|
6795
|
+
* `null` when the entry is unknown. When the in-memory payload map has
|
|
6796
|
+
* been evicted (e.g. after a server restart) and a `payloadStore` was
|
|
6797
|
+
* provided, the at-rest bundle is decrypted and the in-memory map is
|
|
6798
|
+
* refilled. Audit emission lives on the `*WithAudit` variant; this base
|
|
6799
|
+
* accessor is silent so internal callers can read without polluting the
|
|
6800
|
+
* audit trail.
|
|
6297
6801
|
*/
|
|
6298
6802
|
getFullPayload(aggregatorId: string): Promise<unknown>;
|
|
6803
|
+
/**
|
|
6804
|
+
* Return the entry record for the given id, or null when unknown.
|
|
6805
|
+
* Idempotent. v1.3 Upsilon-3.
|
|
6806
|
+
*/
|
|
6807
|
+
getEntry(aggregatorId: string): Promise<AggregatedApproval | null>;
|
|
6808
|
+
/**
|
|
6809
|
+
* Audited variant of `getFullPayload`. Emits the
|
|
6810
|
+
* `cross_harness_approval_payload_decrypted` audit event before
|
|
6811
|
+
* returning. Used by the operator-facing /payload replay route.
|
|
6812
|
+
* v1.3 Upsilon-3.
|
|
6813
|
+
*/
|
|
6814
|
+
getFullPayloadWithAudit(aggregatorId: string, operatorId: string): Promise<unknown>;
|
|
6815
|
+
/**
|
|
6816
|
+
* Return the audit-log entries that led to and surround this approval.
|
|
6817
|
+
* Best-effort matching: aggregator-side emissions (AGGREGATED, RESOLVED,
|
|
6818
|
+
* DEDUPED, replay events) all carry `details.aggregator_id` and link
|
|
6819
|
+
* directly. Gate-side emissions (`gate_*:operation`) do not carry the
|
|
6820
|
+
* aggregator id at v1.3, so they are matched via timestamp window
|
|
6821
|
+
* (entry.created_at to entry.resolved_at + 1s, or expires_at + 1s while
|
|
6822
|
+
* pending) and operation suffix. Emits AUDIT_TRAIL_VIEWED on call.
|
|
6823
|
+
* v1.3 Upsilon-3.
|
|
6824
|
+
*/
|
|
6825
|
+
getAuditTrail(aggregatorId: string, operatorId: string): Promise<AuditEntry[]>;
|
|
6826
|
+
/**
|
|
6827
|
+
* List historical (resolved) approvals. Excludes pending entries by
|
|
6828
|
+
* design: `list()` is the pending-inbox surface and `getHistory()` is
|
|
6829
|
+
* the resolved-replay surface. Emits REPLAYED on each call. v1.3
|
|
6830
|
+
* Upsilon-3.
|
|
6831
|
+
*/
|
|
6832
|
+
getHistory(opts: {
|
|
6833
|
+
status?: AggregatedApprovalStatus;
|
|
6834
|
+
sinceTs?: string;
|
|
6835
|
+
limit?: number;
|
|
6836
|
+
} | undefined, operatorId: string): Promise<AggregatedApproval[]>;
|
|
6299
6837
|
/**
|
|
6300
6838
|
* Resolve an entry. Used by both:
|
|
6301
6839
|
* 1. The gate wire-up on channel-decision return.
|