@sanctuary-framework/mcp-server 1.1.7 → 1.2.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.
package/dist/index.d.cts CHANGED
@@ -2965,6 +2965,9 @@ type HubDisplayTemplateArg = {
2965
2965
  } | {
2966
2966
  kind: "policy_id";
2967
2967
  value: string;
2968
+ } | {
2969
+ kind: "channel_template_id";
2970
+ value: string;
2968
2971
  } | {
2969
2972
  kind: "destination_category";
2970
2973
  value: PrivacyDestinationCategory;
@@ -4130,10 +4133,981 @@ type HubInboxAction = (typeof HUB_INBOX_ACTIONS)[number];
4130
4133
  declare const HUB_AGENT_CONTROL_ACTIONS: readonly ["pause", "resume", "restart", "unwrap", "lockdown"];
4131
4134
  type HubAgentControlAction = (typeof HUB_AGENT_CONTROL_ACTIONS)[number];
4132
4135
 
4133
- /** Channel-template identifiers per Walkthrough Key 10 LOCKED starter set. */
4134
- declare const CHANNEL_TEMPLATE_IDS: readonly ["read-outputs-only", "bidirectional-sync", "credential-share-scoped", "plan-inspect-read-only", "escrow-handoff"];
4136
+ /** Channel-template identifiers per v1.2 design-canonical starter set. */
4137
+ declare const CHANNEL_TEMPLATE_IDS: readonly ["request-approve-act", "read-then-report", "scheduled-digest", "plan-draft-only", "fortress-relay", "concierge-loop"];
4135
4138
  type ChannelTemplateId = (typeof CHANNEL_TEMPLATE_IDS)[number];
4136
4139
 
4140
+ /**
4141
+ * Sanctuary MCP Server — Intelligence Substrate Selector Types
4142
+ *
4143
+ * The substrate selector lets operators pick (and re-pick) the LLM substrate
4144
+ * that powers Sanctuary's intelligence-layer surfaces: concierge, sentinel
4145
+ * scoring, gate explanation, privacy filter Tier 2, template suggestion,
4146
+ * direct-agent gate advisory.
4147
+ *
4148
+ * Per Erik directive 2026-04-29: the selector IS the architecture. Sanctuary's
4149
+ * job is to make the tradeoffs visible and let the operator choose. Multi-option
4150
+ * framing is preserved; no single substrate is surfaced as a winner.
4151
+ *
4152
+ * Per the position paper Intelligence_Layer_Drift_Audit_and_Substrate_Resolution_2026-04-29.md
4153
+ * (sections 4 + 5), four substrates ship as operator-pickable:
4154
+ *
4155
+ * - local: Ollama-hosted local model (Gemma 2 2B / Phi-4 Mini / Llama 3.1 8B)
4156
+ * - venice: Venice.ai privacy-respecting hosted relay
4157
+ * - frontier-with-filter: operator's frontier API account, Privacy Filter Tier 2 pre-egress
4158
+ * - hybrid: per-surface routing across the above
4159
+ * - disabled: surface refuses LLM calls; Tier 1 regex fallback for privacy filter
4160
+ *
4161
+ * Per-surface defaults (Erik ratification 2026-04-29):
4162
+ * - concierge: local (Gemma 2 2B)
4163
+ * - sentinel-scoring: local (rules + Phi-4 Mini on escalation)
4164
+ * - gate-explanation: templates v1.2; small LLM v1.3+ for custom policies
4165
+ * - privacy-filter-tier-2: local (NER + small LLM PII classifier)
4166
+ * - direct-agent-gate-advisor: same as concierge by default
4167
+ * - template-suggestion: same as concierge by default
4168
+ */
4169
+ /**
4170
+ * The set of intelligence-layer surfaces the selector routes for.
4171
+ *
4172
+ * The enum is closed. Adding a surface requires updating the selector's
4173
+ * default-config builder, the audit emission shape registry, and the
4174
+ * transparency UI in the same PR.
4175
+ */
4176
+ type Surface = "concierge" | "direct-agent-gate-advisor" | "sentinel-scoring" | "gate-explanation" | "privacy-filter-tier-2" | "template-suggestion";
4177
+ /**
4178
+ * The substrate choice an operator may bind to a surface.
4179
+ */
4180
+ type SubstrateChoice = "local" | "venice" | "frontier-with-filter" | "hybrid" | "disabled";
4181
+ /**
4182
+ * Local-model picks for the Ollama-backed local substrate.
4183
+ *
4184
+ * Bundled tiers per position paper §5 hardware-tier table:
4185
+ * - gemma-2-2b (Baseline / 8GB RAM)
4186
+ * - phi-4-mini (Mid / 16GB RAM)
4187
+ * - llama-3.1-8b (Pro / 32GB+ RAM)
4188
+ *
4189
+ * Custom Ollama-installed models can be specified via `customModelTag` on the
4190
+ * substrate config; the selector probes Ollama for availability at runtime.
4191
+ */
4192
+ type LocalModelPick = "gemma-2-2b" | "phi-4-mini" | "llama-3.1-8b";
4193
+ /**
4194
+ * Frontier providers operator may pick for the frontier-with-filter substrate.
4195
+ * v1.2 ships three; v1.3+ may add Mistral / xAI / Cohere.
4196
+ */
4197
+ type FrontierProvider = "anthropic" | "openai" | "google";
4198
+ /**
4199
+ * What the selector does when its chosen substrate fails (e.g. Ollama not
4200
+ * running, Venice API unreachable, frontier API key revoked).
4201
+ *
4202
+ * Per surface, operator-configurable. Defaults per position paper §5:
4203
+ * - concierge: degrade-silent (next-tier substrate)
4204
+ * - sentinel-scoring: conservative-deny (refuse rather than route elsewhere)
4205
+ * - privacy-filter-tier-2: degrade-silent (Tier 1 regex always works)
4206
+ * - others: conservative-deny
4207
+ */
4208
+ type FallbackBehavior = "conservative-deny" | "degrade-silent" | "disable-surface";
4209
+ /**
4210
+ * Per-surface routing rules for the hybrid substrate.
4211
+ *
4212
+ * v1.2 ships per-surface routing only — operator pre-binds each surface to a
4213
+ * concrete substrate. Per-query sensitivity classification routing is deferred
4214
+ * to v1.3+.
4215
+ */
4216
+ interface HybridRoutingRules {
4217
+ perSurface: Record<Surface, Exclude<SubstrateChoice, "hybrid">>;
4218
+ }
4219
+ /**
4220
+ * Operator-readable status badge for the transparency UI and chat headers.
4221
+ *
4222
+ * Backends emit a stable shape (no free-form prose); the dashboard's template
4223
+ * registry renders it. This mirrors the v1.1 hub `HubDisplayTemplateArg`
4224
+ * discipline so secret-leakage into operator-facing copy stays structurally
4225
+ * impossible.
4226
+ */
4227
+ interface SubstrateBadge {
4228
+ surface: Surface;
4229
+ substrate: SubstrateChoice;
4230
+ /** Stable badge label key resolved by dashboard template registry. */
4231
+ labelKey: string;
4232
+ /** Stable tradeoff body key resolved by dashboard template registry. */
4233
+ tradeoffKey: string;
4234
+ /** Stable status; green = working; yellow = degraded; red = disabled / failing. */
4235
+ status: "green" | "yellow" | "red";
4236
+ }
4237
+ /**
4238
+ * Frontier provider configs. Operator's API key per provider is stored
4239
+ * encrypted; never leaves the fortress except as outbound HTTPS to the
4240
+ * provider after Privacy Filter Tier 2 redaction.
4241
+ */
4242
+ interface FrontierProviderConfig {
4243
+ /** Operator's per-provider API key (encrypted at rest). */
4244
+ anthropic?: string;
4245
+ openai?: string;
4246
+ google?: string;
4247
+ }
4248
+ /**
4249
+ * Operator-overridable substrate config. Persisted encrypted under the
4250
+ * fortress master key in storage namespace `_intelligence`.
4251
+ *
4252
+ * v1.0 of this shape (`version: 1`) is the only shape this PR ships. Future
4253
+ * v1.x extensions (per-query sensitivity classifier, MLX runtime pick,
4254
+ * per-patron substrate scoping) MUST bump this version and provide a
4255
+ * forward-compat migration in the selector.
4256
+ */
4257
+ interface SubstrateConfig {
4258
+ version: 1;
4259
+ /** Operator's per-surface choice. Defaults from position paper §5. */
4260
+ perSurface: Record<Surface, SubstrateChoice>;
4261
+ /** Operator's local-model picks per surface, when 'local' is chosen. */
4262
+ localModelPicks: Partial<Record<Surface, LocalModelPick>>;
4263
+ /** Custom Ollama model tag override per surface (overrides localModelPicks if set). */
4264
+ customLocalModelTags?: Partial<Record<Surface, string>>;
4265
+ /** Ollama HTTP endpoint; defaults to http://localhost:11434. */
4266
+ ollamaEndpoint?: string;
4267
+ /** Venice API key, if 'venice' chosen for any surface. Encrypted at rest. */
4268
+ veniceApiKey?: string;
4269
+ /** Venice model selection; defaults to llama-3.1-70b. */
4270
+ veniceModel?: string;
4271
+ /** Frontier provider configs, if 'frontier-with-filter' chosen. */
4272
+ frontierConfig: FrontierProviderConfig;
4273
+ /** Hybrid routing rules, if 'hybrid' chosen for any surface. */
4274
+ hybridRules?: HybridRoutingRules;
4275
+ /** Fallback behavior per surface. */
4276
+ fallback: Record<Surface, FallbackBehavior>;
4277
+ /**
4278
+ * Operator preference for the picker modal: when true, picking a
4279
+ * substrate + key for any one surface fans out to every surface in one
4280
+ * save (Finding SS, v1.2.0-rc.1). Defaults to true; operators wanting
4281
+ * per-surface granularity flip the picker toggle off and the next
4282
+ * single-surface choice persists this back to false.
4283
+ */
4284
+ applyToAllSurfaces?: boolean;
4285
+ /** ISO8601 timestamp of last operator change. */
4286
+ updatedAt: string;
4287
+ }
4288
+ interface SummarizeRequest {
4289
+ kind: "summarize";
4290
+ context: string;
4291
+ query: string;
4292
+ maxTokens?: number;
4293
+ }
4294
+ interface ClassifyRequest {
4295
+ kind: "classify";
4296
+ items: string[];
4297
+ categories: string[];
4298
+ maxTokens?: number;
4299
+ }
4300
+ interface RedactRequest {
4301
+ kind: "redact";
4302
+ text: string;
4303
+ }
4304
+ /**
4305
+ * Per-substrate response envelope. Substrate clients return content + telemetry;
4306
+ * audit-emission and badge-rendering happen at the selector layer.
4307
+ */
4308
+ interface SubstrateResponse {
4309
+ /** The substrate that actually served this invocation (may differ from chosen if fallback fired). */
4310
+ servedBy: SubstrateChoice;
4311
+ /** Stable failure-class enum on failure; null on success. */
4312
+ failureClass: SubstrateFailureClass | null;
4313
+ /** Body shape varies by request kind. */
4314
+ body: {
4315
+ kind: "summarize";
4316
+ text: string;
4317
+ } | {
4318
+ kind: "classify";
4319
+ results: {
4320
+ category: string;
4321
+ confidence: number;
4322
+ }[];
4323
+ } | {
4324
+ kind: "redact";
4325
+ redacted: string;
4326
+ placeholders: Record<string, string>;
4327
+ } | {
4328
+ kind: "failure";
4329
+ message: string;
4330
+ };
4331
+ /** ISO8601 of when invocation completed. */
4332
+ completedAt: string;
4333
+ /** Total wall-clock latency in ms. */
4334
+ latencyMs: number;
4335
+ }
4336
+ /**
4337
+ * Stable failure-class enum. Backends return one of these when a substrate
4338
+ * invocation fails; the selector's audit emission carries this verbatim.
4339
+ *
4340
+ * Adding a new failure class requires updating the audit-event consumer and
4341
+ * the dashboard status-mapping in the same PR.
4342
+ */
4343
+ type SubstrateFailureClass = "substrate_unavailable" | "substrate_misconfigured" | "substrate_rate_limited" | "substrate_auth_failed" | "substrate_timeout" | "substrate_capability_unsupported" | "substrate_disabled" | "internal_error";
4344
+ /**
4345
+ * Capability profile for each substrate. Surfaces consult this before
4346
+ * invoking; if the substrate cannot serve the request kind, the selector
4347
+ * applies the fallback policy.
4348
+ */
4349
+ interface SubstrateCapability {
4350
+ summarize: boolean;
4351
+ classify: boolean;
4352
+ redact: boolean;
4353
+ }
4354
+ /**
4355
+ * The handle a surface holds after `selector.getSubstrate(surface)`. The
4356
+ * surface invokes via the typed methods; the substrate client implements them.
4357
+ *
4358
+ * Surfaces MUST treat any thrown error as a `substrate_unavailable` failure;
4359
+ * the selector's invoke wrapper catches and emits the audit event.
4360
+ */
4361
+ interface SubstrateHandle {
4362
+ surface: Surface;
4363
+ substrate: SubstrateChoice;
4364
+ badge: SubstrateBadge;
4365
+ capability: SubstrateCapability;
4366
+ /**
4367
+ * Optional operator-readable display string for the chat / dashboard headers.
4368
+ * Resolved from the badge labelKey; produced by the selector at handle issue,
4369
+ * not by the substrate client.
4370
+ */
4371
+ displayLabel: string;
4372
+ summarize?: (req: SummarizeRequest) => Promise<SubstrateResponse>;
4373
+ classify?: (req: ClassifyRequest) => Promise<SubstrateResponse>;
4374
+ redact?: (req: RedactRequest) => Promise<SubstrateResponse>;
4375
+ }
4376
+ /**
4377
+ * Operator-visible per-surface status report rendered by the transparency UI.
4378
+ *
4379
+ * The dashboard SPA consumes this via the `/api/hub/intelligence/status`
4380
+ * route; the picker modal consumes it before substrate selection to surface
4381
+ * hardware-capability hints.
4382
+ */
4383
+ interface SubstrateStatusReport {
4384
+ version: "1.2";
4385
+ generatedAt: string;
4386
+ surfaces: SurfaceStatus[];
4387
+ hardware: HardwareCapabilityReport;
4388
+ }
4389
+ interface SurfaceStatus {
4390
+ surface: Surface;
4391
+ chosen: SubstrateChoice;
4392
+ badge: SubstrateBadge;
4393
+ /** Substrate-side health probe result. */
4394
+ health: "ok" | "degraded" | "unavailable";
4395
+ /** Stable failure class when health != "ok"; null when "ok". */
4396
+ failureClass: SubstrateFailureClass | null;
4397
+ /**
4398
+ * Recent runtime + validation failures for this surface, capped at 5
4399
+ * entries and time-windowed to 24 hours. Sourced from real chat-call
4400
+ * outcomes (and validateKey results for substrates that expose it),
4401
+ * NOT from the static probe-time misconfig check.
4402
+ *
4403
+ * Per Finding VV (v1.2.0-rc.1): the operator-visible badge degrades to
4404
+ * "yellow" / "degraded" whenever this array is non-empty even if the
4405
+ * static probe still says ok. This closes the truth-telling gap where
4406
+ * the dashboard reported all surfaces healthy after the runtime chat
4407
+ * had already failed against the same substrate.
4408
+ */
4409
+ recentFailures: RecentFailureEntry[];
4410
+ }
4411
+ /**
4412
+ * One observed substrate failure surfaced via `/api/hub/intelligence/status`.
4413
+ * Carries enough metadata for the operator to triage (when, what failure
4414
+ * class, brief operator-safe snippet) without leaking request bodies,
4415
+ * response bodies, or operator credentials.
4416
+ *
4417
+ * Snippets are bounded human-readable strings (e.g. "venice configured
4418
+ * model not found") drawn from the substrate failure-message field; the
4419
+ * selector strips anything resembling an API key or PII before retention.
4420
+ * Operators wanting full forensic detail consult the L2 audit log; this
4421
+ * surface is the at-a-glance triage path.
4422
+ */
4423
+ interface RecentFailureEntry {
4424
+ /** ISO8601 timestamp of when the failure was observed. */
4425
+ ts: string;
4426
+ /** Stable failure-class enum carried verbatim from the substrate response. */
4427
+ failureClass: SubstrateFailureClass;
4428
+ /** Bounded operator-safe snippet describing the failure. */
4429
+ snippet: string;
4430
+ }
4431
+ interface HardwareCapabilityReport {
4432
+ /** Detected total RAM in GB. Self-reported via os.totalmem(). */
4433
+ totalRamGb: number;
4434
+ /** Apple Silicon family if detectable, or "other". */
4435
+ cpuArch: "apple-silicon-m1" | "apple-silicon-m2" | "apple-silicon-m3" | "apple-silicon-m4" | "apple-silicon-other" | "x86_64" | "other";
4436
+ /** Computed tier per position paper §5 (baseline / mid / pro / below-baseline). */
4437
+ tier: "below-baseline" | "baseline" | "mid" | "pro";
4438
+ /** Recommended local model tier per detected hardware. */
4439
+ recommendedLocalModel: LocalModelPick | null;
4440
+ /** Whether Ollama is reachable at the configured endpoint. */
4441
+ ollamaReachable: boolean;
4442
+ /** Models present in Ollama at the time of the report. Empty if not reachable. */
4443
+ ollamaModels: string[];
4444
+ }
4445
+
4446
+ /**
4447
+ * Sanctuary MCP Server — Frontier-with-Filter Substrate
4448
+ *
4449
+ * Operator's frontier API account (Anthropic / OpenAI / Google) wrapped with
4450
+ * mandatory pre-egress Privacy Filter Tier 2 redaction.
4451
+ *
4452
+ * Per position paper §5: every query routes through Privacy Filter Tier 2
4453
+ * BEFORE the frontier API call. The Privacy Filter event log captures the
4454
+ * pre-redaction match count so the operator can audit what was sent.
4455
+ *
4456
+ * Honesty discipline (CLAUDE.md):
4457
+ * - Tradeoff badge text explicitly names the leakage
4458
+ * - Required caveat about subtle PII (paraphrased addresses, contextual
4459
+ * identifiers) appears in tradeoff text
4460
+ * - Operator MUST acknowledge before configuration completes (enforced
4461
+ * by the picker modal in the dashboard SPA, deferred to the v1.2 UI
4462
+ * follow-up commit)
4463
+ *
4464
+ * Provider clients are kept minimal: one Chat Completions / Messages call
4465
+ * per surface request, no streaming in v1.2 (streaming is a v1.3 chat
4466
+ * surface enhancement).
4467
+ */
4468
+
4469
+ /**
4470
+ * Pre-egress redaction hook. The substrate adapter calls this on every
4471
+ * outbound text; returns the redacted text + placeholder map. The Privacy
4472
+ * Filter Tier 2 implementation lives in `l2-operational/privacy-filter.ts`
4473
+ * (extended in this PR to wire the substrate-aware Tier 2 path).
4474
+ *
4475
+ * For v1.2, the redactor passed in is responsible for emitting the audit
4476
+ * event `intelligence_pii_redaction_event` via the selector's auditLog
4477
+ * binding. The frontier substrate is provider-agnostic about how redaction
4478
+ * happens; it just refuses to call the frontier API on `failureClass !== null`.
4479
+ */
4480
+ type FrontierRedactor = (text: string) => Promise<{
4481
+ redacted: string;
4482
+ matchCount: number;
4483
+ }>;
4484
+
4485
+ /**
4486
+ * Sanctuary MCP Server — Intelligence Substrate Selector
4487
+ *
4488
+ * The selector IS the architecture (Erik directive 2026-04-29). Operators
4489
+ * pick (and re-pick) the LLM substrate that powers each intelligence-layer
4490
+ * surface; the selector binds choices to surfaces, provides a typed
4491
+ * `SubstrateHandle` to consumers, applies operator-configured fallback
4492
+ * behavior on substrate failure, and emits audit events for every change
4493
+ * and every invocation.
4494
+ *
4495
+ * Architecture per position paper section 5:
4496
+ * ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
4497
+ * │ concierge│ │ sentinel │ │ gate-expl│ │ priv-f-2 │ ... surfaces
4498
+ * └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
4499
+ * ▼ ▼ ▼ ▼
4500
+ * ┌──────────────────────────────────────────────────┐
4501
+ * │ SubstrateSelector (per-surface routing) │
4502
+ * └────┬──────────┬──────────┬──────────┬────────────┘
4503
+ * ▼ ▼ ▼ ▼
4504
+ * ┌────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
4505
+ * │ Local │ │ Venice │ │ Frontier │ │ Disabled │
4506
+ * │(Ollama)│ │ (relay) │ │ + filter │ │ (refuse) │
4507
+ * └────────┘ └──────────┘ └──────────┘ └──────────┘
4508
+ *
4509
+ * The hybrid substrate is per-surface routing only at v1.2 — operator
4510
+ * pre-binds each surface to a concrete substrate; per-query sensitivity
4511
+ * classification routing is deferred to v1.3+.
4512
+ *
4513
+ * Audit emission discipline:
4514
+ * Every public method that changes config or invokes a substrate appends
4515
+ * an `IntelligenceAuditPayload` to the L2 audit log. The selector never
4516
+ * stores raw request bodies, response bodies, or operator credentials in
4517
+ * audit details; only safe metadata (surface, substrate, hashes, latency,
4518
+ * failure-class enum). The event payload contracts live in
4519
+ * `contracts/v1.2/intelligence-events.ts`.
4520
+ *
4521
+ * Sovereignty invariants preserved:
4522
+ * - Operator API keys live in the encrypted SubstrateConfig persisted
4523
+ * under the fortress master key.
4524
+ * - Frontier-with-filter substrate ALWAYS routes through a redactor
4525
+ * before any frontier API call; the redactor argument is required in
4526
+ * the constructor; passing a null redactor disables the substrate.
4527
+ * - Sentinel-scoring surface defaults to `conservative-deny` fallback so
4528
+ * substrate failure cannot silently allow an unverified tool call.
4529
+ */
4530
+
4531
+ /**
4532
+ * Configuration the selector needs at construction. The audit-log binding
4533
+ * is required: a selector without an audit log violates the audit-emission
4534
+ * invariant and is structurally rejected.
4535
+ */
4536
+ interface SelectorConfig {
4537
+ storage: StorageBackend;
4538
+ masterKey: Uint8Array;
4539
+ auditLog: AuditLog;
4540
+ /** Identity that owns the substrate config. Stamped on every audit event. */
4541
+ identityId: string;
4542
+ /**
4543
+ * Pre-egress redactor for the frontier-with-filter substrate. Defaults
4544
+ * to `IDENTITY_REDACTOR` until Privacy Filter Tier 2 wires through.
4545
+ * Passing a custom redactor at construction allows tests + the Tier 2
4546
+ * commit to install a real implementation without modifying the
4547
+ * selector.
4548
+ */
4549
+ redactor?: FrontierRedactor;
4550
+ /**
4551
+ * Optional fetch override for substrate clients. Forwarded to Ollama,
4552
+ * Venice, and frontier client constructors so tests can stub out
4553
+ * network calls.
4554
+ */
4555
+ fetchImpl?: typeof fetch;
4556
+ }
4557
+ declare class SubstrateSelector {
4558
+ private store;
4559
+ private auditLog;
4560
+ private identityId;
4561
+ private redactor;
4562
+ private fetchImpl;
4563
+ private config;
4564
+ private loaded;
4565
+ /**
4566
+ * Per-surface ring buffer of recent runtime + validation failures. See
4567
+ * `RECENT_FAILURES_CAP` and `RECENT_FAILURES_WINDOW_MS`. Populated by
4568
+ * `recordRecentFailure()` from the `invoke()` failure path and from the
4569
+ * post-config validation hook on substrates that expose validateKey.
4570
+ * Drives the operator-visible degrade in `getOperatorVisibleStatus()`.
4571
+ */
4572
+ private recentFailures;
4573
+ constructor(cfg: SelectorConfig);
4574
+ /**
4575
+ * Load (or initialize) the operator's substrate config. Emits the
4576
+ * `intelligence_config_loaded` audit event regardless of branch so the
4577
+ * audit chain shows config-load activity on boot.
4578
+ */
4579
+ load(): Promise<void>;
4580
+ /**
4581
+ * Operator picked (or re-picked) a substrate for a surface. Persists the
4582
+ * change and emits `intelligence_substrate_chosen`. The picker modal
4583
+ * surfaces the tradeoff text BEFORE invoking this method; the audit
4584
+ * payload captures the hash of that text for auditor verification.
4585
+ */
4586
+ setPerSurfaceChoice(surface: Surface, substrate: SubstrateChoice): Promise<void>;
4587
+ /**
4588
+ * Apply a substrate choice to every surface in one save. Used by the
4589
+ * picker modal's "Apply to all surfaces" affordance (Finding SS,
4590
+ * v1.2.0-rc.1). Persists the per-surface map mutation in a single
4591
+ * write and emits ONE `intelligence_bulk_substrate_chosen` audit
4592
+ * event rather than six per-surface events.
4593
+ *
4594
+ * If `localModelPick` is provided AND substrate === "local", the same
4595
+ * pick is applied to every surface; ignored for other substrates.
4596
+ *
4597
+ * Per-surface bindings the operator had previously customized are
4598
+ * captured in the audit payload's `prior_substrates` map so the
4599
+ * forensic record makes the configuration discontinuity visible.
4600
+ */
4601
+ applyChoiceToAllSurfaces(substrate: SubstrateChoice, opts?: {
4602
+ localModelPick?: LocalModelPick | null;
4603
+ }): Promise<void>;
4604
+ /**
4605
+ * Persist the operator's "Apply to all surfaces" preference. The
4606
+ * picker modal calls this when the operator flips the toggle so the
4607
+ * preference survives a dashboard reload. Does not emit a discrete
4608
+ * audit event; the next bulk or per-surface choice carries the
4609
+ * operator-visible signal.
4610
+ */
4611
+ setApplyToAllPreference(value: boolean): Promise<void>;
4612
+ /**
4613
+ * Operator picked a local model for a surface bound to the local
4614
+ * substrate. Mirrors `setPerSurfaceChoice` for the model-picker case.
4615
+ * Does not emit a separate audit event class; the next
4616
+ * `intelligence_substrate_chosen` event captures the change and the
4617
+ * dashboard transparency UI calls this method only as part of a binding
4618
+ * flow that already audited the substrate choice.
4619
+ */
4620
+ setLocalModelPick(surface: Surface, pick: LocalModelPick | null): Promise<void>;
4621
+ /**
4622
+ * Set the Venice API key. Stored as part of the encrypted
4623
+ * SubstrateConfig record. Does not emit a substrate_chosen event by
4624
+ * itself; the dashboard flow always pairs key entry with a substrate
4625
+ * choice and the choice carries the audit semantics.
4626
+ *
4627
+ * Post-set validation (Finding VV, v1.2.0-rc.1): if a non-null key is
4628
+ * provided, the selector probes Venice with `validateKey()` and records
4629
+ * a failure entry on every Venice-bound surface when the result is not
4630
+ * `"ok"`. This lets the operator-visible status degrade from validation
4631
+ * outcomes alone, not just from runtime chat-call failures, so a broken
4632
+ * key or drifted model is visible before the operator's first chat
4633
+ * attempt. Probe failures (timeout, transport) are not recorded; the
4634
+ * runtime path will surface those when the operator actually invokes.
4635
+ */
4636
+ setVeniceApiKey(apiKey: string | null): Promise<void>;
4637
+ /**
4638
+ * Probe Venice with the given API key and record a failure entry on
4639
+ * every surface bound to Venice when the probe returns anything other
4640
+ * than `"ok"`. Failures map to stable substrate failure-class enum
4641
+ * values: `invalid-key` -> `substrate_auth_failed`, `invalid-model` ->
4642
+ * `substrate_misconfigured`, `unreachable` -> swallow (runtime path
4643
+ * will surface).
4644
+ */
4645
+ private validateVeniceAndRecord;
4646
+ /**
4647
+ * Set a frontier provider's API key. See `setVeniceApiKey` for the
4648
+ * audit-semantics rationale.
4649
+ */
4650
+ setFrontierApiKey(provider: FrontierProvider, apiKey: string | null): Promise<void>;
4651
+ /**
4652
+ * Set the hybrid routing rules. The picker modal calls this when the
4653
+ * operator saves the hybrid configuration tab. Validates the rules
4654
+ * (every surface bound, no `hybrid` recursion) before persist; throws
4655
+ * on invalid input so the operator-facing form can surface the failure.
4656
+ *
4657
+ * Setting rules does NOT change any surface's per-surface choice; the
4658
+ * operator must also pick `hybrid` for the surfaces they want routed
4659
+ * through these rules. This separation keeps the flow obvious in the
4660
+ * UI (set rules + then choose hybrid for each surface that should use
4661
+ * them) and lets the operator test rule changes without mass-flipping
4662
+ * surfaces to hybrid.
4663
+ */
4664
+ setHybridRules(rules: HybridRoutingRules): Promise<void>;
4665
+ /**
4666
+ * Override the per-surface fallback behavior. Defaults are set per
4667
+ * position paper section 5; the picker modal surfaces the tradeoff and
4668
+ * the operator can override.
4669
+ */
4670
+ setFallbackBehavior(surface: Surface, fallback: FallbackBehavior): Promise<void>;
4671
+ /**
4672
+ * Reset every binding to the per-surface defaults. Emits
4673
+ * `intelligence_config_reset` so post-reset audit trails make the
4674
+ * configuration discontinuity visible.
4675
+ */
4676
+ resetToDefaults(): Promise<void>;
4677
+ /**
4678
+ * Snapshot the current config (read-only). Tests + the dashboard
4679
+ * transparency UI consume this; mutations always go through the
4680
+ * setter methods so audit-emission stays consistent.
4681
+ */
4682
+ getConfig(): SubstrateConfig;
4683
+ /**
4684
+ * Install (or replace) the redactor used by the frontier-with-filter
4685
+ * substrate. Bootstrap code calls this after constructing the selector
4686
+ * with the default IDENTITY_REDACTOR; the Privacy Filter Tier 2
4687
+ * commit's `buildPrivacyTier2Redactor` produces the redactor that
4688
+ * closes over the selector for audit emission. Late-binding via this
4689
+ * method avoids the circular construction dependency
4690
+ * (selector needs redactor; redactor needs selector for emit).
4691
+ *
4692
+ * Subsequent calls to getSubstrate("...frontier-with-filter") will use
4693
+ * the installed redactor; already-issued handles continue to use the
4694
+ * redactor that was installed at handle-issue time. Consumers that
4695
+ * cache handles SHOULD re-issue after installRedactor.
4696
+ */
4697
+ installRedactor(redactor: FrontierRedactor): void;
4698
+ /**
4699
+ * Build a typed SubstrateHandle for the given surface. Lazily
4700
+ * instantiates the substrate client based on the operator's binding;
4701
+ * the handle carries the substrate label, tradeoff badge, capability
4702
+ * surface, and bound `summarize` / `classify` / `redact` methods that
4703
+ * delegate to the substrate client.
4704
+ *
4705
+ * For the `disabled` substrate, the returned handle has the capability
4706
+ * surface zeroed and no methods bound; consumers checking
4707
+ * `handle.capability.summarize === false` short-circuit to the
4708
+ * surface's static fallback (templates for gate-explanation, Tier 1
4709
+ * regex for privacy-filter-tier-2, etc.).
4710
+ *
4711
+ * For the `hybrid` substrate, this method delegates to the per-surface
4712
+ * routing rules (set via `setPerSurfaceChoice` on a substrate other
4713
+ * than `hybrid`); v1.2 ships per-surface routing only.
4714
+ */
4715
+ getSubstrate(surface: Surface): Promise<SubstrateHandle>;
4716
+ /**
4717
+ * Operator-visible per-surface status report. Consumers: the dashboard
4718
+ * transparency UI and the `/api/hub/intelligence/status` route.
4719
+ *
4720
+ * Probes hardware + Ollama at call time. The dashboard re-fetches every
4721
+ * 5 minutes to refresh status badges (degraded -> ok flip when Ollama
4722
+ * comes back up, and so on).
4723
+ */
4724
+ getOperatorVisibleStatus(): Promise<SubstrateStatusReport>;
4725
+ /**
4726
+ * Probe the host machine's hardware capability. v1.2 reports total RAM
4727
+ * + CPU arch (Apple Silicon family if detectable) and computes the tier
4728
+ * per position paper section 5. Used by the picker modal to surface
4729
+ * "below-baseline -> recommend Venice" guidance.
4730
+ */
4731
+ probeHardware(): Promise<HardwareCapabilityReport>;
4732
+ /**
4733
+ * Invoke a substrate with audit emission. Wraps the underlying
4734
+ * SubstrateHandle method, applies fallback behavior on failure per the
4735
+ * operator's per-surface config, and emits `intelligence_substrate_invoked`
4736
+ * + (on failure) `intelligence_substrate_failure`.
4737
+ *
4738
+ * Consumers SHOULD call this method rather than calling `handle.summarize`
4739
+ * etc. directly; the handle is exposed for cases where audit emission
4740
+ * needs to be explicitly skipped (probe paths, tests).
4741
+ */
4742
+ invokeSummarize(surface: Surface, req: SummarizeRequest): Promise<SubstrateResponse>;
4743
+ invokeClassify(surface: Surface, req: ClassifyRequest): Promise<SubstrateResponse>;
4744
+ invokeRedact(surface: Surface, req: RedactRequest): Promise<SubstrateResponse>;
4745
+ private ensureLoaded;
4746
+ private invoke;
4747
+ /**
4748
+ * Append a failure entry to the per-surface ring buffer. The selector
4749
+ * uses this in the `invoke()` failure path and in the post-config
4750
+ * `setVeniceApiKey` validation hook so the operator-visible badge
4751
+ * degrades from runtime AND from validation outcomes.
4752
+ *
4753
+ * Operator-safe by construction: snippets are bounded by
4754
+ * `FAILURE_SNIPPET_MAX_LEN` and pulled from substrate `failure.message`
4755
+ * fields (which substrates produce for operator-readable output and
4756
+ * never include API keys or PII). The cap + window prune happen on
4757
+ * every read in `recentFailuresFor()` so the ring buffer never grows.
4758
+ */
4759
+ private recordRecentFailure;
4760
+ /**
4761
+ * Read recent failures for a surface, pruning entries older than the
4762
+ * 24-hour window. Pruning happens lazily on read so a long-quiet surface
4763
+ * does not retain stale entries from yesterday's debugging.
4764
+ */
4765
+ private recentFailuresFor;
4766
+ /**
4767
+ * Test-only seam: lets selector tests assert on recorded failures
4768
+ * without poking the private ring buffer. Returns a defensive copy.
4769
+ */
4770
+ getRecentFailuresForTest(surface: Surface): RecentFailureEntry[];
4771
+ /**
4772
+ * Test-only seam: lets selector tests seed a recent-failure entry on a
4773
+ * surface so the buffer-clear behavior added for Finding ZZ (v1.2.0-rc.2)
4774
+ * can be exercised without spinning up a real failing substrate. Mirrors
4775
+ * `getRecentFailuresForTest`; never call from production paths.
4776
+ */
4777
+ recordRecentFailureForTest(surface: Surface, failureClass: SubstrateFailureClass, snippet: string): void;
4778
+ /**
4779
+ * Build a substrate handle for a surface bound to a concrete choice.
4780
+ * The substrate-client constructor is paid lazily so the selector
4781
+ * doesn't pre-spawn HTTP clients for substrates the operator never
4782
+ * touches.
4783
+ */
4784
+ private buildHandle;
4785
+ private disabledHandle;
4786
+ private localHandle;
4787
+ private veniceHandle;
4788
+ private frontierHandle;
4789
+ private probeSurfaceHealth;
4790
+ private makeBadge;
4791
+ private fallbackAction;
4792
+ /**
4793
+ * Internal hook for the Privacy Filter Tier 2 commit. Lets a redactor
4794
+ * audit-emit a `pii_redaction_event` payload through the selector
4795
+ * without exposing the AuditLog binding directly to redactor
4796
+ * implementations.
4797
+ */
4798
+ emitRedactionEvent(args: {
4799
+ surface: Surface;
4800
+ substrate: SubstrateChoice;
4801
+ matchCount: number;
4802
+ filterTier: 1 | 2;
4803
+ }): void;
4804
+ private emit;
4805
+ }
4806
+
4807
+ /**
4808
+ * Sanctuary MCP Server — Operator Chat Types
4809
+ *
4810
+ * Distinct from the existing agent-to-agent mesh chat (`chat-service.ts`).
4811
+ * Operator chat is the operator-fortress concierge surface: an operator
4812
+ * types into the v1.1 dashboard, Sanctuary persists the conversation,
4813
+ * audits every message, and routes responses through the substrate
4814
+ * selector.
4815
+ *
4816
+ * The direct-agent surface (operator-to-wrapped-agent conversation) was
4817
+ * removed in the v1.2 reshape. The concierge surface (operator-to-
4818
+ * Sanctuary) is the only operator chat surface that ships in v1.2.
4819
+ *
4820
+ * Sovereignty invariants:
4821
+ * - Conversation history is encrypted at rest under the fortress master
4822
+ * key via an HKDF-derived purpose key.
4823
+ * - Audit emission is non-optional: every operator submit and every
4824
+ * concierge reply lands in the L2 audit log.
4825
+ * - No raw message bodies are stored in audit `details`; only message
4826
+ * hashes and safe metadata. Bodies live in the encrypted chat store
4827
+ * that the operator can export, delete, or rotate.
4828
+ */
4829
+
4830
+ /**
4831
+ * The chat surface a message belongs to. Concierge is the only operator
4832
+ * chat surface in v1.2; the type stays a single-member union for
4833
+ * forward-compatibility if additional Sanctuary-side surfaces land later.
4834
+ */
4835
+ type OperatorChatSurface = "concierge";
4836
+ /**
4837
+ * Message-role enum. Concierge has two roles: `operator` (the human) and
4838
+ * `concierge` (Sanctuary's response).
4839
+ */
4840
+ type OperatorChatRole = "operator" | "concierge";
4841
+ /**
4842
+ * One message in a concierge thread. The body is stored encrypted at
4843
+ * rest; the in-memory shape carries the cleartext for service consumers,
4844
+ * and the audit emission carries only `message_hash` + safe metadata.
4845
+ */
4846
+ interface OperatorChatMessage {
4847
+ /** Stable message id (uuid) assigned at create time. */
4848
+ message_id: string;
4849
+ /** Surface this message belongs to. */
4850
+ surface: OperatorChatSurface;
4851
+ /** Sender role. */
4852
+ role: OperatorChatRole;
4853
+ /** Cleartext body. NEVER appears in audit `details`. */
4854
+ body: string;
4855
+ /** ISO8601 timestamp the message was created. */
4856
+ created_at: string;
4857
+ /**
4858
+ * For concierge response messages: which substrate served the response.
4859
+ * Null on operator-sent messages. Surfaces in the chat header badge.
4860
+ */
4861
+ served_by?: SubstrateChoice;
4862
+ /**
4863
+ * For concierge response messages: latency of the substrate call in ms.
4864
+ * Operator-side surfaces this in the message tooltip / activity feed.
4865
+ */
4866
+ substrate_latency_ms?: number;
4867
+ }
4868
+ /**
4869
+ * The shape persisted to disk under `_chat/<surface>/<thread-key>`.
4870
+ *
4871
+ * Threads are append-only; the persisted record is read-rewritten on
4872
+ * every message append. v1.2 caps thread length at
4873
+ * `OPERATOR_CHAT_MAX_THREAD_LENGTH`; older messages drop off when the
4874
+ * cap is exceeded.
4875
+ */
4876
+ interface OperatorChatThread {
4877
+ /** Forward-compat field. v1.2 uses 1; bump on schema change. */
4878
+ version: 1;
4879
+ /** Surface (concierge in v1.2). */
4880
+ surface: OperatorChatSurface;
4881
+ /** Thread key; concierge is fortress-scoped under `CONCIERGE_THREAD_KEY`. */
4882
+ thread_key: string;
4883
+ /** Append-only message log; oldest first. */
4884
+ messages: OperatorChatMessage[];
4885
+ /** ISO8601 timestamp the thread was last updated. */
4886
+ updated_at: string;
4887
+ }
4888
+ /**
4889
+ * Concierge response envelope. Carries the substrate response (or
4890
+ * fallback message) plus the operator-visible substrate badge.
4891
+ */
4892
+ interface ConciergeResponse {
4893
+ /** The response message persisted into the concierge thread. */
4894
+ message: OperatorChatMessage;
4895
+ /** The substrate that served this query. */
4896
+ served_by: SubstrateChoice;
4897
+ /** Operator-visible label rendered next to the response. */
4898
+ display_label: string;
4899
+ /** Outcome class for activity-feed projection. */
4900
+ outcome: "ok" | "substrate_failure" | "substrate_disabled";
4901
+ }
4902
+
4903
+ /**
4904
+ * Sanctuary MCP Server — Operator Chat Persistence
4905
+ *
4906
+ * Encrypted at-rest store for the operator-chat concierge thread.
4907
+ * Mirrors the `IntelligenceConfigStore` shape but addresses records by
4908
+ * `(surface, thread-key)` for forward-compatibility.
4909
+ *
4910
+ * Storage layout:
4911
+ * namespace: `_chat` (underscore-prefixed = reserved L1 namespace)
4912
+ * key: `concierge.{thread_key}`
4913
+ * payload: AES-256-GCM-encrypted `OperatorChatThread` JSON, key
4914
+ * derived via HKDF from the fortress master key with info
4915
+ * string `operator-chat-store-v1`.
4916
+ *
4917
+ * The direct-agent surface (per-agent threads + session records) was
4918
+ * removed in the v1.2 reshape. The concierge surface stores under a
4919
+ * single record with sentinel thread-key `_fortress`
4920
+ * (CONCIERGE_THREAD_KEY).
4921
+ *
4922
+ * Append-only semantics:
4923
+ * Threads are append-only at the message level. Persist is full-record
4924
+ * read-modify-write; the cap on thread length is enforced at append time
4925
+ * (oldest messages fall off when `OPERATOR_CHAT_MAX_THREAD_LENGTH` is
4926
+ * exceeded). The audit log retains every message regardless.
4927
+ */
4928
+
4929
+ /**
4930
+ * Encrypted operator chat persistence.
4931
+ */
4932
+ declare class OperatorChatStore {
4933
+ private storage;
4934
+ private encryptionKey;
4935
+ constructor(storage: StorageBackend, masterKey: Uint8Array);
4936
+ /**
4937
+ * Load a thread. Returns null if no record exists or if the on-disk
4938
+ * record is corrupt; callers treat both as "empty thread, start
4939
+ * fresh" and emit no audit event for the absence.
4940
+ */
4941
+ loadThread(surface: OperatorChatSurface, threadKey: string): Promise<OperatorChatThread | null>;
4942
+ /**
4943
+ * Persist a thread. Caller is responsible for cap enforcement; the
4944
+ * `appendMessage` helper below drops oldest messages before persist
4945
+ * when the cap is exceeded.
4946
+ */
4947
+ saveThread(thread: OperatorChatThread): Promise<void>;
4948
+ /**
4949
+ * Append a message to a thread, creating the thread if it doesn't
4950
+ * exist, and persist. Enforces the per-thread length cap by dropping
4951
+ * oldest messages first. Returns the persisted thread.
4952
+ */
4953
+ appendMessage(surface: OperatorChatSurface, threadKey: string, message: OperatorChatMessage): Promise<OperatorChatThread>;
4954
+ /**
4955
+ * Delete a thread. Used by operator-driven thread reset. The audit
4956
+ * log retains the per-message history regardless.
4957
+ */
4958
+ deleteThread(surface: OperatorChatSurface, threadKey: string): Promise<void>;
4959
+ }
4960
+
4961
+ /**
4962
+ * Sanctuary MCP Server — Operator Chat Service
4963
+ *
4964
+ * The operator-fortress concierge surface. Distinct from the
4965
+ * agent-to-agent mesh chat (`chat-service.ts`) which handles libp2p +
4966
+ * OpenMLS group sessions among wrapped agents.
4967
+ *
4968
+ * The direct-agent surface (operator-to-wrapped-agent conversation) was
4969
+ * removed in the v1.2 reshape; the concierge surface is the only
4970
+ * operator-chat surface in v1.2.
4971
+ *
4972
+ * Concierge surface (path: `sendConcierge`):
4973
+ * 1. PII-filter the operator query (Tier 1 regex).
4974
+ * 2. Build fortress context from audit log + activity feed +
4975
+ * agent registry (`assembleConciergeContext`).
4976
+ * 3. Invoke the substrate selector at `surface: "concierge"`.
4977
+ * 4. Persist operator query + concierge response in the concierge
4978
+ * thread under `_chat/concierge.<sentinel>`.
4979
+ * 5. Emit `operator_concierge_chat` audit event with safe metadata.
4980
+ *
4981
+ * Sovereignty invariants:
4982
+ * - Audit emission is non-optional on every public method that mutates
4983
+ * the chat state. Construction with no audit log is structurally
4984
+ * rejected.
4985
+ * - The substrate selector is OPTIONAL: a service constructed without
4986
+ * one degrades to "Concierge unavailable; substrate not configured".
4987
+ * The wiring layer can instantiate the service before the substrate
4988
+ * selector when bootstrap order requires it.
4989
+ * - The PII filter is the Tier 1 regex shipped in
4990
+ * `l2-operational/privacy-filter.ts`; Tier 2 NER+LLM redaction lives
4991
+ * in the substrate-selector layer (substrate-routed), not here.
4992
+ */
4993
+
4994
+ /**
4995
+ * Snapshot of fortress state surfaces the concierge consults to
4996
+ * answer operator queries. Caller (the hub-service wiring) supplies
4997
+ * the providers; the service wires them into the substrate prompt.
4998
+ *
4999
+ * Each provider returns plain strings the substrate selector folds
5000
+ * into a single context blob; the service does not attempt to
5001
+ * structure the prompt beyond stitching with newlines. Keeping the
5002
+ * prompt-shape decisions in the service-construction layer (rather
5003
+ * than in the wiring) lets the dashboard team iterate concierge
5004
+ * prompts without touching server code.
5005
+ */
5006
+ interface ConciergeContextProviders {
5007
+ /** Recent activity-feed entries as a free-form string, oldest first. */
5008
+ recentActivity: () => Promise<string>;
5009
+ /** Wrapped-agent inventory as a free-form string. */
5010
+ agentInventory: () => Promise<string>;
5011
+ /** Open inbox items as a free-form string. */
5012
+ openInbox: () => Promise<string>;
5013
+ }
5014
+ /**
5015
+ * PII filter the concierge applies to operator queries before passing
5016
+ * them to the substrate. v1.2 wires the Tier 1 regex; the substrate
5017
+ * selector handles Tier 2 NER+LLM redaction internally on egress paths.
5018
+ *
5019
+ * Returns the filtered query plus the count of redactions performed
5020
+ * (surfaced in the audit event for operator visibility).
5021
+ */
5022
+ interface ConciergePiiFilter {
5023
+ filter(input: string): {
5024
+ filtered: string;
5025
+ redactions: number;
5026
+ };
5027
+ }
5028
+ /**
5029
+ * Construction inputs for the operator chat service.
5030
+ */
5031
+ interface OperatorChatServiceDeps {
5032
+ /** Encrypted at-rest store; built from fortress master key. */
5033
+ store: OperatorChatStore;
5034
+ /** Audit log for `operator_concierge_chat` events. */
5035
+ auditLog: AuditLog;
5036
+ /** Operator identity that owns the chat session. */
5037
+ identityId: string;
5038
+ /**
5039
+ * Substrate selector for concierge LLM calls. Optional: when omitted
5040
+ * the concierge surface degrades to a "substrate not configured"
5041
+ * response.
5042
+ */
5043
+ substrateSelector?: SubstrateSelector;
5044
+ /**
5045
+ * Concierge context providers. Required when a substrate selector
5046
+ * is wired; otherwise unused. Wiring layer assembles these from the
5047
+ * activity feed, agent registry, and inbox aggregator.
5048
+ */
5049
+ conciergeContextProviders?: ConciergeContextProviders;
5050
+ /**
5051
+ * PII filter applied to concierge queries pre-substrate. Optional;
5052
+ * when omitted the service still works (no redactions performed).
5053
+ * v1.2 wiring passes the Tier 1 regex.
5054
+ */
5055
+ conciergePiiFilter?: ConciergePiiFilter;
5056
+ /**
5057
+ * Stable max tokens for concierge responses. Defaults to 512 (small
5058
+ * enough for snappy local-model latency, large enough for a useful
5059
+ * summary). Operator-tunable via dashboard config.
5060
+ */
5061
+ conciergeMaxTokens?: number;
5062
+ }
5063
+ declare class OperatorChatService {
5064
+ private store;
5065
+ private auditLog;
5066
+ private identityId;
5067
+ private substrateSelector?;
5068
+ private contextProviders?;
5069
+ private piiFilter?;
5070
+ private conciergeMaxTokens;
5071
+ constructor(deps: OperatorChatServiceDeps);
5072
+ /**
5073
+ * Operator submit on the concierge surface. Persists the operator's
5074
+ * query, fans out to the substrate selector, persists the response,
5075
+ * and emits the audit event.
5076
+ *
5077
+ * On substrate failure or absence: persists an honest "concierge
5078
+ * unavailable" response and audit-emits with `outcome` = the
5079
+ * appropriate failure class. The chat history reflects what the
5080
+ * operator sees on the page (no silent dropping).
5081
+ */
5082
+ sendConcierge(query: string): Promise<ConciergeResponse>;
5083
+ /**
5084
+ * Read the persisted concierge thread, oldest message first. Returns
5085
+ * an empty array when no thread exists yet.
5086
+ */
5087
+ getConciergeHistory(): Promise<OperatorChatMessage[]>;
5088
+ /**
5089
+ * Stitch fortress state into a single context blob the substrate
5090
+ * folds into its summarization prompt.
5091
+ *
5092
+ * The blob is intentionally plain text (not structured JSON) because
5093
+ * small local models (Gemma 2 2B) handle plain text more reliably
5094
+ * than nested structures. Format:
5095
+ *
5096
+ * ```
5097
+ * ## Recent activity
5098
+ * <recentActivity output>
5099
+ *
5100
+ * ## Wrapped agents
5101
+ * <agentInventory output>
5102
+ *
5103
+ * ## Open inbox
5104
+ * <openInbox output>
5105
+ * ```
5106
+ */
5107
+ private assembleConciergeContext;
5108
+ private emit;
5109
+ }
5110
+
4137
5111
  /**
4138
5112
  * Sanctuary v1.1. Operator Hub API Types
4139
5113
  *
@@ -4171,6 +5145,11 @@ interface HubTier1ApprovalEnqueuedResult {
4171
5145
  */
4172
5146
  operation_category: HubApprovalPendingItem["operation_category"];
4173
5147
  }
5148
+ interface HubTemplateBindingApprovalEnqueuedResult extends HubTier1ApprovalEnqueuedResult {
5149
+ current_template_id?: ChannelTemplateId;
5150
+ proposed_template_id: ChannelTemplateId;
5151
+ compiled_policy_id: string;
5152
+ }
4174
5153
  /**
4175
5154
  * Result returned from a fortress-scope Tier-1 action that has been
4176
5155
  * deferred to operator approval. Distinguished from the per-agent shape by
@@ -4225,11 +5204,7 @@ interface HubAgentController {
4225
5204
  * approval lands.
4226
5205
  */
4227
5206
  bindPolicy(agentId: string, policyId: string): Promise<void>;
4228
- /**
4229
- * Apply a non-Tier-1 channel-template binding. Channel templates are
4230
- * compositions over the canonical four slots; binding does not require
4231
- * Tier 1 approval at v1.1.
4232
- */
5207
+ /** Apply an operator-approved channel-template binding. */
4233
5208
  bindChannelTemplate(agentId: string, templateId: ChannelTemplateId): Promise<void>;
4234
5209
  }
4235
5210
  /**
@@ -4362,6 +5337,11 @@ interface HubServiceDeps {
4362
5337
  * returning the in-memory projection.
4363
5338
  */
