@sanctuary-framework/mcp-server 1.1.7 → 1.2.1

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 (37) hide show
  1. package/README.md +112 -52
  2. package/dist/cli.cjs +6552 -392
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.js +6551 -395
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.cjs +10215 -4615
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +1175 -30
  9. package/dist/index.d.ts +1175 -30
  10. package/dist/index.js +7501 -1906
  11. package/dist/index.js.map +1 -1
  12. package/dist/templates/coding-assistant/commitments.json +14 -0
  13. package/dist/templates/coding-assistant/defaults.json +34 -0
  14. package/dist/templates/coding-assistant/onboarding.md +24 -0
  15. package/dist/templates/coding-assistant/policy.md +1 -0
  16. package/dist/templates/coding-assistant/template.json +23 -0
  17. package/dist/templates/handoff-coordinator/commitments.json +14 -0
  18. package/dist/templates/handoff-coordinator/defaults.json +10 -0
  19. package/dist/templates/handoff-coordinator/onboarding.md +23 -0
  20. package/dist/templates/handoff-coordinator/policy.md +1 -0
  21. package/dist/templates/handoff-coordinator/template.json +17 -0
  22. package/dist/templates/ops-runner/commitments.json +14 -0
  23. package/dist/templates/ops-runner/defaults.json +12 -0
  24. package/dist/templates/ops-runner/onboarding.md +25 -0
  25. package/dist/templates/ops-runner/policy.md +1 -0
  26. package/dist/templates/ops-runner/template.json +16 -0
  27. package/dist/templates/planner/commitments.json +9 -0
  28. package/dist/templates/planner/defaults.json +10 -0
  29. package/dist/templates/planner/onboarding.md +22 -0
  30. package/dist/templates/planner/policy.md +1 -0
  31. package/dist/templates/planner/template.json +8 -0
  32. package/dist/templates/research-assistant/commitments.json +9 -0
  33. package/dist/templates/research-assistant/defaults.json +25 -0
  34. package/dist/templates/research-assistant/onboarding.md +21 -0
  35. package/dist/templates/research-assistant/policy.md +1 -0
  36. package/dist/templates/research-assistant/template.json +8 -0
  37. package/package.json +9 -4
package/dist/index.d.ts CHANGED
@@ -1248,6 +1248,18 @@ declare class PolicyStore {
1248
1248
  private persist;
1249
1249
  }
1250
1250
 
1251
+ /**
1252
+ * Signature scheme identifier embedded in every AuditEntry and crypto-agility-bearing surface.
1253
+ *
1254
+ * v1.0: only "ed25519-v1" is valid.
1255
+ * v1.x post-quantum migration: "ed25519+ml-dsa-v1" hybrid will be introduced.
1256
+ * v1.0 verifiers MUST reject unknown schemes; the field exists so v1.x can add
1257
+ * hybrid signing without breaking v1.0 readers. Do not optimize this field away.
1258
+ *
1259
+ * Spec: §5.3 (Crypto-agility per thesis §3 L1 PQ note).
1260
+ */
1261
+ type SignatureScheme$1 = "ed25519-v1";
1262
+
1251
1263
  /**
1252
1264
  * Sanctuary MCP Server — Sovereignty Health Report (SHR) Types
1253
1265
  *
@@ -1258,6 +1270,7 @@ declare class PolicyStore {
1258
1270
  *
1259
1271
  * SHR version: 1.0
1260
1272
  */
1273
+
1261
1274
  type LayerStatus = "active" | "degraded" | "inactive";
1262
1275
  type DegradationSeverity = "info" | "warning" | "critical";
1263
1276
  type DegradationCode = "NO_TEE" | "PROCESS_ISOLATION_ONLY" | "COMMITMENT_ONLY" | "NO_ZK_PROOFS" | "SELF_REPORTED_ATTESTATION" | "NO_SELECTIVE_DISCLOSURE" | "BASIC_SYBIL_ONLY" | "NO_REPUTATION_HISTORY" | "LOW_TIER_DOMINANCE" | "STALE_REPUTATION" | "DISPUTE_ON_RECORD" | "NO_VERASCORE_LINK";