4364
5339
  readPersistedLocalAgents?: () => LocalAgentRecord[];
5340
+ /**
5341
+ * Optional persisted agent writer. Used when approved hub actions mutate
5342
+ * registry-local fields that should survive refresh and restart.
5343
+ */
5344
+ writePersistedLocalAgents?: (records: LocalAgentRecord[]) => void;
4365
5345
  /** Inbox aggregation sources. */
4366
5346
  inboxSources: HubInboxSources;
4367
5347
  /** Activity feed sources. */
@@ -4391,6 +5371,44 @@ interface HubServiceDeps {
4391
5371
  * Defaults to `() => new Date()` when omitted.
4392
5372
  */
4393
5373
  now?: () => Date;
5374
+ /**
5375
+ * Optional operator-chat service. When supplied, the hub exposes the
5376
+ * concierge chat endpoints; when omitted, those routes return
5377
+ * HubCapabilityError. Construction-time injection lets the dashboard
5378
+ * wiring layer build the chat service against the same audit log +
5379
+ * master key + storage backend as the rest of the v1.1 hub without
5380
+ * forcing the chat-service constructor through a circular import.
5381
+ * The direct-agent chat surface was removed in the v1.2 reshape
5382
+ * (2026-04-30); the field name is retained for source compatibility.
5383
+ */
5384
+ operatorChat?: OperatorChatService;
5385
+ }
5386
+ /**
5387
+ * Click-to-inspect panel returned by `openAgentInspectPanel`. The panel
5388
+ * surfaces the agent's recent activity, pending approvals routed through
5389
+ * this agent, and policy summary at a glance, repurposing the click-
5390
+ * to-chat wire-up shipped in PR #98 + PR #100. The direct-agent chat
5391
+ * surface was removed in the v1.2 reshape; this panel is the operational
5392
+ * destination for the click.
5393
+ */
5394
+ interface HubAgentInspectPanel {
5395
+ /** The agent the panel was opened on. */
5396
+ agent_id: string;
5397
+ /** ISO8601 timestamp the panel was opened. */
5398
+ opened_at: string;
5399
+ /** Recent activity-feed entries for this agent, oldest first. */
5400
+ recent_activity: HubActivityFeedEntry[];
5401
+ /**
5402
+ * Open Tier 1 inbox items routed through this agent. Empty when the
5403
+ * agent has no pending approvals.
5404
+ */
5405
+ pending_approvals: HubInboxItem[];
5406
+ /**
5407
+ * Bound policy summary for this agent. Null when the agent has no
5408
+ * channel-template binding (e.g. immediately after wrap, before the
5409
+ * operator picks a template).
5410
+ */
5411
+ policy_summary: HubPolicySummary | null;
4394
5412
  }
4395
5413
 
4396
5414
  /**
@@ -4438,10 +5456,10 @@ declare class HubService {
4438
5456
  */
4439
5457
  bindAgentPolicy(agentId: string, policyId: string): HubTier1ApprovalEnqueuedResult;
4440
5458
  /**
4441
- * Bind a different channel template. Not Tier 1; channel templates are
4442
- * compositions over the four canonical slots and bind synchronously.
5459
+ * Bind a different channel template. Tier 1: enqueues a policy_change
5460
+ * approval item and only applies the binding after operator approval.
4443
5461
  */
4444
- bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown): Promise<LocalAgentRecord>;
5462
+ bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown): HubTemplateBindingApprovalEnqueuedResult;
4445
5463
  /**
4446
5464
  * Enqueue a fortress-scope Tier 1 lockdown. One inbox item is created;
4447
5465
  * on operator approval the handler iterates `agentController.lockdown`
@@ -4484,6 +5502,33 @@ declare class HubService {
4484
5502
  * Test/inspection helper. Not part of the public service surface.
4485
5503
  */
4486
5504
  inboxStoreSize(): number;
5505
+ private requireOperatorChat;
5506
+ /**
5507
+ * Operator submit on the concierge chat surface. Pass-through to the
5508
+ * operator-chat-service which owns substrate-selector routing, PII
5509
+ * filtering, persistence, and audit emission.
5510
+ */
5511
+ sendConcierge(query: string): Promise<ConciergeResponse>;
5512
+ /**
5513
+ * Read the persisted concierge thread.
5514
+ */
5515
+ getConciergeHistory(): Promise<OperatorChatMessage[]>;
5516
+ /**
5517
+ * Open the click-to-inspect/approve panel for a wrapped agent. The
5518
+ * panel surfaces recent activity routed through this agent, pending
5519
+ * Tier 1 approvals on this agent, and the bound policy summary,
5520
+ * repurposing the click-to-chat wire-up from PR #98 + PR #100 to a
5521
+ * substrate-aligned inspect destination after the v1.2 reshape removed
5522
+ * direct-agent chat.
5523
+ *
5524
+ * Synchronous open shape preserved (caller's `await` semantics
5525
+ * unchanged from the prior `openDirectAgentSession` surface). The
5526
+ * panel is read-only; no side effects beyond the audit-event emission
5527
+ * for the panel-open itself.
5528
+ */
5529
+ openAgentInspectPanel(agentId: string, options?: {
5530
+ activityLimit?: number;
5531
+ }): Promise<HubAgentInspectPanel>;
4487
5532
  }
4488
5533
 
4489
5534
  /**
@@ -4509,16 +5554,29 @@ declare class HubService {
4509
5554
  * the v1.2 work to project those into operator cards.
4510
5555
  * - Activity feed reads from the real audit log. This is the one source
4511
5556
  * that's already complete in v1.1.0 and just needs to be plugged in.
4512
- * - Agent controller errors on every action. v1.1.1 cannot honestly
4513
- * pause / unwrap / lockdown anything because no agent registry yet
4514
- * exists; the wiring returns `HubCapabilityError` rather than lying
4515
- * about what shipped.
5557
+ * - Agent controller errors on runtime harness actions that v1.1 cannot
5558
+ * honestly execute. Channel-template binding is registry-local in v1.2,
5559
+ * so its controller hook is a no-op and HubService persists the binding
5560
+ * after Tier 1 approval.
4516
5561
  */
4517
5562
 
4518
5563
  interface V11Bindings {
4519
5564
  hubService: HubService;
4520
5565
  identityId: string;
4521
5566
  fortressId: string;
5567
+ /**
5568
+ * Optional Intelligence Substrate Selector. Dispatch layer routes
5569
+ * `/api/hub/intelligence/*` here when set; absent means the routes
5570
+ * 503 with a "selector not configured" body.
5571
+ */
5572
+ intelligenceSelector?: SubstrateSelector;
5573
+ /**
5574
+ * Optional Operator Chat Service. Constructed when storage + masterKey
5575
+ * are passed to `buildV11Bindings`. The HubService accepts this
5576
+ * directly via its `operatorChat` dep; callers that want the harness-
5577
+ * side `recordAgentReply` integration also need the service handle.
5578
+ */
5579
+ operatorChatService?: OperatorChatService;
4522
5580
  }
4523
5581
 
4524
5582
  /**