@@ -1337,6 +1350,7 @@ interface SHRBody {
1337
1350
  interface SignedSHR {
1338
1351
  body: SHRBody;
1339
1352
  signed_by: string;
1353
+ signature_scheme: SignatureScheme$1;
1340
1354
  signature: string;
1341
1355
  }
1342
1356
  interface SHRVerificationResult {
@@ -2925,13 +2939,13 @@ interface PrivacyRehydratedPayload extends PrivacyAuditPayloadHeader {
2925
2939
  type PrivacyAuditPayload = PrivacyFilteredPayload | PrivacyAllowedPayload | PrivacyDeniedPayload | PrivacyErrorPayload | PrivacyRehydratedPayload;
2926
2940
 
2927
2941
  /**
2928
- * Sanctuary v1.1 Operator Hub Event Contracts
2942
+ * Sanctuary v1.1 Operator Hub Event Contracts
2929
2943
  *
2930
2944
  * Shared shapes for the unified inbox, the activity feed, and the per-agent
2931
2945
  * status panels. The operator hub API workstream (Prompt 5) emits these; the
2932
2946
  * dashboard UI workstream (Prompt 8) consumes them. v1.2 mobile companion
2933
2947
  * planning will evaluate these shapes when it scopes a phone surface, but
2934
- * v1.1 does not commit to mobile compatibility these contracts are
2948
+ * v1.1 does not commit to mobile compatibility. These contracts are
2935
2949
  * tuned for the local dashboard surface only.
2936
2950
  *
2937
2951
  * Local-only invariant:
@@ -2952,7 +2966,7 @@ type PrivacyAuditPayload = PrivacyFilteredPayload | PrivacyAllowedPayload | Priv
2952
2966
  * accepted; the dashboard rejects rendering on any value outside this union.
2953
2967
  *
2954
2968
  * The renderer treats every value as data to interpolate into a fixed
2955
- * template registered under `template_id` never as raw content. This
2969
+ * template registered under `template_id`, never as raw content. This
2956
2970
  * defends against secrets, query text, file paths, and client names leaking
2957
2971
  * into inbox cards via stringly-typed display fields.
2958
2972
  */
@@ -2965,6 +2979,9 @@ type HubDisplayTemplateArg = {
2965
2979
  } | {
2966
2980
  kind: "policy_id";
2967
2981
  value: string;
2982
+ } | {
2983
+ kind: "channel_template_id";
2984
+ value: string;
2968
2985
  } | {
2969
2986
  kind: "destination_category";
2970
2987
  value: PrivacyDestinationCategory;
@@ -3012,7 +3029,7 @@ interface HubInboxItemHeader {
3012
3029
  display_template_id: string;
3013
3030
  /**
3014
3031
  * Typed args interpolated into the template. Every value MUST be a
3015
- * `HubDisplayTemplateArg` instance no free-form strings. Renderers
3032
+ * `HubDisplayTemplateArg` instance, no free-form strings. Renderers
3016
3033
  * reject any arg outside this union, which structurally blocks secret
3017
3034
  * leakage via inbox copy.
3018
3035
  */
@@ -3097,7 +3114,7 @@ interface HubBudgetWarningItem extends HubInboxItemHeader {
3097
3114
  used_fraction: number;
3098
3115
  }
3099
3116
  /**
3100
- * Recovery prompt operator should run a recovery flow (passphrase reset,
3117
+ * Recovery prompt. Operator should run a recovery flow (passphrase reset,
3101
3118
  * keychain rebind, exit drill, etc.).
3102
3119
  */
3103
3120
  interface HubRecoveryPromptItem extends HubInboxItemHeader {
@@ -3143,12 +3160,32 @@ interface HubActivityFeedEntry {
3143
3160
  category: "policy_decision" | "approval" | "denial" | "egress" | "privacy" | "handoff" | "lifecycle" | "config" | "other";
3144
3161
  /**
3145
3162
  * Display template id. Resolved by the dashboard against the activity-feed
3146
- * template catalog. Backends MUST NOT emit raw summary text the template
3163
+ * template catalog. Backends MUST NOT emit raw summary text. The template
3147
3164
  * id plus typed args is the only legitimate channel.
3148
3165
  */
3149
3166
  display_template_id: string;
3150
3167
  /** Typed args. Same constraints as `HubInboxItemHeader.display_template_args`. */
3151
3168
  display_template_args: HubDisplayTemplateArg[];
3169
+ /**
3170
+ * Per-action attestation fragment for dashboard timeline rendering.
3171
+ *
3172
+ * Optional; absence means the row renders without a badge.
3173
+ *
3174
+ * `state` drives the `att-action` CSS class on the rendered badge.
3175
+ * `fragment` is a deterministic short hex string derived from the
3176
+ * audit-chain entry id; it gives operators a stable per-row visual hook
3177
+ * the same shape the Sprint Piece 2 attestation gallery shows.
3178
+ *
3179
+ * Important: the fragment is NOT a real per-event Ed25519 signature.
3180
+ * The audit chain itself is tamper-evident at the main-process boundary
3181
+ * (scope-lock §8); the fragment is the visible projection of the entry's
3182
+ * audit-chain identity. Real per-event signatures land post-v1.5+ in the
3183
+ * Crypto Agility Sprint.
3184
+ */
3185
+ attestation?: {
3186
+ state: "verified" | "degraded" | "unverified" | "neutral";
3187
+ fragment: string;
3188
+ };
3152
3189
  }
3153
3190
  /**
3154
3191
  * Per-agent status snapshot returned by the hub API. Mirrors the agent
@@ -3758,6 +3795,27 @@ declare class MemoryStorage implements StorageBackend {
3758
3795
  * - Secure deletion overwrites file content with random bytes before unlinking
3759
3796
  * - Directory creation uses restrictive permissions (0o700)
3760
3797
  * - File creation uses restrictive permissions (0o600)
3798
+ *
3799
+ * Path encoding (bijective, full-sweep #41):
3800
+ * Distinct (namespace, key) inputs MUST produce distinct on-disk paths;
3801
+ * otherwise an agent that can choose namespace/key strings within a tenant
3802
+ * could overwrite or read another namespace by colliding on the sanitized
3803
+ * form (multi-tenant isolation invariant). The encoder retains the safe
3804
+ * set [A-Za-z0-9_.-] (so internal namespaces such as `_audit`, `_bridge`,
3805
+ * etc. preserve their on-disk paths verbatim) and `!`-escapes every other
3806
+ * character as `!XX` where XX is the upper-hex byte. The escape character
3807
+ * `!` itself is NOT in the safe set, so a literal `!` in input encodes as
3808
+ * `!21` and decoding remains unambiguous.
3809
+ *
3810
+ * Legacy fallback (forward compatibility):
3811
+ * Pre-fix code used `replace(/[^a-zA-Z0-9_-]/g, "_")` for namespaces and
3812
+ * `replace(/[^a-zA-Z0-9_.-]/g, "_")` for keys; non-bijective. read(),
3813
+ * exists(), and delete() try the new path first; on ENOENT they fall back
3814
+ * to the legacy path so existing fortresses with operator-supplied
3815
+ * namespaces containing non-safe characters keep working. write() always
3816
+ * uses the new bijective path. list() and totalSize() walk on-disk
3817
+ * directory names directly and cannot disambiguate legacy collision-class
3818
+ * pairs; they are forward-only by design.
3761
3819
  */
3762
3820
 
3763
3821
  declare class FilesystemStorage implements StorageBackend {
@@ -3765,9 +3823,12 @@ declare class FilesystemStorage implements StorageBackend {
3765
3823
  constructor(basePath: string);
3766
3824
  private entryPath;
3767
3825
  private namespacePath;
3826
+ private legacyEntryPath;
3768
3827
  write(namespace: string, key: string, data: Uint8Array): Promise<void>;
3769
3828
  read(namespace: string, key: string): Promise<Uint8Array | null>;
3829
+ private readAtPath;
3770
3830
  delete(namespace: string, key: string, secureOverwrite?: boolean): Promise<boolean>;
3831
+ private deleteAtPath;
3771
3832
  list(namespace: string, prefix?: string): Promise<StorageEntryMeta[]>;
3772
3833
  exists(namespace: string, key: string): Promise<boolean>;
3773
3834
  totalSize(): Promise<number>;
@@ -3921,6 +3982,31 @@ interface ImportExitBundleOptions {
3921
3982
  sourceRecoveryKey?: string;
3922
3983
  sourceMasterKey?: Uint8Array;
3923
3984
  destinationSignerIdentityId?: string;
3985
+ /**
3986
+ * v1.0.2 (i) / full-sweep #54. When the destination fortress already has a
3987
+ * staged `public_identity` for the bundle's identity_id, activation is refused
3988
+ * unless the operator passes this flag. The CLI surfaces the flag as
3989
+ * `--force-rebind` and re-prompts Tier 1 confirmation. When `forceRebind` is
3990
+ * true and the rebind triggers, an `exit_bundle_force_rebind` L1 audit entry
3991
+ * records the explicit replacement.
3992
+ */
3993
+ forceRebind?: boolean;
3994
+ /**
3995
+ * v1.0.2 / full-sweep #55. Reputation attestations whose signer DID is not
3996
+ * present in the bundle's published identity material are marked
3997
+ * `unverifiable` by the verifier. By default the verdict is now strict and
3998
+ * an unverifiable attestation fails the bundle. Setting this flag opts the
3999
+ * operator in to an explicit relaxed verdict (Tier 1 confirmation in CLI).
4000
+ */
4001
+ acceptUnverifiableAttestations?: boolean;
4002
+ }
4003
+ /**
4004
+ * Structured error raised by `importExitBundle` for codes the CLI / hub want
4005
+ * to branch on without parsing free-text messages. v1.0.2 (i) / full-sweep #54.
4006
+ */
4007
+ declare class ExitBundleImportError extends Error {
4008
+ readonly code: string;
4009
+ constructor(code: string, message: string);
3924
4010
  }
3925
4011
  interface ExitBundleConflictReport {
3926
4012
  public_identity_exists: boolean;
@@ -3957,14 +4043,6 @@ declare function exportExitBundle(opts: ExportExitBundleOptions): Promise<Export
3957
4043
  declare function importExitBundle(opts: ImportExitBundleOptions): Promise<ImportExitBundleResult>;
3958
4044
  declare function exitBundleManifestShape(): Record<string, unknown>;
3959
4045
 
3960
- /**
3961
- * Sanctuary v1.1 exit-bundle verifier.
3962
- *
3963
- * Verifies the signed SANCTUARY_EXIT_BUNDLE_V1 manifest, every artifact hash,
3964
- * and the exported identity / reputation signatures that are independently
3965
- * verifiable from public material in the bundle.
3966
- */
3967
-
3968
4046
  interface ExitBundleDetailedVerifierResult extends ExitBundleVerifierResult {
3969
4047
  manifest_path: string;
3970
4048
  manifest_hash: string | null;
@@ -3995,7 +4073,19 @@ interface LoadedExitArtifact<T = unknown> {
3995
4073
  }
3996
4074
  declare function readManifest(bundleDir: string): Promise<ExitBundleManifest>;
3997
4075
  declare function loadExitArtifact<T = unknown>(bundleDir: string, manifest: ExitBundleManifest, kind: ExitBundleArtifactKind): Promise<LoadedExitArtifact<T> | null>;
3998
- declare function verifyExitBundle(bundleDir: string): Promise<ExitBundleDetailedVerifierResult>;
4076
+ /**
4077
+ * Caller-supplied verifier knobs. v1.0.2 / full-sweep #55.
4078
+ *
4079
+ * `acceptUnverifiableAttestations` flips the bundle verdict from strict-by-default
4080
+ * (any unverifiable attestation fails the bundle) to a relaxed verdict that
4081
+ * tolerates attestations whose signer DID is not in the bundle's published
4082
+ * identity material. Operators opt in explicitly through the CLI
4083
+ * `--accept-unverifiable-attestations` flag (Tier 1 confirmation).
4084
+ */
4085
+ interface VerifyExitBundleOptions {
4086
+ acceptUnverifiableAttestations?: boolean;
4087
+ }
4088
+ declare function verifyExitBundle(bundleDir: string, options?: VerifyExitBundleOptions): Promise<ExitBundleDetailedVerifierResult>;
3999
4089
 
4000
4090
  /**
4001
4091
  * `sanctuary exit` CLI.
@@ -4130,10 +4220,981 @@ type HubInboxAction = (typeof HUB_INBOX_ACTIONS)[number];
4130
4220
  declare const HUB_AGENT_CONTROL_ACTIONS: readonly ["pause", "resume", "restart", "unwrap", "lockdown"];
4131
4221
  type HubAgentControlAction = (typeof HUB_AGENT_CONTROL_ACTIONS)[number];
4132
4222
 
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"];
4223
+ /** Channel-template identifiers per the five-template canonical starter set. */
4224
+ declare const CHANNEL_TEMPLATE_IDS: readonly ["request-approve-act", "read-then-report", "scheduled-digest", "plan-draft-only", "fortress-relay"];
4135
4225
  type ChannelTemplateId = (typeof CHANNEL_TEMPLATE_IDS)[number];
4136
4226
 
4227
+ /**
4228
+ * Sanctuary MCP Server — Intelligence Substrate Selector Types
4229
+ *
4230
+ * The substrate selector lets operators pick (and re-pick) the LLM substrate
4231
+ * that powers Sanctuary's intelligence-layer surfaces: concierge, sentinel
4232
+ * scoring, gate explanation, privacy filter Tier 2, template suggestion,
4233
+ * direct-agent gate advisory.
4234
+ *
4235
+ * Per Erik directive 2026-04-29: the selector IS the architecture. Sanctuary's
4236
+ * job is to make the tradeoffs visible and let the operator choose. Multi-option
4237
+ * framing is preserved; no single substrate is surfaced as a winner.
4238
+ *
4239
+ * Per the position paper Intelligence_Layer_Drift_Audit_and_Substrate_Resolution_2026-04-29.md
4240
+ * (sections 4 + 5), four substrates ship as operator-pickable:
4241
+ *
4242
+ * - local: Ollama-hosted local model (Gemma 2 2B / Phi-4 Mini / Llama 3.1 8B)
4243
+ * - venice: Venice.ai privacy-respecting hosted relay
4244
+ * - frontier-with-filter: operator's frontier API account, Privacy Filter Tier 2 pre-egress
4245
+ * - hybrid: per-surface routing across the above
4246
+ * - disabled: surface refuses LLM calls; Tier 1 regex fallback for privacy filter
4247
+ *
4248
+ * Per-surface defaults (Erik ratification 2026-04-29):
4249
+ * - concierge: local (Gemma 2 2B)
4250
+ * - sentinel-scoring: local (rules + Phi-4 Mini on escalation)
4251
+ * - gate-explanation: templates v1.2; small LLM v1.3+ for custom policies
4252
+ * - privacy-filter-tier-2: local (NER + small LLM PII classifier)
4253
+ * - direct-agent-gate-advisor: same as concierge by default
4254
+ * - template-suggestion: same as concierge by default
4255
+ */
4256
+ /**
4257
+ * The set of intelligence-layer surfaces the selector routes for.
4258
+ *
4259
+ * The enum is closed. Adding a surface requires updating the selector's
4260
+ * default-config builder, the audit emission shape registry, and the
4261
+ * transparency UI in the same PR.
4262
+ */
4263
+ type Surface = "concierge" | "direct-agent-gate-advisor" | "sentinel-scoring" | "gate-explanation" | "privacy-filter-tier-2" | "template-suggestion";
4264
+ /**
4265
+ * The substrate choice an operator may bind to a surface.
4266
+ */
4267
+ type SubstrateChoice = "local" | "venice" | "frontier-with-filter" | "hybrid" | "disabled";
4268
+ /**
4269
+ * Local-model picks for the Ollama-backed local substrate.
4270
+ *
4271
+ * Bundled tiers per position paper §5 hardware-tier table:
4272
+ * - gemma-2-2b (Baseline / 8GB RAM)
4273
+ * - phi-4-mini (Mid / 16GB RAM)
4274
+ * - llama-3.1-8b (Pro / 32GB+ RAM)
4275
+ *
4276
+ * Custom Ollama-installed models can be specified via `customModelTag` on the
4277
+ * substrate config; the selector probes Ollama for availability at runtime.
4278
+ */
4279
+ type LocalModelPick = "gemma-2-2b" | "phi-4-mini" | "llama-3.1-8b";
4280
+ /**
4281
+ * Frontier providers operator may pick for the frontier-with-filter substrate.
4282
+ * v1.2 ships three; v1.3+ may add Mistral / xAI / Cohere.
4283
+ */
4284
+ type FrontierProvider = "anthropic" | "openai" | "google";
4285
+ /**
4286
+ * What the selector does when its chosen substrate fails (e.g. Ollama not
4287
+ * running, Venice API unreachable, frontier API key revoked).
4288
+ *
4289
+ * Per surface, operator-configurable. Defaults per position paper §5:
4290
+ * - concierge: degrade-silent (next-tier substrate)
4291
+ * - sentinel-scoring: conservative-deny (refuse rather than route elsewhere)
4292
+ * - privacy-filter-tier-2: degrade-silent (Tier 1 regex always works)
4293
+ * - others: conservative-deny
4294
+ */
4295
+ type FallbackBehavior = "conservative-deny" | "degrade-silent" | "disable-surface";
4296
+ /**
4297
+ * Per-surface routing rules for the hybrid substrate.
4298
+ *
4299
+ * v1.2 ships per-surface routing only — operator pre-binds each surface to a
4300
+ * concrete substrate. Per-query sensitivity classification routing is deferred
4301
+ * to v1.3+.
4302
+ */
4303
+ interface HybridRoutingRules {
4304
+ perSurface: Record<Surface, Exclude<SubstrateChoice, "hybrid">>;
4305
+ }
4306
+ /**
4307
+ * Operator-readable status badge for the transparency UI and chat headers.
4308
+ *
4309
+ * Backends emit a stable shape (no free-form prose); the dashboard's template
4310
+ * registry renders it. This mirrors the v1.1 hub `HubDisplayTemplateArg`
4311
+ * discipline so secret-leakage into operator-facing copy stays structurally
4312
+ * impossible.
4313
+ */
4314
+ interface SubstrateBadge {
4315
+ surface: Surface;
4316
+ substrate: SubstrateChoice;
4317
+ /** Stable badge label key resolved by dashboard template registry. */
4318
+ labelKey: string;
4319
+ /** Stable tradeoff body key resolved by dashboard template registry. */
4320
+ tradeoffKey: string;
4321
+ /** Stable status; green = working; yellow = degraded; red = disabled / failing. */
4322
+ status: "green" | "yellow" | "red";
4323
+ }
4324
+ /**
4325
+ * Frontier provider configs. Operator's API key per provider is stored
4326
+ * encrypted; never leaves the fortress except as outbound HTTPS to the
4327
+ * provider after Privacy Filter Tier 2 redaction.
4328
+ */
4329
+ interface FrontierProviderConfig {
4330
+ /** Operator's per-provider API key (encrypted at rest). */
4331
+ anthropic?: string;
4332
+ openai?: string;
4333
+ google?: string;
4334
+ }
4335
+ /**
4336
+ * Operator-overridable substrate config. Persisted encrypted under the
4337
+ * fortress master key in storage namespace `_intelligence`.
4338
+ *
4339
+ * v1.0 of this shape (`version: 1`) is the only shape this PR ships. Future
4340
+ * v1.x extensions (per-query sensitivity classifier, MLX runtime pick,
4341
+ * per-patron substrate scoping) MUST bump this version and provide a
4342
+ * forward-compat migration in the selector.
4343
+ */
4344
+ interface SubstrateConfig {
4345
+ version: 1;
4346
+ /** Operator's per-surface choice. Defaults from position paper §5. */
4347
+ perSurface: Record<Surface, SubstrateChoice>;
4348
+ /** Operator's local-model picks per surface, when 'local' is chosen. */
4349
+ localModelPicks: Partial<Record<Surface, LocalModelPick>>;
4350
+ /** Custom Ollama model tag override per surface (overrides localModelPicks if set). */
4351
+ customLocalModelTags?: Partial<Record<Surface, string>>;
4352
+ /** Ollama HTTP endpoint; defaults to http://localhost:11434. */
4353
+ ollamaEndpoint?: string;
4354
+ /** Venice API key, if 'venice' chosen for any surface. Encrypted at rest. */
4355
+ veniceApiKey?: string;
4356
+ /** Venice model selection; defaults to llama-3.1-70b. */
4357
+ veniceModel?: string;
4358
+ /** Frontier provider configs, if 'frontier-with-filter' chosen. */
4359
+ frontierConfig: FrontierProviderConfig;
4360
+ /** Hybrid routing rules, if 'hybrid' chosen for any surface. */
4361
+ hybridRules?: HybridRoutingRules;
4362
+ /** Fallback behavior per surface. */
4363
+ fallback: Record<Surface, FallbackBehavior>;
4364
+ /**
4365
+ * Operator preference for the picker modal: when true, picking a
4366
+ * substrate + key for any one surface fans out to every surface in one
4367
+ * save (Finding SS, v1.2.0-rc.1). Defaults to true; operators wanting
4368
+ * per-surface granularity flip the picker toggle off and the next
4369
+ * single-surface choice persists this back to false.
4370
+ */
4371
+ applyToAllSurfaces?: boolean;
4372
+ /** ISO8601 timestamp of last operator change. */
4373
+ updatedAt: string;
4374
+ }
4375
+ interface SummarizeRequest {
4376
+ kind: "summarize";
4377
+ context: string;
4378
+ query: string;
4379
+ maxTokens?: number;
4380
+ }
4381
+ interface ClassifyRequest {
4382
+ kind: "classify";
4383
+ items: string[];
4384
+ categories: string[];
4385
+ maxTokens?: number;
4386
+ }
4387
+ interface RedactRequest {
4388
+ kind: "redact";
4389
+ text: string;
4390
+ }
4391
+ /**
4392
+ * Per-substrate response envelope. Substrate clients return content + telemetry;
4393
+ * audit-emission and badge-rendering happen at the selector layer.
4394
+ */
4395
+ interface SubstrateResponse {
4396
+ /** The substrate that actually served this invocation (may differ from chosen if fallback fired). */
4397
+ servedBy: SubstrateChoice;
4398
+ /** Stable failure-class enum on failure; null on success. */
4399
+ failureClass: SubstrateFailureClass | null;
4400
+ /** Body shape varies by request kind. */
4401
+ body: {
4402
+ kind: "summarize";
4403
+ text: string;
4404
+ } | {
4405
+ kind: "classify";
4406
+ results: {
4407
+ category: string;
4408
+ confidence: number;
4409
+ }[];
4410
+ } | {
4411
+ kind: "redact";
4412
+ redacted: string;
4413
+ placeholders: Record<string, string>;
4414
+ } | {
4415
+ kind: "failure";
4416
+ message: string;
4417
+ };
4418
+ /** ISO8601 of when invocation completed. */
4419
+ completedAt: string;
4420
+ /** Total wall-clock latency in ms. */
4421
+ latencyMs: number;
4422
+ }
4423
+ /**
4424
+ * Stable failure-class enum. Backends return one of these when a substrate
4425
+ * invocation fails; the selector's audit emission carries this verbatim.
4426
+ *
4427
+ * Adding a new failure class requires updating the audit-event consumer and
4428
+ * the dashboard status-mapping in the same PR.
4429
+ */
4430
+ type SubstrateFailureClass = "substrate_unavailable" | "substrate_misconfigured" | "substrate_rate_limited" | "substrate_auth_failed" | "substrate_timeout" | "substrate_capability_unsupported" | "substrate_disabled" | "internal_error";
4431
+ /**
4432
+ * Capability profile for each substrate. Surfaces consult this before
4433
+ * invoking; if the substrate cannot serve the request kind, the selector
4434
+ * applies the fallback policy.
4435
+ */
4436
+ interface SubstrateCapability {
4437
+ summarize: boolean;
4438
+ classify: boolean;
4439
+ redact: boolean;
4440
+ }
4441
+ /**
4442
+ * The handle a surface holds after `selector.getSubstrate(surface)`. The
4443
+ * surface invokes via the typed methods; the substrate client implements them.
4444
+ *
4445
+ * Surfaces MUST treat any thrown error as a `substrate_unavailable` failure;
4446
+ * the selector's invoke wrapper catches and emits the audit event.
4447
+ */
4448
+ interface SubstrateHandle {
4449
+ surface: Surface;
4450
+ substrate: SubstrateChoice;
4451
+ badge: SubstrateBadge;
4452
+ capability: SubstrateCapability;
4453
+ /**
4454
+ * Optional operator-readable display string for the chat / dashboard headers.
4455
+ * Resolved from the badge labelKey; produced by the selector at handle issue,
4456
+ * not by the substrate client.
4457
+ */
4458
+ displayLabel: string;
4459
+ summarize?: (req: SummarizeRequest) => Promise<SubstrateResponse>;
4460
+ classify?: (req: ClassifyRequest) => Promise<SubstrateResponse>;
4461
+ redact?: (req: RedactRequest) => Promise<SubstrateResponse>;
4462
+ }
4463
+ /**
4464
+ * Operator-visible per-surface status report rendered by the transparency UI.
4465
+ *
4466
+ * The dashboard SPA consumes this via the `/api/hub/intelligence/status`
4467
+ * route; the picker modal consumes it before substrate selection to surface
4468
+ * hardware-capability hints.
4469
+ */
4470
+ interface SubstrateStatusReport {
4471
+ version: "1.2";
4472
+ generatedAt: string;
4473
+ surfaces: SurfaceStatus[];
4474
+ hardware: HardwareCapabilityReport;
4475
+ }
4476
+ interface SurfaceStatus {
4477
+ surface: Surface;
4478
+ chosen: SubstrateChoice;
4479
+ badge: SubstrateBadge;
4480
+ /** Substrate-side health probe result. */
4481
+ health: "ok" | "degraded" | "unavailable";
4482
+ /** Stable failure class when health != "ok"; null when "ok". */
4483
+ failureClass: SubstrateFailureClass | null;
4484
+ /**
4485
+ * Recent runtime + validation failures for this surface, capped at 5
4486
+ * entries and time-windowed to 24 hours. Sourced from real chat-call
4487
+ * outcomes (and validateKey results for substrates that expose it),
4488
+ * NOT from the static probe-time misconfig check.
4489
+ *
4490
+ * Per Finding VV (v1.2.0-rc.1): the operator-visible badge degrades to
4491
+ * "yellow" / "degraded" whenever this array is non-empty even if the
4492
+ * static probe still says ok. This closes the truth-telling gap where
4493
+ * the dashboard reported all surfaces healthy after the runtime chat
4494
+ * had already failed against the same substrate.
4495
+ */
4496
+ recentFailures: RecentFailureEntry[];
4497
+ }
4498
+ /**
4499
+ * One observed substrate failure surfaced via `/api/hub/intelligence/status`.
4500
+ * Carries enough metadata for the operator to triage (when, what failure
4501
+ * class, brief operator-safe snippet) without leaking request bodies,
4502
+ * response bodies, or operator credentials.
4503
+ *
4504
+ * Snippets are bounded human-readable strings (e.g. "venice configured
4505
+ * model not found") drawn from the substrate failure-message field; the
4506
+ * selector strips anything resembling an API key or PII before retention.
4507
+ * Operators wanting full forensic detail consult the L2 audit log; this
4508
+ * surface is the at-a-glance triage path.
4509
+ */
4510
+ interface RecentFailureEntry {
4511
+ /** ISO8601 timestamp of when the failure was observed. */
4512
+ ts: string;
4513
+ /** Stable failure-class enum carried verbatim from the substrate response. */
4514
+ failureClass: SubstrateFailureClass;
4515
+ /** Bounded operator-safe snippet describing the failure. */
4516
+ snippet: string;
4517
+ }
4518
+ interface HardwareCapabilityReport {
4519
+ /** Detected total RAM in GB. Self-reported via os.totalmem(). */
4520
+ totalRamGb: number;
4521
+ /** Apple Silicon family if detectable, or "other". */
4522
+ cpuArch: "apple-silicon-m1" | "apple-silicon-m2" | "apple-silicon-m3" | "apple-silicon-m4" | "apple-silicon-other" | "x86_64" | "other";
4523
+ /** Computed tier per position paper §5 (baseline / mid / pro / below-baseline). */
4524
+ tier: "below-baseline" | "baseline" | "mid" | "pro";
4525
+ /** Recommended local model tier per detected hardware. */
4526
+ recommendedLocalModel: LocalModelPick | null;
4527
+ /** Whether Ollama is reachable at the configured endpoint. */
4528
+ ollamaReachable: boolean;
4529
+ /** Models present in Ollama at the time of the report. Empty if not reachable. */
4530
+ ollamaModels: string[];
4531
+ }
4532
+
4533
+ /**
4534
+ * Sanctuary MCP Server — Frontier-with-Filter Substrate
4535
+ *
4536
+ * Operator's frontier API account (Anthropic / OpenAI / Google) wrapped with
4537
+ * mandatory pre-egress Privacy Filter Tier 2 redaction.
4538
+ *
4539
+ * Per position paper §5: every query routes through Privacy Filter Tier 2
4540
+ * BEFORE the frontier API call. The Privacy Filter event log captures the
4541
+ * pre-redaction match count so the operator can audit what was sent.
4542
+ *
4543
+ * Honesty discipline (CLAUDE.md):
4544
+ * - Tradeoff badge text explicitly names the leakage
4545
+ * - Required caveat about subtle PII (paraphrased addresses, contextual
4546
+ * identifiers) appears in tradeoff text
4547
+ * - Operator MUST acknowledge before configuration completes (enforced
4548
+ * by the picker modal in the dashboard SPA, deferred to the v1.2 UI
4549
+ * follow-up commit)
4550
+ *
4551
+ * Provider clients are kept minimal: one Chat Completions / Messages call
4552
+ * per surface request, no streaming in v1.2 (streaming is a v1.3 chat
4553
+ * surface enhancement).
4554
+ */
4555
+
4556
+ /**
4557
+ * Pre-egress redaction hook. The substrate adapter calls this on every
4558
+ * outbound text; returns the redacted text + placeholder map. The Privacy
4559
+ * Filter Tier 2 implementation lives in `l2-operational/privacy-filter.ts`
4560
+ * (extended in this PR to wire the substrate-aware Tier 2 path).
4561
+ *
4562
+ * For v1.2, the redactor passed in is responsible for emitting the audit
4563
+ * event `intelligence_pii_redaction_event` via the selector's auditLog
4564
+ * binding. The frontier substrate is provider-agnostic about how redaction
4565
+ * happens; it just refuses to call the frontier API on `failureClass !== null`.
4566
+ */
4567
+ type FrontierRedactor = (text: string) => Promise<{
4568
+ redacted: string;
4569
+ matchCount: number;
4570
+ }>;
4571
+
4572
+ /**
4573
+ * Sanctuary MCP Server — Intelligence Substrate Selector
4574
+ *
4575
+ * The selector IS the architecture (Erik directive 2026-04-29). Operators
4576
+ * pick (and re-pick) the LLM substrate that powers each intelligence-layer
4577
+ * surface; the selector binds choices to surfaces, provides a typed
4578
+ * `SubstrateHandle` to consumers, applies operator-configured fallback
4579
+ * behavior on substrate failure, and emits audit events for every change
4580
+ * and every invocation.
4581
+ *
4582
+ * Architecture per position paper section 5:
4583
+ * ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
4584
+ * │ concierge│ │ sentinel │ │ gate-expl│ │ priv-f-2 │ ... surfaces
4585
+ * └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
4586
+ * ▼ ▼ ▼ ▼
4587
+ * ┌──────────────────────────────────────────────────┐
4588
+ * │ SubstrateSelector (per-surface routing) │
4589
+ * └────┬──────────┬──────────┬──────────┬────────────┘
4590
+ * ▼ ▼ ▼ ▼
4591
+ * ┌────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
4592
+ * │ Local │ │ Venice │ │ Frontier │ │ Disabled │
4593
+ * │(Ollama)│ │ (relay) │ │ + filter │ │ (refuse) │
4594
+ * └────────┘ └──────────┘ └──────────┘ └──────────┘
4595
+ *
4596
+ * The hybrid substrate is per-surface routing only at v1.2 — operator
4597
+ * pre-binds each surface to a concrete substrate; per-query sensitivity
4598
+ * classification routing is deferred to v1.3+.
4599
+ *
4600
+ * Audit emission discipline:
4601
+ * Every public method that changes config or invokes a substrate appends
4602
+ * an `IntelligenceAuditPayload` to the L2 audit log. The selector never
4603
+ * stores raw request bodies, response bodies, or operator credentials in
4604
+ * audit details; only safe metadata (surface, substrate, hashes, latency,
4605
+ * failure-class enum). The event payload contracts live in
4606
+ * `contracts/v1.2/intelligence-events.ts`.
4607
+ *
4608
+ * Sovereignty invariants preserved:
4609
+ * - Operator API keys live in the encrypted SubstrateConfig persisted
4610
+ * under the fortress master key.
4611
+ * - Frontier-with-filter substrate ALWAYS routes through a redactor
4612
+ * before any frontier API call; the redactor argument is required in
4613
+ * the constructor; passing a null redactor disables the substrate.
4614
+ * - Sentinel-scoring surface defaults to `conservative-deny` fallback so
4615
+ * substrate failure cannot silently allow an unverified tool call.
4616
+ */
4617
+
4618
+ /**
4619
+ * Configuration the selector needs at construction. The audit-log binding
4620
+ * is required: a selector without an audit log violates the audit-emission
4621
+ * invariant and is structurally rejected.
4622
+ */
4623
+ interface SelectorConfig {
4624
+ storage: StorageBackend;
4625
+ masterKey: Uint8Array;
4626
+ auditLog: AuditLog;
4627
+ /** Identity that owns the substrate config. Stamped on every audit event. */
4628
+ identityId: string;
4629
+ /**
4630
+ * Pre-egress redactor for the frontier-with-filter substrate. Defaults
4631
+ * to `IDENTITY_REDACTOR` until Privacy Filter Tier 2 wires through.
4632
+ * Passing a custom redactor at construction allows tests + the Tier 2
4633
+ * commit to install a real implementation without modifying the
4634
+ * selector.
4635
+ */
4636
+ redactor?: FrontierRedactor;
4637
+ /**
4638
+ * Optional fetch override for substrate clients. Forwarded to Ollama,
4639
+ * Venice, and frontier client constructors so tests can stub out
4640
+ * network calls.
4641
+ */
4642
+ fetchImpl?: typeof fetch;
4643
+ }
4644
+ declare class SubstrateSelector {
4645
+ private store;
4646
+ private auditLog;
4647
+ private identityId;
4648
+ private redactor;
4649
+ private fetchImpl;
4650
+ private config;
4651
+ private loaded;
4652
+ /**
4653
+ * Per-surface ring buffer of recent runtime + validation failures. See
4654
+ * `RECENT_FAILURES_CAP` and `RECENT_FAILURES_WINDOW_MS`. Populated by
4655
+ * `recordRecentFailure()` from the `invoke()` failure path and from the
4656
+ * post-config validation hook on substrates that expose validateKey.
4657
+ * Drives the operator-visible degrade in `getOperatorVisibleStatus()`.
4658
+ */
4659
+ private recentFailures;
4660
+ constructor(cfg: SelectorConfig);
4661
+ /**
4662
+ * Load (or initialize) the operator's substrate config. Emits the
4663
+ * `intelligence_config_loaded` audit event regardless of branch so the
4664
+ * audit chain shows config-load activity on boot.
4665
+ */
4666
+ load(): Promise<void>;
4667
+ /**
4668
+ * Operator picked (or re-picked) a substrate for a surface. Persists the
4669
+ * change and emits `intelligence_substrate_chosen`. The picker modal
4670
+ * surfaces the tradeoff text BEFORE invoking this method; the audit
4671
+ * payload captures the hash of that text for auditor verification.
4672
+ */
4673
+ setPerSurfaceChoice(surface: Surface, substrate: SubstrateChoice): Promise<void>;
4674
+ /**
4675
+ * Apply a substrate choice to every surface in one save. Used by the
4676
+ * picker modal's "Apply to all surfaces" affordance (Finding SS,
4677
+ * v1.2.0-rc.1). Persists the per-surface map mutation in a single
4678
+ * write and emits ONE `intelligence_bulk_substrate_chosen` audit
4679
+ * event rather than six per-surface events.
4680
+ *
4681
+ * If `localModelPick` is provided AND substrate === "local", the same
4682
+ * pick is applied to every surface; ignored for other substrates.
4683
+ *
4684
+ * Per-surface bindings the operator had previously customized are
4685
+ * captured in the audit payload's `prior_substrates` map so the
4686
+ * forensic record makes the configuration discontinuity visible.
4687
+ */
4688
+ applyChoiceToAllSurfaces(substrate: SubstrateChoice, opts?: {
4689
+ localModelPick?: LocalModelPick | null;
4690
+ }): Promise<void>;
4691
+ /**
4692
+ * Persist the operator's "Apply to all surfaces" preference. The
4693
+ * picker modal calls this when the operator flips the toggle so the
4694
+ * preference survives a dashboard reload. Does not emit a discrete
4695
+ * audit event; the next bulk or per-surface choice carries the
4696
+ * operator-visible signal.
4697
+ */
4698
+ setApplyToAllPreference(value: boolean): Promise<void>;
4699
+ /**
4700
+ * Operator picked a local model for a surface bound to the local
4701
+ * substrate. Mirrors `setPerSurfaceChoice` for the model-picker case.
4702
+ * Does not emit a separate audit event class; the next
4703
+ * `intelligence_substrate_chosen` event captures the change and the
4704
+ * dashboard transparency UI calls this method only as part of a binding
4705
+ * flow that already audited the substrate choice.
4706
+ */
4707
+ setLocalModelPick(surface: Surface, pick: LocalModelPick | null): Promise<void>;
4708
+ /**
4709
+ * Set the Venice API key. Stored as part of the encrypted
4710
+ * SubstrateConfig record. Does not emit a substrate_chosen event by
4711
+ * itself; the dashboard flow always pairs key entry with a substrate
4712
+ * choice and the choice carries the audit semantics.
4713
+ *
4714
+ * Post-set validation (Finding VV, v1.2.0-rc.1): if a non-null key is
4715
+ * provided, the selector probes Venice with `validateKey()` and records
4716
+ * a failure entry on every Venice-bound surface when the result is not
4717
+ * `"ok"`. This lets the operator-visible status degrade from validation
4718
+ * outcomes alone, not just from runtime chat-call failures, so a broken
4719
+ * key or drifted model is visible before the operator's first chat
4720
+ * attempt. Probe failures (timeout, transport) are not recorded; the
4721
+ * runtime path will surface those when the operator actually invokes.
4722
+ */
4723
+ setVeniceApiKey(apiKey: string | null): Promise<void>;
4724
+ /**
4725
+ * Probe Venice with the given API key and record a failure entry on
4726
+ * every surface bound to Venice when the probe returns anything other
4727
+ * than `"ok"`. Failures map to stable substrate failure-class enum
4728
+ * values: `invalid-key` -> `substrate_auth_failed`, `invalid-model` ->
4729
+ * `substrate_misconfigured`, `unreachable` -> swallow (runtime path
4730
+ * will surface).
4731
+ */
4732
+ private validateVeniceAndRecord;
4733
+ /**
4734
+ * Set a frontier provider's API key. See `setVeniceApiKey` for the
4735
+ * audit-semantics rationale.
4736
+ */
4737
+ setFrontierApiKey(provider: FrontierProvider, apiKey: string | null): Promise<void>;
4738
+ /**
4739
+ * Set the hybrid routing rules. The picker modal calls this when the
4740
+ * operator saves the hybrid configuration tab. Validates the rules
4741
+ * (every surface bound, no `hybrid` recursion) before persist; throws
4742
+ * on invalid input so the operator-facing form can surface the failure.
4743
+ *
4744
+ * Setting rules does NOT change any surface's per-surface choice; the
4745
+ * operator must also pick `hybrid` for the surfaces they want routed
4746
+ * through these rules. This separation keeps the flow obvious in the
4747
+ * UI (set rules + then choose hybrid for each surface that should use
4748
+ * them) and lets the operator test rule changes without mass-flipping
4749
+ * surfaces to hybrid.
4750
+ */
4751
+ setHybridRules(rules: HybridRoutingRules): Promise<void>;
4752
+ /**
4753
+ * Override the per-surface fallback behavior. Defaults are set per
4754
+ * position paper section 5; the picker modal surfaces the tradeoff and
4755
+ * the operator can override.
4756
+ */
4757
+ setFallbackBehavior(surface: Surface, fallback: FallbackBehavior): Promise<void>;
4758
+ /**
4759
+ * Reset every binding to the per-surface defaults. Emits
4760
+ * `intelligence_config_reset` so post-reset audit trails make the
4761
+ * configuration discontinuity visible.
4762
+ */
4763
+ resetToDefaults(): Promise<void>;
4764
+ /**
4765
+ * Snapshot the current config (read-only). Tests + the dashboard
4766
+ * transparency UI consume this; mutations always go through the
4767
+ * setter methods so audit-emission stays consistent.
4768
+ */
4769
+ getConfig(): SubstrateConfig;
4770
+ /**
4771
+ * Install (or replace) the redactor used by the frontier-with-filter
4772
+ * substrate. Bootstrap code calls this after constructing the selector
4773
+ * with the default IDENTITY_REDACTOR; the Privacy Filter Tier 2
4774
+ * commit's `buildPrivacyTier2Redactor` produces the redactor that
4775
+ * closes over the selector for audit emission. Late-binding via this
4776
+ * method avoids the circular construction dependency
4777
+ * (selector needs redactor; redactor needs selector for emit).
4778
+ *
4779
+ * Subsequent calls to getSubstrate("...frontier-with-filter") will use
4780
+ * the installed redactor; already-issued handles continue to use the
4781
+ * redactor that was installed at handle-issue time. Consumers that
4782
+ * cache handles SHOULD re-issue after installRedactor.
4783
+ */
4784
+ installRedactor(redactor: FrontierRedactor): void;
4785
+ /**
4786
+ * Build a typed SubstrateHandle for the given surface. Lazily
4787
+ * instantiates the substrate client based on the operator's binding;
4788
+ * the handle carries the substrate label, tradeoff badge, capability
4789
+ * surface, and bound `summarize` / `classify` / `redact` methods that
4790
+ * delegate to the substrate client.
4791
+ *
4792
+ * For the `disabled` substrate, the returned handle has the capability
4793
+ * surface zeroed and no methods bound; consumers checking
4794
+ * `handle.capability.summarize === false` short-circuit to the
4795
+ * surface's static fallback (templates for gate-explanation, Tier 1
4796
+ * regex for privacy-filter-tier-2, etc.).
4797
+ *
4798
+ * For the `hybrid` substrate, this method delegates to the per-surface
4799
+ * routing rules (set via `setPerSurfaceChoice` on a substrate other
4800
+ * than `hybrid`); v1.2 ships per-surface routing only.
4801
+ */
4802
+ getSubstrate(surface: Surface): Promise<SubstrateHandle>;
4803
+ /**
4804
+ * Operator-visible per-surface status report. Consumers: the dashboard
4805
+ * transparency UI and the `/api/hub/intelligence/status` route.
4806
+ *
4807
+ * Probes hardware + Ollama at call time. The dashboard re-fetches every
4808
+ * 5 minutes to refresh status badges (degraded -> ok flip when Ollama
4809
+ * comes back up, and so on).
4810
+ */
4811
+ getOperatorVisibleStatus(): Promise<SubstrateStatusReport>;
4812
+ /**
4813
+ * Probe the host machine's hardware capability. v1.2 reports total RAM
4814
+ * + CPU arch (Apple Silicon family if detectable) and computes the tier
4815
+ * per position paper section 5. Used by the picker modal to surface
4816
+ * "below-baseline -> recommend Venice" guidance.
4817
+ */
4818
+ probeHardware(): Promise<HardwareCapabilityReport>;
4819
+ /**
4820
+ * Invoke a substrate with audit emission. Wraps the underlying
4821
+ * SubstrateHandle method, applies fallback behavior on failure per the
4822
+ * operator's per-surface config, and emits `intelligence_substrate_invoked`
4823
+ * + (on failure) `intelligence_substrate_failure`.
4824
+ *
4825
+ * Consumers SHOULD call this method rather than calling `handle.summarize`
4826
+ * etc. directly; the handle is exposed for cases where audit emission
4827
+ * needs to be explicitly skipped (probe paths, tests).
4828
+ */
4829
+ invokeSummarize(surface: Surface, req: SummarizeRequest): Promise<SubstrateResponse>;
4830
+ invokeClassify(surface: Surface, req: ClassifyRequest): Promise<SubstrateResponse>;
4831
+ invokeRedact(surface: Surface, req: RedactRequest): Promise<SubstrateResponse>;
4832
+ private ensureLoaded;
4833
+ private invoke;
4834
+ /**
4835
+ * Append a failure entry to the per-surface ring buffer. The selector
4836
+ * uses this in the `invoke()` failure path and in the post-config
4837
+ * `setVeniceApiKey` validation hook so the operator-visible badge
4838
+ * degrades from runtime AND from validation outcomes.
4839
+ *
4840
+ * Operator-safe by construction: snippets are bounded by
4841
+ * `FAILURE_SNIPPET_MAX_LEN` and pulled from substrate `failure.message`
4842
+ * fields (which substrates produce for operator-readable output and
4843
+ * never include API keys or PII). The cap + window prune happen on
4844
+ * every read in `recentFailuresFor()` so the ring buffer never grows.
4845
+ */
4846
+ private recordRecentFailure;
4847
+ /**
4848
+ * Read recent failures for a surface, pruning entries older than the
4849
+ * 24-hour window. Pruning happens lazily on read so a long-quiet surface
4850
+ * does not retain stale entries from yesterday's debugging.
4851
+ */
4852
+ private recentFailuresFor;
4853
+ /**
4854
+ * Test-only seam: lets selector tests assert on recorded failures
4855
+ * without poking the private ring buffer. Returns a defensive copy.
4856
+ */
4857
+ getRecentFailuresForTest(surface: Surface): RecentFailureEntry[];
4858
+ /**
4859
+ * Test-only seam: lets selector tests seed a recent-failure entry on a
4860
+ * surface so the buffer-clear behavior added for Finding ZZ (v1.2.0-rc.2)
4861
+ * can be exercised without spinning up a real failing substrate. Mirrors
4862
+ * `getRecentFailuresForTest`; never call from production paths.
4863
+ */
4864
+ recordRecentFailureForTest(surface: Surface, failureClass: SubstrateFailureClass, snippet: string): void;
4865
+ /**
4866
+ * Build a substrate handle for a surface bound to a concrete choice.
4867
+ * The substrate-client constructor is paid lazily so the selector
4868
+ * doesn't pre-spawn HTTP clients for substrates the operator never
4869
+ * touches.
4870
+ */
4871
+ private buildHandle;
4872
+ private disabledHandle;
4873
+ private localHandle;
4874
+ private veniceHandle;
4875
+ private frontierHandle;
4876
+ private probeSurfaceHealth;
4877
+ private makeBadge;
4878
+ private fallbackAction;
4879
+ /**
4880
+ * Internal hook for the Privacy Filter Tier 2 commit. Lets a redactor
4881
+ * audit-emit a `pii_redaction_event` payload through the selector
4882
+ * without exposing the AuditLog binding directly to redactor
4883
+ * implementations.
4884
+ */
4885
+ emitRedactionEvent(args: {
4886
+ surface: Surface;
4887
+ substrate: SubstrateChoice;
4888
+ matchCount: number;
4889
+ filterTier: 1 | 2;
4890
+ }): void;
4891
+ private emit;
4892
+ }
4893
+
4894
+ /**
4895
+ * Sanctuary MCP Server — Operator Chat Types
4896
+ *
4897
+ * Distinct from the existing agent-to-agent mesh chat (`chat-service.ts`).
4898
+ * Operator chat is the operator-fortress concierge surface: an operator
4899
+ * types into the v1.1 dashboard, Sanctuary persists the conversation,
4900
+ * audits every message, and routes responses through the substrate
4901
+ * selector.
4902
+ *
4903
+ * The direct-agent surface (operator-to-wrapped-agent conversation) was
4904
+ * removed in the v1.2 reshape. The concierge surface (operator-to-
4905
+ * Sanctuary) is the only operator chat surface that ships in v1.2.
4906
+ *
4907
+ * Sovereignty invariants:
4908
+ * - Conversation history is encrypted at rest under the fortress master
4909
+ * key via an HKDF-derived purpose key.
4910
+ * - Audit emission is non-optional: every operator submit and every
4911
+ * concierge reply lands in the L2 audit log.
4912
+ * - No raw message bodies are stored in audit `details`; only message
4913
+ * hashes and safe metadata. Bodies live in the encrypted chat store
4914
+ * that the operator can export, delete, or rotate.
4915
+ */
4916
+
4917
+ /**
4918
+ * The chat surface a message belongs to. Concierge is the only operator
4919
+ * chat surface in v1.2; the type stays a single-member union for
4920
+ * forward-compatibility if additional Sanctuary-side surfaces land later.
4921
+ */
4922
+ type OperatorChatSurface = "concierge";
4923
+ /**
4924
+ * Message-role enum. Concierge has two roles: `operator` (the human) and
4925
+ * `concierge` (Sanctuary's response).
4926
+ */
4927
+ type OperatorChatRole = "operator" | "concierge";
4928
+ /**
4929
+ * One message in a concierge thread. The body is stored encrypted at
4930
+ * rest; the in-memory shape carries the cleartext for service consumers,
4931
+ * and the audit emission carries only `message_hash` + safe metadata.
4932
+ */
4933
+ interface OperatorChatMessage {
4934
+ /** Stable message id (uuid) assigned at create time. */
4935
+ message_id: string;
4936
+ /** Surface this message belongs to. */
4937
+ surface: OperatorChatSurface;
4938
+ /** Sender role. */
4939
+ role: OperatorChatRole;
4940
+ /** Cleartext body. NEVER appears in audit `details`. */
4941
+ body: string;
4942
+ /** ISO8601 timestamp the message was created. */
4943
+ created_at: string;
4944
+ /**
4945
+ * For concierge response messages: which substrate served the response.
4946
+ * Null on operator-sent messages. Surfaces in the chat header badge.
4947
+ */
4948
+ served_by?: SubstrateChoice;
4949
+ /**
4950
+ * For concierge response messages: latency of the substrate call in ms.
4951
+ * Operator-side surfaces this in the message tooltip / activity feed.
4952
+ */
4953
+ substrate_latency_ms?: number;
4954
+ }
4955
+ /**
4956
+ * The shape persisted to disk under `_chat/<surface>/<thread-key>`.
4957
+ *
4958
+ * Threads are append-only; the persisted record is read-rewritten on
4959
+ * every message append. v1.2 caps thread length at
4960
+ * `OPERATOR_CHAT_MAX_THREAD_LENGTH`; older messages drop off when the
4961
+ * cap is exceeded.
4962
+ */
4963
+ interface OperatorChatThread {
4964
+ /** Forward-compat field. v1.2 uses 1; bump on schema change. */
4965
+ version: 1;
4966
+ /** Surface (concierge in v1.2). */
4967
+ surface: OperatorChatSurface;
4968
+ /** Thread key; concierge is fortress-scoped under `CONCIERGE_THREAD_KEY`. */
4969
+ thread_key: string;
4970
+ /** Append-only message log; oldest first. */
4971
+ messages: OperatorChatMessage[];
4972
+ /** ISO8601 timestamp the thread was last updated. */
4973
+ updated_at: string;
4974
+ }
4975
+ /**
4976
+ * Concierge response envelope. Carries the substrate response (or
4977
+ * fallback message) plus the operator-visible substrate badge.
4978
+ */
4979
+ interface ConciergeResponse {
4980
+ /** The response message persisted into the concierge thread. */
4981
+ message: OperatorChatMessage;
4982
+ /** The substrate that served this query. */
4983
+ served_by: SubstrateChoice;
4984
+ /** Operator-visible label rendered next to the response. */
4985
+ display_label: string;
4986
+ /** Outcome class for activity-feed projection. */
4987
+ outcome: "ok" | "substrate_failure" | "substrate_disabled";
4988
+ }
4989
+
4990
+ /**
4991
+ * Sanctuary MCP Server — Operator Chat Persistence
4992
+ *
4993
+ * Encrypted at-rest store for the operator-chat concierge thread.
4994
+ * Mirrors the `IntelligenceConfigStore` shape but addresses records by
4995
+ * `(surface, thread-key)` for forward-compatibility.
4996
+ *
4997
+ * Storage layout:
4998
+ * namespace: `_chat` (underscore-prefixed = reserved L1 namespace)
4999
+ * key: `concierge.{thread_key}`
5000
+ * payload: AES-256-GCM-encrypted `OperatorChatThread` JSON, key
5001
+ * derived via HKDF from the fortress master key with info
5002
+ * string `operator-chat-store-v1`.
5003
+ *
5004
+ * The direct-agent surface (per-agent threads + session records) was
5005
+ * removed in the v1.2 reshape. The concierge surface stores under a
5006
+ * single record with sentinel thread-key `_fortress`
5007
+ * (CONCIERGE_THREAD_KEY).
5008
+ *
5009
+ * Append-only semantics:
5010
+ * Threads are append-only at the message level. Persist is full-record
5011
+ * read-modify-write; the cap on thread length is enforced at append time
5012
+ * (oldest messages fall off when `OPERATOR_CHAT_MAX_THREAD_LENGTH` is
5013
+ * exceeded). The audit log retains every message regardless.
5014
+ */
5015
+
5016
+ /**
5017
+ * Encrypted operator chat persistence.
5018
+ */
5019
+ declare class OperatorChatStore {
5020
+ private storage;
5021
+ private encryptionKey;
5022
+ constructor(storage: StorageBackend, masterKey: Uint8Array);
5023
+ /**
5024
+ * Load a thread. Returns null if no record exists or if the on-disk
5025
+ * record is corrupt; callers treat both as "empty thread, start
5026
+ * fresh" and emit no audit event for the absence.
5027
+ */
5028
+ loadThread(surface: OperatorChatSurface, threadKey: string): Promise<OperatorChatThread | null>;
5029
+ /**
5030
+ * Persist a thread. Caller is responsible for cap enforcement; the
5031
+ * `appendMessage` helper below drops oldest messages before persist
5032
+ * when the cap is exceeded.
5033
+ */
5034
+ saveThread(thread: OperatorChatThread): Promise<void>;
5035
+ /**
5036
+ * Append a message to a thread, creating the thread if it doesn't
5037
+ * exist, and persist. Enforces the per-thread length cap by dropping
5038
+ * oldest messages first. Returns the persisted thread.
5039
+ */
5040
+ appendMessage(surface: OperatorChatSurface, threadKey: string, message: OperatorChatMessage): Promise<OperatorChatThread>;
5041
+ /**
5042
+ * Delete a thread. Used by operator-driven thread reset. The audit
5043
+ * log retains the per-message history regardless.
5044
+ */
5045
+ deleteThread(surface: OperatorChatSurface, threadKey: string): Promise<void>;
5046
+ }
5047
+
5048
+ /**
5049
+ * Sanctuary MCP Server — Operator Chat Service
5050
+ *
5051
+ * The operator-fortress concierge surface. Distinct from the
5052
+ * agent-to-agent mesh chat (`chat-service.ts`) which handles libp2p +
5053
+ * OpenMLS group sessions among wrapped agents.
5054
+ *
5055
+ * The direct-agent surface (operator-to-wrapped-agent conversation) was
5056
+ * removed in the v1.2 reshape; the concierge surface is the only
5057
+ * operator-chat surface in v1.2.
5058
+ *
5059
+ * Concierge surface (path: `sendConcierge`):
5060
+ * 1. PII-filter the operator query (Tier 1 regex).
5061
+ * 2. Build fortress context from audit log + activity feed +
5062
+ * agent registry (`assembleConciergeContext`).
5063
+ * 3. Invoke the substrate selector at `surface: "concierge"`.
5064
+ * 4. Persist operator query + concierge response in the concierge
5065
+ * thread under `_chat/concierge.<sentinel>`.
5066
+ * 5. Emit `operator_concierge_chat` audit event with safe metadata.
5067
+ *
5068
+ * Sovereignty invariants:
5069
+ * - Audit emission is non-optional on every public method that mutates
5070
+ * the chat state. Construction with no audit log is structurally
5071
+ * rejected.
5072
+ * - The substrate selector is OPTIONAL: a service constructed without
5073
+ * one degrades to "Concierge unavailable; substrate not configured".
5074
+ * The wiring layer can instantiate the service before the substrate
5075
+ * selector when bootstrap order requires it.
5076
+ * - The PII filter is the Tier 1 regex shipped in
5077
+ * `l2-operational/privacy-filter.ts`; Tier 2 NER+LLM redaction lives
5078
+ * in the substrate-selector layer (substrate-routed), not here.
5079
+ */
5080
+
5081
+ /**
5082
+ * Snapshot of fortress state surfaces the concierge consults to
5083
+ * answer operator queries. Caller (the hub-service wiring) supplies
5084
+ * the providers; the service wires them into the substrate prompt.
5085
+ *
5086
+ * Each provider returns plain strings the substrate selector folds
5087
+ * into a single context blob; the service does not attempt to
5088
+ * structure the prompt beyond stitching with newlines. Keeping the
5089
+ * prompt-shape decisions in the service-construction layer (rather
5090
+ * than in the wiring) lets the dashboard team iterate concierge
5091
+ * prompts without touching server code.
5092
+ */
5093
+ interface ConciergeContextProviders {
5094
+ /** Recent activity-feed entries as a free-form string, oldest first. */
5095
+ recentActivity: () => Promise<string>;
5096
+ /** Wrapped-agent inventory as a free-form string. */
5097
+ agentInventory: () => Promise<string>;
5098
+ /** Open inbox items as a free-form string. */
5099
+ openInbox: () => Promise<string>;
5100
+ }
5101
+ /**
5102
+ * PII filter the concierge applies to operator queries before passing
5103
+ * them to the substrate. v1.2 wires the Tier 1 regex; the substrate
5104
+ * selector handles Tier 2 NER+LLM redaction internally on egress paths.
5105
+ *
5106
+ * Returns the filtered query plus the count of redactions performed
5107
+ * (surfaced in the audit event for operator visibility).
5108
+ */
5109
+ interface ConciergePiiFilter {
5110
+ filter(input: string): {
5111
+ filtered: string;
5112
+ redactions: number;
5113
+ };
5114
+ }
5115
+ /**
5116
+ * Construction inputs for the operator chat service.
5117
+ */
5118
+ interface OperatorChatServiceDeps {
5119
+ /** Encrypted at-rest store; built from fortress master key. */
5120
+ store: OperatorChatStore;
5121
+ /** Audit log for `operator_concierge_chat` events. */
5122
+ auditLog: AuditLog;
5123
+ /** Operator identity that owns the chat session. */
5124
+ identityId: string;
5125
+ /**
5126
+ * Substrate selector for concierge LLM calls. Optional: when omitted
5127
+ * the concierge surface degrades to a "substrate not configured"
5128
+ * response.
5129
+ */
5130
+ substrateSelector?: SubstrateSelector;
5131
+ /**
5132
+ * Concierge context providers. Required when a substrate selector
5133
+ * is wired; otherwise unused. Wiring layer assembles these from the
5134
+ * activity feed, agent registry, and inbox aggregator.
5135
+ */
5136
+ conciergeContextProviders?: ConciergeContextProviders;
5137
+ /**
5138
+ * PII filter applied to concierge queries pre-substrate. Optional;
5139
+ * when omitted the service still works (no redactions performed).
5140
+ * v1.2 wiring passes the Tier 1 regex.
5141
+ */
5142
+ conciergePiiFilter?: ConciergePiiFilter;
5143
+ /**
5144
+ * Stable max tokens for concierge responses. Defaults to 512 (small
5145
+ * enough for snappy local-model latency, large enough for a useful
5146
+ * summary). Operator-tunable via dashboard config.
5147
+ */
5148
+ conciergeMaxTokens?: number;
5149
+ }
5150
+ declare class OperatorChatService {
5151
+ private store;
5152
+ private auditLog;
5153
+ private identityId;
5154
+ private substrateSelector?;
5155
+ private contextProviders?;
5156
+ private piiFilter?;
5157
+ private conciergeMaxTokens;
5158
+ constructor(deps: OperatorChatServiceDeps);
5159
+ /**
5160
+ * Operator submit on the concierge surface. Persists the operator's
5161
+ * query, fans out to the substrate selector, persists the response,
5162
+ * and emits the audit event.
5163
+ *
5164
+ * On substrate failure or absence: persists an honest "concierge
5165
+ * unavailable" response and audit-emits with `outcome` = the
5166
+ * appropriate failure class. The chat history reflects what the
5167
+ * operator sees on the page (no silent dropping).
5168
+ */
5169
+ sendConcierge(query: string): Promise<ConciergeResponse>;
5170
+ /**
5171
+ * Read the persisted concierge thread, oldest message first. Returns
5172
+ * an empty array when no thread exists yet.
5173
+ */
5174
+ getConciergeHistory(): Promise<OperatorChatMessage[]>;
5175
+ /**
5176
+ * Stitch fortress state into a single context blob the substrate
5177
+ * folds into its summarization prompt.
5178
+ *
5179
+ * The blob is intentionally plain text (not structured JSON) because
5180
+ * small local models (Gemma 2 2B) handle plain text more reliably
5181
+ * than nested structures. Format:
5182
+ *
5183
+ * ```
5184
+ * ## Recent activity
5185
+ * <recentActivity output>
5186
+ *
5187
+ * ## Wrapped agents
5188
+ * <agentInventory output>
5189
+ *
5190
+ * ## Open inbox
5191
+ * <openInbox output>
5192
+ * ```
5193
+ */
5194
+ private assembleConciergeContext;
5195
+ private emit;
5196
+ }
5197
+
4137
5198
  /**
4138
5199
  * Sanctuary v1.1. Operator Hub API Types
4139
5200
  *
@@ -4171,6 +5232,11 @@ interface HubTier1ApprovalEnqueuedResult {
4171
5232
  */
4172
5233
  operation_category: HubApprovalPendingItem["operation_category"];
4173
5234
  }
5235
+ interface HubTemplateBindingApprovalEnqueuedResult extends HubTier1ApprovalEnqueuedResult {
5236
+ current_template_id?: ChannelTemplateId;
5237
+ proposed_template_id: ChannelTemplateId;
5238
+ compiled_policy_id: string;
5239
+ }
4174
5240
  /**
4175
5241
  * Result returned from a fortress-scope Tier-1 action that has been
4176
5242
  * deferred to operator approval. Distinguished from the per-agent shape by
@@ -4225,11 +5291,7 @@ interface HubAgentController {
4225
5291
  * approval lands.
4226
5292
  */
4227
5293
  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
- */
5294
+ /** Apply an operator-approved channel-template binding. */
4233
5295
  bindChannelTemplate(agentId: string, templateId: ChannelTemplateId): Promise<void>;
4234
5296
  }
4235
5297
  /**
@@ -4362,6 +5424,11 @@ interface HubServiceDeps {
4362
5424
  * returning the in-memory projection.
4363
5425
  */
4364
5426
  readPersistedLocalAgents?: () => LocalAgentRecord[];
5427
+ /**
5428
+ * Optional persisted agent writer. Used when approved hub actions mutate
5429
+ * registry-local fields that should survive refresh and restart.
5430
+ */
5431
+ writePersistedLocalAgents?: (records: LocalAgentRecord[]) => void;
4365
5432
  /** Inbox aggregation sources. */
4366
5433
  inboxSources: HubInboxSources;
4367
5434
  /** Activity feed sources. */
@@ -4391,6 +5458,44 @@ interface HubServiceDeps {
4391
5458
  * Defaults to `() => new Date()` when omitted.
4392
5459
  */
4393
5460
  now?: () => Date;
5461
+ /**
5462
+ * Optional operator-chat service. When supplied, the hub exposes the
5463
+ * concierge chat endpoints; when omitted, those routes return
5464
+ * HubCapabilityError. Construction-time injection lets the dashboard
5465
+ * wiring layer build the chat service against the same audit log +
5466
+ * master key + storage backend as the rest of the v1.1 hub without
5467
+ * forcing the chat-service constructor through a circular import.
5468
+ * The direct-agent chat surface was removed in the v1.2 reshape
5469
+ * (2026-04-30); the field name is retained for source compatibility.
5470
+ */
5471
+ operatorChat?: OperatorChatService;
5472
+ }
5473
+ /**
5474
+ * Click-to-inspect panel returned by `openAgentInspectPanel`. The panel
5475
+ * surfaces the agent's recent activity, pending approvals routed through
5476
+ * this agent, and policy summary at a glance, repurposing the click-
5477
+ * to-chat wire-up shipped in PR #98 + PR #100. The direct-agent chat
5478
+ * surface was removed in the v1.2 reshape; this panel is the operational
5479
+ * destination for the click.
5480
+ */
5481
+ interface HubAgentInspectPanel {
5482
+ /** The agent the panel was opened on. */
5483
+ agent_id: string;
5484
+ /** ISO8601 timestamp the panel was opened. */
5485
+ opened_at: string;
5486
+ /** Recent activity-feed entries for this agent, oldest first. */
5487
+ recent_activity: HubActivityFeedEntry[];
5488
+ /**
5489
+ * Open Tier 1 inbox items routed through this agent. Empty when the
5490
+ * agent has no pending approvals.
5491
+ */
5492
+ pending_approvals: HubInboxItem[];
5493
+ /**
5494
+ * Bound policy summary for this agent. Null when the agent has no
5495
+ * channel-template binding (e.g. immediately after wrap, before the
5496
+ * operator picks a template).
5497
+ */
5498
+ policy_summary: HubPolicySummary | null;
4394
5499
  }
4395
5500
 
4396
5501
  /**
@@ -4438,10 +5543,10 @@ declare class HubService {
4438
5543
  */
4439
5544
  bindAgentPolicy(agentId: string, policyId: string): HubTier1ApprovalEnqueuedResult;
4440
5545
  /**
4441
- * Bind a different channel template. Not Tier 1; channel templates are
4442
- * compositions over the four canonical slots and bind synchronously.
5546
+ * Bind a different channel template. Tier 1: enqueues a policy_change
5547
+ * approval item and only applies the binding after operator approval.
4443
5548
  */
4444
- bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown): Promise<LocalAgentRecord>;
5549
+ bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown): HubTemplateBindingApprovalEnqueuedResult;
4445
5550
  /**
4446
5551
  * Enqueue a fortress-scope Tier 1 lockdown. One inbox item is created;
4447
5552
  * on operator approval the handler iterates `agentController.lockdown`
@@ -4484,6 +5589,33 @@ declare class HubService {
4484
5589
  * Test/inspection helper. Not part of the public service surface.
4485
5590
  */
4486
5591
  inboxStoreSize(): number;
5592
+ private requireOperatorChat;
5593
+ /**
5594
+ * Operator submit on the concierge chat surface. Pass-through to the
5595
+ * operator-chat-service which owns substrate-selector routing, PII
5596
+ * filtering, persistence, and audit emission.
5597
+ */
5598
+ sendConcierge(query: string): Promise<ConciergeResponse>;
5599
+ /**
5600
+ * Read the persisted concierge thread.
5601
+ */
5602
+ getConciergeHistory(): Promise<OperatorChatMessage[]>;
5603
+ /**
5604
+ * Open the click-to-inspect/approve panel for a wrapped agent. The
5605
+ * panel surfaces recent activity routed through this agent, pending
5606
+ * Tier 1 approvals on this agent, and the bound policy summary,
5607
+ * repurposing the click-to-chat wire-up from PR #98 + PR #100 to a
5608
+ * substrate-aligned inspect destination after the v1.2 reshape removed
5609
+ * direct-agent chat.
5610
+ *
5611
+ * Synchronous open shape preserved (caller's `await` semantics
5612
+ * unchanged from the prior `openDirectAgentSession` surface). The
5613
+ * panel is read-only; no side effects beyond the audit-event emission
5614
+ * for the panel-open itself.
5615
+ */
5616
+ openAgentInspectPanel(agentId: string, options?: {
5617
+ activityLimit?: number;
5618
+ }): Promise<HubAgentInspectPanel>;
4487
5619
  }
4488
5620
 
4489
5621
  /**
@@ -4509,16 +5641,29 @@ declare class HubService {
4509
5641
  * the v1.2 work to project those into operator cards.
4510
5642
  * - Activity feed reads from the real audit log. This is the one source
4511
5643
  * 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.
5644
+ * - Agent controller errors on runtime harness actions that v1.1 cannot
5645
+ * honestly execute. Channel-template binding is registry-local in v1.2,
5646
+ * so its controller hook is a no-op and HubService persists the binding
5647
+ * after Tier 1 approval.
4516
5648
  */
4517
5649
 
4518
5650
  interface V11Bindings {
4519
5651
  hubService: HubService;
4520
5652
  identityId: string;
4521
5653
  fortressId: string;
5654
+ /**
5655
+ * Optional Intelligence Substrate Selector. Dispatch layer routes
5656
+ * `/api/hub/intelligence/*` here when set; absent means the routes
5657
+ * 503 with a "selector not configured" body.
5658
+ */
5659
+ intelligenceSelector?: SubstrateSelector;
5660
+ /**
5661
+ * Optional Operator Chat Service. Constructed when storage + masterKey
5662
+ * are passed to `buildV11Bindings`. The HubService accepts this
5663
+ * directly via its `operatorChat` dep; callers that want the harness-
5664
+ * side `recordAgentReply` integration also need the service handle.
5665
+ */
5666
+ operatorChatService?: OperatorChatService;
4522
5667
  }
4523
5668
 
4524
5669
  /**
@@ -5624,4 +6769,4 @@ declare function createSanctuaryServer(options?: {
5624
6769
  storage?: StorageBackend;
5625
6770
  }): Promise<SanctuaryServer>;
5626
6771
 
5627
- export { ATTESTATION_VERSION, type ActivityEntry, type AggregatorSources, ApprovalGate, type ApprovalHandlers, type AttestationBody, type AttestationVerificationResult, AuditLog, AutoApproveChannel, BaselineTracker, type BridgeAttestationRequest, type BridgeAttestationResult, type BridgeCommitment, type BridgeVerificationResult, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, type ConcordiaOutcome, type ConnectionState, type ContextAction, type ContextFilterResult, ContextGateEnforcer, type ContextGatePolicy, ContextGatePolicyStore, type ContextGateRule, type ContextGateTemplate, DashboardApprovalChannel, type DashboardConfig, type DashboardHandle, type DashboardServerOptions, type DetectionResult, type EnforcerConfig, type ExitAuditReceiptsArtifact, type ExitBundleDetailedVerifierResult, type ExitCommandArgs, type ExitCommitmentsArtifact, type ExitEncryptedStateBundle, type ExitPlaceholderVaultMetadataArtifact, type ExitPolicySetArtifact, type ExitPublicIdentityArtifact, type ExportExitBundleOptions, type ExportExitBundleResult, type FederationCapabilities, type FederationPeer, FederationRegistry, type FieldClassification, type FieldFilterResult, FilesystemStorage, type GateResult, HERO_COPY, type HandshakeChallenge, type HandshakeCompletion, type HandshakeResponse, type HandshakeResult, type ImportExitBundleOptions, type ImportExitBundleResult, InMemoryModelProvenanceStore, InjectionDetector, type InjectionDetectorConfig, type InjectionSignal, type L1Status, type L2Status, type L3Status, type L4Status, type LoadedExitArtifact, MODEL_PRESETS, MemoryStorage, type ModelProvenance, type ModelProvenanceStore, type PedersenCommitment, type PeerTrustEvaluation, type PendingApproval, type PolicyRecommendation, PolicyStore, type PrincipalPolicy, type ProtectionSnapshot, type ProviderCategory, ProxyRouter, type ProxyRouterOptions, type ReputationLookup, ReputationStore, type SHRBody, type SHRGeneratorOptions, type SHRVerificationResult, type SanctuaryConfig, type SanctuaryServer, type SignedAttestation, type SignedSHR, type SovereigntyProfile, SovereigntyProfileStore, type SovereigntyProfileUpdate, type SovereigntyTier, type StartDashboardOptions, StateStore, StderrApprovalChannel, type StreamEvent, TIER_WEIGHTS, type TierMetadata, type TieredAttestation, type UpstreamConnection, type UpstreamServer, type UpstreamTool, WebhookApprovalChannel, type WebhookCallbackPayload, type WebhookConfig, type WebhookPayload, type ZKProofOfKnowledge, type ZKRangeProof, canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
6772
+ export { ATTESTATION_VERSION, type ActivityEntry, type AggregatorSources, ApprovalGate, type ApprovalHandlers, type AttestationBody, type AttestationVerificationResult, AuditLog, AutoApproveChannel, BaselineTracker, type BridgeAttestationRequest, type BridgeAttestationResult, type BridgeCommitment, type BridgeVerificationResult, TEMPLATES as CONTEXT_GATE_TEMPLATES, CallbackApprovalChannel, ClientManager, CommitmentStore, type ConcordiaOutcome, type ConnectionState, type ContextAction, type ContextFilterResult, ContextGateEnforcer, type ContextGatePolicy, ContextGatePolicyStore, type ContextGateRule, type ContextGateTemplate, DashboardApprovalChannel, type DashboardConfig, type DashboardHandle, type DashboardServerOptions, type DetectionResult, type EnforcerConfig, type ExitAuditReceiptsArtifact, type ExitBundleDetailedVerifierResult, ExitBundleImportError, type ExitCommandArgs, type ExitCommitmentsArtifact, type ExitEncryptedStateBundle, type ExitPlaceholderVaultMetadataArtifact, type ExitPolicySetArtifact, type ExitPublicIdentityArtifact, type ExportExitBundleOptions, type ExportExitBundleResult, type FederationCapabilities, type FederationPeer, FederationRegistry, type FieldClassification, type FieldFilterResult, FilesystemStorage, type GateResult, HERO_COPY, type HandshakeChallenge, type HandshakeCompletion, type HandshakeResponse, type HandshakeResult, type ImportExitBundleOptions, type ImportExitBundleResult, InMemoryModelProvenanceStore, InjectionDetector, type InjectionDetectorConfig, type InjectionSignal, type L1Status, type L2Status, type L3Status, type L4Status, type LoadedExitArtifact, MODEL_PRESETS, MemoryStorage, type ModelProvenance, type ModelProvenanceStore, type PedersenCommitment, type PeerTrustEvaluation, type PendingApproval, type PolicyRecommendation, PolicyStore, type PrincipalPolicy, type ProtectionSnapshot, type ProviderCategory, ProxyRouter, type ProxyRouterOptions, type ReputationLookup, ReputationStore, type SHRBody, type SHRGeneratorOptions, type SHRVerificationResult, type SanctuaryConfig, type SanctuaryServer, type SignedAttestation, type SignedSHR, type SovereigntyProfile, SovereigntyProfileStore, type SovereigntyProfileUpdate, type SovereigntyTier, type StartDashboardOptions, StateStore, StderrApprovalChannel, type StreamEvent, TIER_WEIGHTS, type TierMetadata, type TieredAttestation, type UpstreamConnection, type UpstreamServer, type UpstreamTool, type VerifyExitBundleOptions, WebhookApprovalChannel, type WebhookCallbackPayload, type WebhookConfig, type WebhookPayload, type ZKProofOfKnowledge, type ZKRangeProof, canonicalize, classifyField, completeHandshake, computeWeightedScore, createBridgeCommitment, createDefaultProfile, createPedersenCommitment, createProofOfKnowledge, createRangeProof, createSanctuaryServer, evaluateField, exitBundleManifestShape, exportExitBundle, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate, importExitBundle, initiateHandshake, listTemplateIds, loadConfig, loadExitArtifact, loadPrincipalPolicy, readManifest, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, runExitCommand, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyExitBundle, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };