@sanctuary-framework/mcp-server 1.0.0-rc.2 → 1.1.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.
package/dist/index.d.cts CHANGED
@@ -2,6 +2,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
3
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
4
4
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
+ import { Writable } from 'node:stream';
5
6
 
6
7
  interface SanctuaryConfig {
7
8
  version: string;
@@ -68,6 +69,12 @@ interface SanctuaryConfig {
68
69
  /** Whether to auto-publish on successful handshake_respond calls. */
69
70
  auto_publish_handshakes: boolean;
70
71
  };
72
+ privacy_filter: {
73
+ mode: "local" | "opf" | "off";
74
+ fail_mode: "closed" | "fallback";
75
+ command: string;
76
+ timeout_ms: number;
77
+ };
71
78
  }
72
79
  /**
73
80
  * Load configuration from file, falling back to defaults.
@@ -761,6 +768,28 @@ interface EncryptedPayload {
761
768
  * - Secure deletion overwrites before unlinking
762
769
  */
763
770
 
771
+ /** On-disk format for an encrypted state entry */
772
+ interface StateEntry {
773
+ /** Format version */
774
+ v: number;
775
+ /** Encrypted payload */
776
+ payload: EncryptedPayload;
777
+ /** Version number (monotonically increasing) */
778
+ ver: number;
779
+ /** Signature over ciphertext by the writing identity (base64url) */
780
+ sig: string;
781
+ /** Identity that wrote this entry */
782
+ kid: string;
783
+ /** SHA-256 of the plaintext value (base64url, for client-side verification) */
784
+ integrity_hash: string;
785
+ /** Metadata */
786
+ metadata: {
787
+ content_type?: string;
788
+ ttl_seconds?: number;
789
+ tags?: string[];
790
+ written_at: string;
791
+ };
792
+ }
764
793
  /** Result of a state write operation */
765
794
  interface WriteResult {
766
795
  key: string;
@@ -2122,6 +2151,124 @@ declare const MODEL_PRESETS: {
2122
2151
  mistral7bLocal: () => ModelProvenance;
2123
2152
  };
2124
2153
 
2154
+ /**
2155
+ * Signature scheme identifier embedded on every signed v1.1 record.
2156
+ *
2157
+ * v1.1 only accepts "ed25519-v1". Mirrors the mesh module: this field is the
2158
+ * forward-compat hinge for v1.4+ post-quantum migration. Verifiers MUST reject
2159
+ * unknown schemes. Do not optimize this field away.
2160
+ *
2161
+ * Mirror of: server/src/mesh/constants.ts SignatureScheme.
2162
+ */
2163
+ type SignatureScheme = "ed25519-v1";
2164
+ /**
2165
+ * Detector classes for the privacy filter. Workstreams MUST use this enum so
2166
+ * the dashboard privacy panel and audit consumers can group consistently.
2167
+ *
2168
+ * "custom" is reserved for operator-defined detectors via policy.
2169
+ */
2170
+ declare const PRIVACY_DETECTOR_CLASSES: readonly ["person", "client", "project", "secret", "credential", "account", "file_path", "domain_term", "custom"];
2171
+ type PrivacyDetectorClass = (typeof PRIVACY_DETECTOR_CLASSES)[number];
2172
+ /**
2173
+ * Outbound destination categories. The privacy filter and the remote-bound
2174
+ * enforcement workstream agree on this enum so events compose end-to-end.
2175
+ *
2176
+ * Categories deliberately mirror the L2 `ProviderCategory` taxonomy in
2177
+ * `server/src/l2-operational/context-gate.ts` (hyphen form) so JSON-stable
2178
+ * strings flow through both layers without translation. If the L2 category
2179
+ * set grows, keep this v1.1 list in lockstep through coordinator review.
2180
+ */
2181
+ declare const PRIVACY_DESTINATION_CATEGORIES: readonly ["inference", "tool-api", "logging", "analytics", "peer-agent", "custom"];
2182
+ type PrivacyDestinationCategory = (typeof PRIVACY_DESTINATION_CATEGORIES)[number];
2183
+ /**
2184
+ * Privacy actions taken on a single field. Uses the L2 vocabulary.
2185
+ *
2186
+ * "rehydrate" is v1.1-new and applies to inbound responses, not outbound calls.
2187
+ */
2188
+ declare const PRIVACY_ACTIONS: readonly ["allow", "redact", "hash", "summarize", "deny", "rehydrate"];
2189
+ type PrivacyAction = (typeof PRIVACY_ACTIONS)[number];
2190
+ /**
2191
+ * Hash algorithm identifier embedded in every privacy event content_hash.
2192
+ *
2193
+ * v1.1 ships ONLY `hmac-sha256-fortress-keyed-v1`: HMAC-SHA-256 over the
2194
+ * raw field value, using a per-fortress secret derived from the master key
2195
+ * via HKDF info string `sanctuary-v1.1-privacy-content-hmac`. Plain SHA-256
2196
+ * is NOT permitted because privacy field values (PII, project names, client
2197
+ * names, file paths) are low-entropy and dictionary-attackable. The keyed
2198
+ * hash makes content hashes meaningful for equality checks across events
2199
+ * within a fortress while remaining opaque to anyone without the per-
2200
+ * fortress key.
2201
+ *
2202
+ * Forward-compat: a future v1.x privacy hardening pass may add ML-DSA-keyed
2203
+ * variants. Verifiers MUST reject unknown algorithms.
2204
+ */
2205
+ declare const PRIVACY_HASH_ALGS: readonly ["hmac-sha256-fortress-keyed-v1"];
2206
+ type PrivacyHashAlg = (typeof PRIVACY_HASH_ALGS)[number];
2207
+ /**
2208
+ * Hub inbox item kinds. Hub API and dashboard consume the same enum so the
2209
+ * unified inbox renders without inventing new categories per surface.
2210
+ */
2211
+ declare const HUB_INBOX_KINDS: readonly ["approval_pending", "blocked_egress", "privacy_event", "budget_warning", "recovery_prompt", "agent_error"];
2212
+ type HubInboxKind = (typeof HUB_INBOX_KINDS)[number];
2213
+ /**
2214
+ * Agent lifecycle states surfaced to the operator hub.
2215
+ *
2216
+ * Underlying harness capability dictates which transitions are reachable; the
2217
+ * hub API surfaces unsupported transitions as "not_supported" rather than
2218
+ * faking them.
2219
+ */
2220
+ declare const HUB_AGENT_STATUSES: readonly ["active", "paused", "restarting", "locked_down", "unwrapping", "error", "unknown"];
2221
+ type HubAgentStatus = (typeof HUB_AGENT_STATUSES)[number];
2222
+ /**
2223
+ * Exit-bundle manifest version. v1 is the only valid value at v1.1 ship.
2224
+ * v1.x bumps require coordinator-approved manifest amendment.
2225
+ */
2226
+ declare const EXIT_BUNDLE_MANIFEST_VERSION: "SANCTUARY_EXIT_BUNDLE_V1";
2227
+ type ExitBundleManifestVersion = typeof EXIT_BUNDLE_MANIFEST_VERSION;
2228
+ /**
2229
+ * Artifact kinds enumerated by the exit-bundle manifest. The export command
2230
+ * MUST list every included artifact under one of these kinds. The verifier
2231
+ * CLI rejects unknown kinds.
2232
+ */
2233
+ declare const EXIT_BUNDLE_ARTIFACT_KINDS: readonly ["public_identity", "encrypted_state", "policy_set", "audit_receipts", "reputation_bundle", "commitments", "placeholder_vault_metadata"];
2234
+ type ExitBundleArtifactKind = (typeof EXIT_BUNDLE_ARTIFACT_KINDS)[number];
2235
+
2236
+ /**
2237
+ * Lightweight local privacy filter for outbound context.
2238
+ *
2239
+ * This is not a replacement for a full detector such as OpenAI privacy-filter.
2240
+ * It gives Sanctuary a deterministic baseline that catches common high-risk
2241
+ * spans inside otherwise-allowed fields, so policy gates are not limited to
2242
+ * top-level field names.
2243
+ */
2244
+
2245
+ type PrivacySpanClass = "email" | "phone" | "ssn" | "credit_card" | "secret_assignment" | "secret" | "credential" | "account_number" | "address" | "person" | "client" | "project" | "file_path" | "domain_term" | "url" | "date" | "custom";
2246
+ declare class PrivacyPlaceholderVault {
2247
+ private storage;
2248
+ private encryptionKey;
2249
+ private lookupKey;
2250
+ private cache;
2251
+ private pathCache;
2252
+ constructor(storage: StorageBackend, masterKey: Uint8Array);
2253
+ placeholderFor(spanClass: PrivacySpanClass, rawValue: string, scope?: string): Promise<string>;
2254
+ resolvePlaceholder(placeholder: string, scope?: string): Promise<string | null>;
2255
+ aliasForFieldPath(path: string, scope?: string): Promise<string>;
2256
+ resolveFieldPathAlias(alias: string, scope?: string): Promise<string | null>;
2257
+ private recordKey;
2258
+ private indexKey;
2259
+ private pathRecordKey;
2260
+ private pathIndexKey;
2261
+ private readIndex;
2262
+ private writeIndex;
2263
+ private readRecord;
2264
+ private writeRecord;
2265
+ private readPathIndex;
2266
+ private writePathIndex;
2267
+ private readPathRecord;
2268
+ private writePathRecord;
2269
+ private hmacString;
2270
+ }
2271
+
2125
2272
  /**
2126
2273
  * Sanctuary MCP Server — L2 Context Gating: Automatic Enforcer
2127
2274
  *
@@ -2170,9 +2317,10 @@ interface EnforcerStatus {
2170
2317
  declare class ContextGateEnforcer {
2171
2318
  private policyStore;
2172
2319
  private auditLog;
2320
+ private privacyVault?;
2173
2321
  private config;
2174
2322
  private stats;
2175
- constructor(policyStore: ContextGatePolicyStore, auditLog: AuditLog, config: EnforcerConfig);
2323
+ constructor(policyStore: ContextGatePolicyStore, auditLog: AuditLog, config: EnforcerConfig, privacyVault?: PrivacyPlaceholderVault);
2176
2324
  /**
2177
2325
  * Wrap a tool handler to apply automatic context gating.
2178
2326
  *
@@ -2274,6 +2422,9 @@ interface UpstreamServer {
2274
2422
  tool_overrides?: Record<string, {
2275
2423
  tier: 1 | 2 | 3;
2276
2424
  }>;
2425
+ destination_category?: "inference" | "tool-api" | "logging" | "analytics" | "peer-agent" | "custom";
2426
+ privacy_policy_id?: string;
2427
+ privacy_identity_id?: string;
2277
2428
  }
2278
2429
  interface SovereigntyProfile {
2279
2430
  version: 1;
@@ -2581,6 +2732,887 @@ declare class CallGovernor {
2581
2732
  private pruneDuplicateCache;
2582
2733
  }
2583
2734
 
2735
+ /**
2736
+ * Sanctuary v1.1 — Privacy Audit Payload Contracts
2737
+ *
2738
+ * Shared payload shapes emitted by the privacy core (Prompt 3) and consumed by
2739
+ * the remote-bound enforcement workstream (Prompt 4), the operator hub API
2740
+ * (Prompt 5), and the dashboard privacy panel (Prompt 8).
2741
+ *
2742
+ * Local-only invariant:
2743
+ * Payloads describe outbound traffic from a single fortress on a single
2744
+ * operator's machine. v1.1 ships local-only, single-operator scope.
2745
+ *
2746
+ * Enclosure-and-signing model:
2747
+ * Privacy events are NOT signed objects on their own. They are payloads
2748
+ * carried inside an enclosing audit-chain entry (`l2-operational/audit-log`
2749
+ * AuditEntry encrypted-at-rest under L1, plus `mesh/audit-batch`
2750
+ * SignedAuditBatch where federation propagation applies). The enclosing
2751
+ * audit entry carries the signature scheme; this file deliberately does not
2752
+ * declare a signature field on any payload type.
2753
+ *
2754
+ * When an enforcement path emits a privacy event:
2755
+ * 1. Build a `PrivacyAuditPayload` from this file.
2756
+ * 2. Append it as the `details` (or equivalent payload field) of an
2757
+ * `AuditEntry` in the L2 audit log.
2758
+ * 3. The mesh-layer batch envelope (`SIGNATURE_SCHEME_V1`) signs the
2759
+ * batch that carries this entry.
2760
+ *
2761
+ * Safe-metadata invariant:
2762
+ * Raw sensitive content MUST NEVER appear in any field of any payload defined
2763
+ * in this file. Only safe metadata is permitted: detector class, field path,
2764
+ * action, content hash, policy id, destination category, identity id,
2765
+ * placeholder labels.
2766
+ *
2767
+ * If a future workstream needs to surface a sensitive value to the operator,
2768
+ * it MUST go through the encrypted placeholder vault — not through these
2769
+ * payload shapes.
2770
+ */
2771
+
2772
+ /**
2773
+ * Common metadata header on every v1.1 privacy audit payload.
2774
+ *
2775
+ * `event_id` is the audit-chain id assigned by the enclosing AuditEntry.
2776
+ * `policy_id` is the bound privacy policy that produced the decision.
2777
+ * `identity_id` is the operator identity the policy is bound to.
2778
+ */
2779
+ interface PrivacyAuditPayloadHeader {
2780
+ /** v1.1 payload-shape version. */
2781
+ version: "1.1";
2782
+ /** Unique event id; SHOULD match the enclosing audit-chain id. */
2783
+ event_id: string;
2784
+ /** ISO8601 timestamp of the decision. */
2785
+ emitted_at: string;
2786
+ /** Policy id that produced this decision. */
2787
+ policy_id: string;
2788
+ /** Identity the policy is bound to. */
2789
+ identity_id: string;
2790
+ /** Wrapped agent the outbound traffic originates from. */
2791
+ agent_id: string;
2792
+ /** Destination category for the outbound payload. */
2793
+ destination_category: PrivacyDestinationCategory;
2794
+ }
2795
+ /**
2796
+ * Per-field decision recorded inside a privacy audit payload. Field-level
2797
+ * decisions bubble up into the rolled-up payload kinds below.
2798
+ *
2799
+ * Path-and-hash safety:
2800
+ * `field_path` MUST be sanitized — see `PRIVACY_FIELD_PATH_PATTERN` in
2801
+ * constants.ts. Raw object keys (which can themselves leak sensitive
2802
+ * information) MUST NOT appear on the wire. `content_hash` MUST use a keyed
2803
+ * hash algorithm (`hmac-sha256-fortress-keyed-v1` at v1.1) to defeat
2804
+ * dictionary attacks against low-entropy field values.
2805
+ */
2806
+ interface PrivacyFieldDecision {
2807
+ /**
2808
+ * Sanitized field path. Conforms to `PRIVACY_FIELD_PATH_PATTERN`:
2809
+ * positional references like "$0" / "$1.$2" or typed positional like
2810
+ * "obj:0" / "key:1.val:0". The fortress-local mapping back to the real
2811
+ * path is held in the placeholder vault. Real object keys MUST NOT
2812
+ * appear here.
2813
+ */
2814
+ field_path: string;
2815
+ /** Detector class that fired on this field. */
2816
+ detector_class: PrivacyDetectorClass;
2817
+ /** Action taken on this field. */
2818
+ action: PrivacyAction;
2819
+ /**
2820
+ * Keyed hash of the original field value. Hex-encoded.
2821
+ * Algorithm pinned via `hash_alg`; v1.1 only accepts
2822
+ * `hmac-sha256-fortress-keyed-v1`. The HMAC key is fortress-local
2823
+ * (HKDF-derived from the master key); equality of two `content_hash`
2824
+ * values within the same fortress means equal field values, but the
2825
+ * hash is opaque to any verifier outside the fortress and resists
2826
+ * dictionary attacks against low-entropy values. Plain SHA-256 is NOT
2827
+ * permitted. The original value MUST NOT appear anywhere in the payload.
2828
+ */
2829
+ content_hash: string;
2830
+ /** Hash algorithm identifier. v1.1 only accepts the keyed HMAC variant. */
2831
+ hash_alg: PrivacyHashAlg;
2832
+ /**
2833
+ * Stable placeholder label this field was substituted with on the outbound
2834
+ * payload, if applicable. Examples: "PERSON_1", "CLIENT_3", "SECRET_2".
2835
+ */
2836
+ placeholder_label?: string;
2837
+ }
2838
+ /**
2839
+ * Outbound payload was filtered. At least one field was redacted, hashed, or
2840
+ * substituted with a placeholder, and the call proceeded.
2841
+ */
2842
+ interface PrivacyFilteredPayload extends PrivacyAuditPayloadHeader {
2843
+ kind: "filtered";
2844
+ field_decisions: PrivacyFieldDecision[];
2845
+ /**
2846
+ * Keyed hash of the canonicalized outbound payload after filtering.
2847
+ * Same `hash_alg` constraint as `PrivacyFieldDecision.content_hash`.
2848
+ */
2849
+ outbound_payload_hash: string;
2850
+ /** Hash algorithm. v1.1 only accepts `hmac-sha256-fortress-keyed-v1`. */
2851
+ payload_hash_alg: PrivacyHashAlg;
2852
+ }
2853
+ /**
2854
+ * Outbound payload was allowed unchanged. No detector fired, or every fired
2855
+ * detector resolved to "allow" under the bound policy.
2856
+ */
2857
+ interface PrivacyAllowedPayload extends PrivacyAuditPayloadHeader {
2858
+ kind: "allowed";
2859
+ /**
2860
+ * Keyed hash of the canonicalized outbound payload.
2861
+ * Same `hash_alg` constraint as `PrivacyFieldDecision.content_hash`.
2862
+ */
2863
+ outbound_payload_hash: string;
2864
+ /** Hash algorithm. v1.1 only accepts `hmac-sha256-fortress-keyed-v1`. */
2865
+ payload_hash_alg: PrivacyHashAlg;
2866
+ }
2867
+ /**
2868
+ * Outbound payload was denied. Either a "deny" rule fired on a field, or the
2869
+ * fail-closed default applied because the policy or vault was unavailable.
2870
+ *
2871
+ * The `denial_reason_class` is a machine-friendly label, NOT a free-text
2872
+ * explanation. Operator-facing UIs may render a generic message keyed by this
2873
+ * label; raw policy-rule details MUST NOT be revealed.
2874
+ */
2875
+ interface PrivacyDeniedPayload extends PrivacyAuditPayloadHeader {
2876
+ kind: "denied";
2877
+ /**
2878
+ * Coarse denial reason class. Permitted values:
2879
+ * - "policy_deny_rule" — at least one field had a deny rule
2880
+ * - "fail_closed_no_policy" — fail-closed because policy missing
2881
+ * - "fail_closed_filter_error" — fail-closed because filter raised
2882
+ * - "fail_closed_vault_error" — fail-closed because vault unavailable
2883
+ * - "operator_override_denied" — explicit operator override path failed
2884
+ */
2885
+ denial_reason_class: "policy_deny_rule" | "fail_closed_no_policy" | "fail_closed_filter_error" | "fail_closed_vault_error" | "operator_override_denied";
2886
+ }
2887
+ /**
2888
+ * Privacy filter raised an unrecoverable error and outbound traffic failed
2889
+ * closed. Distinct from PrivacyDeniedPayload because the operator may want to
2890
+ * surface error events as alerts rather than as policy decisions.
2891
+ */
2892
+ interface PrivacyErrorPayload extends PrivacyAuditPayloadHeader {
2893
+ kind: "error";
2894
+ /** Stable error code. Implementation-specific catalog. No raw stack traces. */
2895
+ error_code: string;
2896
+ }
2897
+ /**
2898
+ * Inbound provider response was rehydrated using the placeholder vault. Only
2899
+ * fires when the bound policy permits rehydration for the destination category.
2900
+ */
2901
+ interface PrivacyRehydratedPayload extends PrivacyAuditPayloadHeader {
2902
+ kind: "rehydrated";
2903
+ /**
2904
+ * Number of placeholders successfully rehydrated. The placeholders themselves
2905
+ * are NOT logged.
2906
+ */
2907
+ rehydrated_count: number;
2908
+ /** Number of placeholders that could not be rehydrated (e.g., vault entry expired). */
2909
+ unresolvable_count: number;
2910
+ /**
2911
+ * Keyed hash of the canonicalized rehydrated response.
2912
+ * Same `hash_alg` constraint as `PrivacyFieldDecision.content_hash`.
2913
+ */
2914
+ response_hash: string;
2915
+ /** Hash algorithm. v1.1 only accepts `hmac-sha256-fortress-keyed-v1`. */
2916
+ response_hash_alg: PrivacyHashAlg;
2917
+ }
2918
+ /**
2919
+ * Discriminated union of every v1.1 privacy audit payload. The hub API and
2920
+ * dashboard privacy panel switch on `kind`. Integrity of these payloads
2921
+ * derives from the enclosing audit entry (encrypted-at-rest, plus signed at
2922
+ * the mesh-batch layer where applicable). No payload in this file carries a
2923
+ * self-signature.
2924
+ */
2925
+ type PrivacyAuditPayload = PrivacyFilteredPayload | PrivacyAllowedPayload | PrivacyDeniedPayload | PrivacyErrorPayload | PrivacyRehydratedPayload;
2926
+
2927
+ /**
2928
+ * Sanctuary v1.1 — Operator Hub Event Contracts
2929
+ *
2930
+ * Shared shapes for the unified inbox, the activity feed, and the per-agent
2931
+ * status panels. The operator hub API workstream (Prompt 5) emits these; the
2932
+ * dashboard UI workstream (Prompt 8) consumes them. v1.2 mobile companion
2933
+ * planning will evaluate these shapes when it scopes a phone surface, but
2934
+ * v1.1 does not commit to mobile compatibility — these contracts are
2935
+ * tuned for the local dashboard surface only.
2936
+ *
2937
+ * Local-only invariant:
2938
+ * Every event in this file describes activity inside a single fortress on a
2939
+ * single operator's machine. Cross-fortress, fleet, and public-federation
2940
+ * events are out of scope.
2941
+ *
2942
+ * Safe-metadata invariant:
2943
+ * No raw sensitive content in any field. Inbox cards may carry display titles
2944
+ * and subtitles, but those titles MUST be derived from policy templates and
2945
+ * MUST NOT contain raw query text, tool args, filenames, client names,
2946
+ * passphrases, or secrets.
2947
+ */
2948
+
2949
+ /**
2950
+ * Allowed value space for `display_template_args`. Only safe primitives plus
2951
+ * a small enumerated value set are permitted. Free-form strings ARE NOT
2952
+ * accepted; the dashboard rejects rendering on any value outside this union.
2953
+ *
2954
+ * The renderer treats every value as data to interpolate into a fixed
2955
+ * template registered under `template_id` — never as raw content. This
2956
+ * defends against secrets, query text, file paths, and client names leaking
2957
+ * into inbox cards via stringly-typed display fields.
2958
+ */
2959
+ type HubDisplayTemplateArg = {
2960
+ kind: "agent_id";
2961
+ value: string;
2962
+ } | {
2963
+ kind: "identity_id";
2964
+ value: string;
2965
+ } | {
2966
+ kind: "policy_id";
2967
+ value: string;
2968
+ } | {
2969
+ kind: "destination_category";
2970
+ value: PrivacyDestinationCategory;
2971
+ } | {
2972
+ kind: "tier";
2973
+ value: "tier1" | "tier2" | "tier3";
2974
+ } | {
2975
+ kind: "count";
2976
+ value: number;
2977
+ } | {
2978
+ kind: "iso8601";
2979
+ value: string;
2980
+ } | {
2981
+ kind: "duration_ms";
2982
+ value: number;
2983
+ };
2984
+ /**
2985
+ * Common header on every v1.1 hub inbox item.
2986
+ *
2987
+ * Display rendering uses a `template_id` plus typed args, NOT free-form
2988
+ * strings. The dashboard owns the template registry; backends only emit the
2989
+ * id and the args. This makes secret-leakage into inbox copy structurally
2990
+ * impossible: a backend cannot smuggle raw query content into a card by
2991
+ * accident, because there is no free-text channel.
2992
+ */
2993
+ interface HubInboxItemHeader {
2994
+ /** v1.1 event-shape version. */
2995
+ version: "1.1";
2996
+ /** Stable inbox-item id. SHOULD reference an audit-chain id where applicable. */
2997
+ item_id: string;
2998
+ /** Inbox kind discriminator. */
2999
+ kind: HubInboxKind;
3000
+ /** ISO8601 timestamp the item was created. */
3001
+ created_at: string;
3002
+ /** Wrapped agent id this item refers to, if applicable. */
3003
+ agent_id?: string;
3004
+ /** Operator identity id. */
3005
+ identity_id: string;
3006
+ /**
3007
+ * Display template id. Resolved by the dashboard against a registered
3008
+ * template catalog; the catalog owns the actual render strings. Backends
3009
+ * MUST NOT emit raw display copy. Template id space is namespaced per
3010
+ * inbox kind, e.g., "approval_pending.tier1.export".
3011
+ */
3012
+ display_template_id: string;
3013
+ /**
3014
+ * Typed args interpolated into the template. Every value MUST be a
3015
+ * `HubDisplayTemplateArg` instance — no free-form strings. Renderers
3016
+ * reject any arg outside this union, which structurally blocks secret
3017
+ * leakage via inbox copy.
3018
+ */
3019
+ display_template_args: HubDisplayTemplateArg[];
3020
+ /** Whether the operator has resolved this inbox item. */
3021
+ resolved: boolean;
3022
+ /** ISO8601 timestamp of resolution, if resolved. */
3023
+ resolved_at?: string;
3024
+ }
3025
+ /**
3026
+ * A pending Tier 1 or Tier 2 approval surfaced to the operator inbox.
3027
+ */
3028
+ interface HubApprovalPendingItem extends HubInboxItemHeader {
3029
+ kind: "approval_pending";
3030
+ /** Policy tier the underlying operation falls under. */
3031
+ tier: "tier1" | "tier2";
3032
+ /**
3033
+ * Coarse operation category. Stable enum so the UI can group consistently;
3034
+ * does not reveal underlying tool args. Names align with the canonical
3035
+ * Tier 1 / Tier 2 sets in `server/src/principal-policy/loader.ts`. Names
3036
+ * NOT in the canonical loader (e.g., `exit_bundle_export`, `policy_change`,
3037
+ * `lockdown`, `unwrap`) are v1.1-new and MUST be added to the loader's
3038
+ * Tier 1 set in the same PR that lands their behavior. Drift is a release
3039
+ * blocker.
3040
+ */
3041
+ operation_category: "state_export" | "state_import" | "state_delete" | "identity_rotate" | "reputation_export" | "reputation_import" | "sanctuary_export_identity_bundle" | "exit_bundle_export" | "exit_bundle_import" | "exit_bundle_rekey" | "policy_change" | "lockdown" | "unwrap" | "other";
3042
+ /** ISO8601 deadline after which the request will be auto-denied if applicable. */
3043
+ deadline?: string;
3044
+ /**
3045
+ * Result fields surfaced after the Tier 1 handler runs. Empty until
3046
+ * resolution. Populated for fortress-scope `exit_bundle_export` so the
3047
+ * dashboard exit-drill page can advance the wizard from the inbox
3048
+ * resolution alone, without round-tripping through the activity feed.
3049
+ * Per-agent items leave it undefined. Producers MUST keep field values
3050
+ * to safe metadata only (paths and hex hashes); raw secrets MUST NOT
3051
+ * appear here.
3052
+ */
3053
+ resolution_payload?: {
3054
+ bundle_dir?: string;
3055
+ manifest_hash?: string;
3056
+ artifact_count?: number;
3057
+ };
3058
+ }
3059
+ /**
3060
+ * Outbound traffic was blocked by the egress policy or the privacy filter.
3061
+ */
3062
+ interface HubBlockedEgressItem extends HubInboxItemHeader {
3063
+ kind: "blocked_egress";
3064
+ /**
3065
+ * Destination category. Same enum as the privacy filter so unified inbox,
3066
+ * privacy panel, and egress dashboards group consistently. Drift is a
3067
+ * release blocker.
3068
+ */
3069
+ destination_category: PrivacyDestinationCategory;
3070
+ /**
3071
+ * Why the egress was blocked. Coarse enum, not free text. Specific policy
3072
+ * rule names are NOT revealed.
3073
+ */
3074
+ block_reason_class: "egress_policy_deny" | "budget_exceeded" | "privacy_fail_closed" | "privacy_deny_rule" | "lockdown_active" | "other";
3075
+ }
3076
+ /**
3077
+ * A privacy event surfaced to the inbox. References the underlying privacy
3078
+ * event id; the inbox card is a thin pointer, not a duplicate of the event.
3079
+ */
3080
+ interface HubPrivacyEventItem extends HubInboxItemHeader {
3081
+ kind: "privacy_event";
3082
+ /** Foreign key into the privacy-event chain. */
3083
+ privacy_event_id: string;
3084
+ /** Privacy event kind. */
3085
+ privacy_event_kind: "filtered" | "allowed" | "denied" | "error" | "rehydrated";
3086
+ }
3087
+ /**
3088
+ * A budget threshold was crossed.
3089
+ */
3090
+ interface HubBudgetWarningItem extends HubInboxItemHeader {
3091
+ kind: "budget_warning";
3092
+ /** Budget bucket identifier (daily / monthly / per-agent / custom). */
3093
+ bucket: "daily" | "monthly" | "per_agent" | "custom";
3094
+ /** Soft-warn or hard-cap. Hard-cap usually requires operator unblock. */
3095
+ severity: "soft_warn" | "hard_cap";
3096
+ /** Percentage of budget used at event time. 0..1. */
3097
+ used_fraction: number;
3098
+ }
3099
+ /**
3100
+ * Recovery prompt — operator should run a recovery flow (passphrase reset,
3101
+ * keychain rebind, exit drill, etc.).
3102
+ */
3103
+ interface HubRecoveryPromptItem extends HubInboxItemHeader {
3104
+ kind: "recovery_prompt";
3105
+ /** Coarse recovery class. */
3106
+ recovery_class: "passphrase_reset" | "keychain_rebind" | "config_backup_restore" | "exit_drill" | "other";
3107
+ }
3108
+ /**
3109
+ * Agent reported an internal error.
3110
+ */
3111
+ interface HubAgentErrorItem extends HubInboxItemHeader {
3112
+ kind: "agent_error";
3113
+ /** Stable error code class. Implementation-specific catalog. */
3114
+ error_class: string;
3115
+ /** Whether the agent is still serving traffic after the error. */
3116
+ agent_still_active: boolean;
3117
+ }
3118
+ /**
3119
+ * Discriminated union of every v1.1 hub inbox item.
3120
+ */
3121
+ type HubInboxItem = HubApprovalPendingItem | HubBlockedEgressItem | HubPrivacyEventItem | HubBudgetWarningItem | HubRecoveryPromptItem | HubAgentErrorItem;
3122
+ /**
3123
+ * Activity feed entry. The feed is a flat stream backed by the audit chain;
3124
+ * inbox items often reference a feed entry, but feed entries are not always
3125
+ * inbox items.
3126
+ *
3127
+ * Activity feed entries are read-from-storage projections of audit-chain
3128
+ * entries. They are NOT signed envelopes themselves; integrity derives from
3129
+ * the underlying audit entry. Consumers that need to verify the feed entry
3130
+ * MUST resolve it to the source audit entry by `entry_id` and verify there.
3131
+ */
3132
+ interface HubActivityFeedEntry {
3133
+ version: "1.1";
3134
+ /** Audit-chain entry id. */
3135
+ entry_id: string;
3136
+ /** ISO8601 timestamp. */
3137
+ emitted_at: string;
3138
+ /** Wrapped agent id, if applicable. */
3139
+ agent_id?: string;
3140
+ /** Identity id. */
3141
+ identity_id: string;
3142
+ /** Stable category enum. */
3143
+ category: "policy_decision" | "approval" | "denial" | "egress" | "privacy" | "handoff" | "lifecycle" | "config" | "other";
3144
+ /**
3145
+ * Display template id. Resolved by the dashboard against the activity-feed
3146
+ * template catalog. Backends MUST NOT emit raw summary text — the template
3147
+ * id plus typed args is the only legitimate channel.
3148
+ */
3149
+ display_template_id: string;
3150
+ /** Typed args. Same constraints as `HubInboxItemHeader.display_template_args`. */
3151
+ display_template_args: HubDisplayTemplateArg[];
3152
+ }
3153
+ /**
3154
+ * Per-agent status snapshot returned by the hub API. Mirrors the agent
3155
+ * registry record but is computed-on-read; it does not flow through the
3156
+ * audit chain by itself.
3157
+ */
3158
+ interface HubAgentStatusSnapshot {
3159
+ version: "1.1";
3160
+ agent_id: string;
3161
+ status: HubAgentStatus;
3162
+ /**
3163
+ * Reason class for the current status. Same enum as
3164
+ * `LocalAgentRecord.status_reason_class`. Stable, not free text. Present
3165
+ * only when status is `locked_down`, `error`, or `restarting`.
3166
+ */
3167
+ status_reason_class?: "operator_lockdown" | "policy_breach" | "budget_hard_cap" | "harness_error" | "harness_unreachable" | "passphrase_required" | "config_drift" | "other";
3168
+ /** ISO8601 last-seen timestamp. */
3169
+ last_activity_at: string;
3170
+ /** Inbox-item ids currently unresolved against this agent. */
3171
+ open_inbox_item_ids: string[];
3172
+ }
3173
+
3174
+ /**
3175
+ * Sanctuary v1.1 — Local Agent Registry Records
3176
+ *
3177
+ * Shared shape for the per-agent record returned by the operator hub registry
3178
+ * API. Implementations of the hub API workstream (Prompt 5) write these;
3179
+ * dashboard UI (Prompt 8), internal coordination (Prompt 6), and the exit
3180
+ * bundle workstream (Prompt 7) read these.
3181
+ *
3182
+ * Local-only invariant:
3183
+ * Records describe agents wrapped by a single fortress on a single operator's
3184
+ * machine. Cross-fortress agent discovery is deferred to v1.3.
3185
+ *
3186
+ * No-secret invariant:
3187
+ * Records MUST NOT carry private keys, passphrases, recovery seeds, or raw
3188
+ * provider credentials. Provider identifiers and policy ids are safe;
3189
+ * provider tokens are not.
3190
+ */
3191
+
3192
+ /**
3193
+ * Coarse harness label. Mirrors the README-supported wrap targets at v1.1
3194
+ * ship. Adding a harness requires a new entry here plus harness-compatibility
3195
+ * coverage from Prompt 9.
3196
+ *
3197
+ * `mastra` has a first-class Tier B adapter at
3198
+ * `server/src/agent-contract/adapters/mastra.ts` and is therefore a
3199
+ * first-class kind here. `langgraph` does NOT have a dedicated adapter at
3200
+ * v1.1 ship; LangGraph wraps land via the generic `sanctuary wrap --wrap
3201
+ * <path>` path and surface as `generic_mcp`. When a dedicated LangGraph
3202
+ * adapter ships, add `langgraph` here in the same PR.
3203
+ */
3204
+ type LocalHarnessKind = "openclaw" | "hermes" | "claude_code" | "cursor" | "cline" | "mastra" | "generic_mcp" | "other";
3205
+ /**
3206
+ * Provider category for the underlying model. Distinct from the privacy
3207
+ * destination category enum because providers and destinations are not 1:1
3208
+ * (one provider can serve multiple destination categories).
3209
+ */
3210
+ interface LocalAgentModelProvider {
3211
+ /** Coarse provider label, e.g., "anthropic", "openai", "xai", "local", "other". */
3212
+ vendor: string;
3213
+ /** Concrete model identifier reported by the provider, e.g., "claude-opus-4". */
3214
+ model_id: string;
3215
+ /**
3216
+ * Whether the model runs locally (on-device or LAN) versus a remote provider.
3217
+ * Local models still flow through the privacy filter where applicable.
3218
+ */
3219
+ runs_locally: boolean;
3220
+ }
3221
+ /**
3222
+ * Budget summary surfaced to the hub. Concrete budget enforcement lives in
3223
+ * the policy engine; this is a read-only snapshot.
3224
+ */
3225
+ interface LocalAgentBudgetSummary {
3226
+ /** Daily bucket. */
3227
+ daily?: {
3228
+ /** Allotment unit, e.g., "tokens", "usd". */
3229
+ unit: string;
3230
+ /** Total allotment for the bucket. */
3231
+ cap: number;
3232
+ /** Amount used so far in the bucket window. */
3233
+ used: number;
3234
+ /** Soft-warn threshold fraction (0..1). */
3235
+ soft_warn?: number;
3236
+ };
3237
+ /** Monthly bucket. */
3238
+ monthly?: {
3239
+ unit: string;
3240
+ cap: number;
3241
+ used: number;
3242
+ soft_warn?: number;
3243
+ };
3244
+ /** ISO8601 timestamp of last budget refresh. */
3245
+ last_refreshed_at: string;
3246
+ }
3247
+ /**
3248
+ * The canonical local agent registry record for v1.1.
3249
+ *
3250
+ * Implementations SHOULD treat this as read-from-storage; mutating fields is
3251
+ * the responsibility of the hub API plus the underlying agent lifecycle owner
3252
+ * (wrap / unwrap / template assignment / lockdown).
3253
+ */
3254
+ interface LocalAgentRecord {
3255
+ version: "1.1";
3256
+ /** Stable agent identifier, scoped to this fortress. */
3257
+ agent_id: string;
3258
+ /** Operator identity that owns the agent. */
3259
+ identity_id: string;
3260
+ /** Harness the agent runs under. */
3261
+ harness: LocalHarnessKind;
3262
+ /** Free-text harness version string from the harness, when available. */
3263
+ harness_version?: string;
3264
+ /** Model and provider details. */
3265
+ model_provider: LocalAgentModelProvider;
3266
+ /** Bound policy id (compiled policy artifact). */
3267
+ policy_id: string;
3268
+ /**
3269
+ * Bound channel-template id, when one applies. Template id space is owned
3270
+ * by the policy engine.
3271
+ */
3272
+ channel_template_id?: string;
3273
+ /** Current lifecycle status as surfaced to the hub. */
3274
+ status: HubAgentStatus;
3275
+ /**
3276
+ * Reason class for the current status. Stable enum, not free text.
3277
+ * Present only when status is `locked_down`, `error`, or `restarting`.
3278
+ * The dashboard renders human-readable copy from a template catalog
3279
+ * keyed by this value; backends MUST NOT emit raw reason text.
3280
+ */
3281
+ status_reason_class?: "operator_lockdown" | "policy_breach" | "budget_hard_cap" | "harness_error" | "harness_unreachable" | "passphrase_required" | "config_drift" | "other";
3282
+ /** Budget summary snapshot. */
3283
+ budget_summary: LocalAgentBudgetSummary;
3284
+ /** ISO8601 timestamp of last agent activity. */
3285
+ last_activity_at: string;
3286
+ /** ISO8601 timestamp of wrap. */
3287
+ wrapped_at: string;
3288
+ /**
3289
+ * Capabilities flag set surfaced by the harness. Stable string set;
3290
+ * dashboard renders supported controls based on this set.
3291
+ */
3292
+ capabilities: {
3293
+ can_pause: boolean;
3294
+ can_resume: boolean;
3295
+ can_restart: boolean;
3296
+ can_unwrap: boolean;
3297
+ can_lockdown: boolean;
3298
+ can_chat: boolean;
3299
+ can_change_template: boolean;
3300
+ };
3301
+ }
3302
+ /**
3303
+ * Note on signing:
3304
+ * `LocalAgentRecord` is a read-from-storage projection of agent registry
3305
+ * state. It is NOT a signed envelope. Signed audit entries that reference
3306
+ * this record (wrap, unwrap, policy change, lockdown, etc.) carry the
3307
+ * signature scheme on the enclosing audit-chain entry, not on the record
3308
+ * itself. Consumers that need to verify a registry change MUST resolve
3309
+ * through the audit chain.
3310
+ */
3311
+ /**
3312
+ * Hub-side filter shape used by the registry list API. Workstreams may extend
3313
+ * with additional filters; the canonical fields below are stable.
3314
+ */
3315
+ interface LocalAgentRegistryFilter {
3316
+ /** Restrict to one identity id. */
3317
+ identity_id?: string;
3318
+ /** Restrict to one or more harness kinds. */
3319
+ harnesses?: LocalHarnessKind[];
3320
+ /** Restrict to one or more lifecycle statuses. */
3321
+ statuses?: HubAgentStatus[];
3322
+ /**
3323
+ * Maximum records to return. Implementations SHOULD enforce a server-side
3324
+ * upper bound regardless of caller value.
3325
+ */
3326
+ limit?: number;
3327
+ /** Pagination cursor; opaque to the caller. */
3328
+ cursor?: string;
3329
+ }
3330
+
3331
+ /**
3332
+ * Sanctuary v1.1 — Exit Bundle Manifest Contract
3333
+ *
3334
+ * `SANCTUARY_EXIT_BUNDLE_V1` is the canonical manifest produced by the v1.1
3335
+ * exit-bundle workstream (Prompt 7) and consumed by the standalone verifier
3336
+ * CLI plus the import command.
3337
+ *
3338
+ * Local-only invariant:
3339
+ * The bundle is produced by a single fortress on a single operator's machine
3340
+ * and consumed by another single-operator destination. v1.1 ships local-only,
3341
+ * single-operator scope. Cross-operator transfer is deferred to v1.3.
3342
+ *
3343
+ * Identity-binding invariant:
3344
+ * The manifest is signed by the operator's fortress-master identity. The
3345
+ * verifier MUST refuse to activate any artifact whose hash chain does not
3346
+ * match the manifest, and MUST refuse to activate any manifest whose
3347
+ * signature does not verify against the bound public identity.
3348
+ *
3349
+ * Public-only invariant:
3350
+ * The bundle MUST NOT contain private keys, raw passphrases, or recovery
3351
+ * seeds. Encrypted state may travel inside the bundle, but the master key
3352
+ * MUST NOT. Re-keying happens on import using new operator-supplied
3353
+ * material.
3354
+ *
3355
+ * Manifest-version invariant:
3356
+ * v1.1 ships only "SANCTUARY_EXIT_BUNDLE_V1". Future versions land through
3357
+ * coordinator-approved spec amendments. v1.1 verifiers MUST reject unknown
3358
+ * manifest versions rather than silently accept.
3359
+ */
3360
+
3361
+ /**
3362
+ * Hashing algorithm identifier embedded in every artifact entry. v1.1 uses
3363
+ * SHA-256. Forward-compat: a future bundle version may add SHA-3 or BLAKE3.
3364
+ */
3365
+ type ExitBundleHashAlg = "sha256";
3366
+ /**
3367
+ * One artifact entry inside the manifest. Each artifact lives at a relative
3368
+ * path inside the bundle archive. The verifier reads the file at that path
3369
+ * and confirms its hash. See `EXIT_BUNDLE_PATH_PATTERN` and the safe-path
3370
+ * rules block above for the full set of constraints.
3371
+ */
3372
+ interface ExitBundleArtifactEntry {
3373
+ /** Coarse artifact kind. */
3374
+ kind: ExitBundleArtifactKind;
3375
+ /**
3376
+ * Relative path inside the bundle, POSIX-style. MUST satisfy
3377
+ * `EXIT_BUNDLE_PATH_PATTERN` and the safe-path rules block above.
3378
+ * The verifier rejects any entry whose path is absolute, contains "..",
3379
+ * contains backslashes, or duplicates another entry's path. The verifier
3380
+ * does NOT follow symlinks or hardlinks during extraction.
3381
+ */
3382
+ path: string;
3383
+ /** Hash algorithm. v1.1 ships only "sha256". */
3384
+ hash_alg: ExitBundleHashAlg;
3385
+ /** Hex-encoded artifact hash. */
3386
+ hash: string;
3387
+ /** Size in bytes. Verifier MAY independently re-size and reject on mismatch. */
3388
+ size_bytes: number;
3389
+ /**
3390
+ * Optional discriminator inside the kind, e.g., "audit-receipt-2026-Q2".
3391
+ * Verifier does not interpret this; the import command may use it to order
3392
+ * artifacts. Same path-pattern constraints DO NOT apply to subkind, but
3393
+ * subkind MUST NOT contain control characters.
3394
+ */
3395
+ subkind?: string;
3396
+ }
3397
+ /**
3398
+ * Identity binding embedded in the manifest. Carries only public material.
3399
+ */
3400
+ interface ExitBundleIdentityBinding {
3401
+ /** Operator identity id. */
3402
+ identity_id: string;
3403
+ /** Fortress id. */
3404
+ fortress_id: string;
3405
+ /** Base64url-encoded Ed25519 public key of the fortress-master. */
3406
+ fortress_master_pubkey: string;
3407
+ /** Optional DID, when one is bound to this identity. */
3408
+ did?: string;
3409
+ }
3410
+ /**
3411
+ * v1 manifest body — what the operator's fortress-master signs.
3412
+ *
3413
+ * `signature_scheme` lives INSIDE the body deliberately so the scheme is
3414
+ * covered by the fortress-master signature. Without this, an attacker who
3415
+ * substituted both `signature` and `signature_scheme` on the wrapper could
3416
+ * present a valid-looking manifest with a different scheme. Pinning the
3417
+ * scheme inside the signed bytes makes the crypto-agility hinge non-
3418
+ * substitutable.
3419
+ */
3420
+ interface ExitBundleManifestBody {
3421
+ /** Manifest version constant. */
3422
+ manifest_version: ExitBundleManifestVersion;
3423
+ /** ISO8601 timestamp at export time. */
3424
+ exported_at: string;
3425
+ /** Source fortress / operator identity binding. */
3426
+ identity_binding: ExitBundleIdentityBinding;
3427
+ /**
3428
+ * Sanctuary version string at export time. Verifier surfaces version skew
3429
+ * to the operator if the destination fortress is older.
3430
+ */
3431
+ source_sanctuary_version: string;
3432
+ /** Every artifact carried in the bundle. */
3433
+ artifacts: ExitBundleArtifactEntry[];
3434
+ /**
3435
+ * Aggregate hash over canonicalize(artifacts[]). Lets the verifier perform
3436
+ * a fast sanity check before walking each entry.
3437
+ */
3438
+ artifacts_aggregate_hash: string;
3439
+ /** Aggregate hash algorithm. v1.1 ships only "sha256". */
3440
+ artifacts_aggregate_hash_alg: ExitBundleHashAlg;
3441
+ /**
3442
+ * Tier 1 audit-event id that captured the export approval. Consumers MAY
3443
+ * cross-reference with the source fortress audit chain.
3444
+ */
3445
+ export_approval_audit_id: string;
3446
+ /**
3447
+ * Signature scheme covering the wrapper's `signature` field. v1.1 ships
3448
+ * only "ed25519-v1". Embedded inside the signed body so the scheme cannot
3449
+ * be substituted independently of the signature.
3450
+ */
3451
+ signature_scheme: SignatureScheme;
3452
+ }
3453
+ /**
3454
+ * Full signed manifest. Top-level shape that ships in the bundle archive.
3455
+ *
3456
+ * The wrapper carries only the `signature` and a pointer to the body. The
3457
+ * scheme that produced the signature lives inside `body.signature_scheme`,
3458
+ * so the signature itself attests to which scheme produced it.
3459
+ */
3460
+ interface ExitBundleManifest {
3461
+ /** Body — what the signature covers. */
3462
+ body: ExitBundleManifestBody;
3463
+ /**
3464
+ * Base64url-encoded Ed25519 signature over canonicalize(body) by the
3465
+ * fortress-master private key. The bundle archive is otherwise unsigned;
3466
+ * artifact integrity comes from artifact hashes inside the body. The
3467
+ * signing scheme is `body.signature_scheme`.
3468
+ */
3469
+ signature: string;
3470
+ }
3471
+ /**
3472
+ * Verifier output shape. The standalone verifier CLI emits this structure on
3473
+ * stdout (or as JSON for tool consumers) so import commands can branch
3474
+ * cleanly. v1.1 ships one VerifierResult shape; future versions extend.
3475
+ */
3476
+ interface ExitBundleVerifierResult {
3477
+ version: "1.1";
3478
+ /** True iff manifest signature, every artifact hash, and the aggregate hash all verify. */
3479
+ passed: boolean;
3480
+ /** ISO8601 timestamp of verification. */
3481
+ verified_at: string;
3482
+ /** Reference to the verified manifest body. */
3483
+ manifest_summary: {
3484
+ manifest_version: ExitBundleManifestVersion;
3485
+ fortress_id: string;
3486
+ identity_id: string;
3487
+ exported_at: string;
3488
+ artifact_count: number;
3489
+ };
3490
+ /**
3491
+ * Per-artifact verification result. Order matches `manifest.body.artifacts`.
3492
+ */
3493
+ artifact_results: Array<{
3494
+ path: string;
3495
+ kind: ExitBundleArtifactKind;
3496
+ hash_passed: boolean;
3497
+ size_passed: boolean;
3498
+ }>;
3499
+ /**
3500
+ * Coarse failure-class enum, when `passed` is false. Distinct from a free-text
3501
+ * error message so import commands can branch.
3502
+ */
3503
+ failure_class?: "manifest_signature_invalid" | "manifest_unknown_version" | "manifest_signature_scheme_invalid" | "artifact_hash_mismatch" | "artifact_missing" | "artifact_size_mismatch" | "aggregate_hash_mismatch" | "artifact_path_unsafe" | "artifact_path_duplicate" | "artifact_path_escapes_root" | "archive_contains_symlink" | "private_material_present" | "other";
3504
+ }
3505
+
3506
+ /**
3507
+ * Sanctuary v1.1 — local query privacy core.
3508
+ *
3509
+ * This module is intentionally independent of dashboard UI and transport
3510
+ * enforcement. Enforcement paths call it with a payload, destination category,
3511
+ * identity-bound policy, and encrypted placeholder vault; the module returns a
3512
+ * filtered payload or a fail-closed denial plus a shared privacy audit payload.
3513
+ */
3514
+
3515
+ type PrivacyPolicyAction = "placeholder" | Extract<PrivacyAction, "allow" | "redact" | "hash" | "deny">;
3516
+ interface PrivacyPolicy {
3517
+ version: 1;
3518
+ policy_id: string;
3519
+ policy_name?: string;
3520
+ identity_id: string;
3521
+ detector_actions?: Partial<Record<PrivacyDetectorClass, PrivacyPolicyAction>>;
3522
+ destination_detector_actions?: Partial<Record<PrivacyDestinationCategory, Partial<Record<PrivacyDetectorClass, PrivacyPolicyAction>>>>;
3523
+ client_names?: string[];
3524
+ project_names?: string[];
3525
+ domain_terms?: string[];
3526
+ person_names?: string[];
3527
+ placeholder_scope?: "identity" | "policy";
3528
+ rehydration?: {
3529
+ default_action?: "allow" | "deny";
3530
+ allowed_destinations?: PrivacyDestinationCategory[];
3531
+ denied_destinations?: PrivacyDestinationCategory[];
3532
+ };
3533
+ max_depth?: number;
3534
+ max_payload_bytes?: number;
3535
+ max_string_bytes?: number;
3536
+ operator_override?: {
3537
+ allow_on_policy_error?: boolean;
3538
+ allow_on_filter_error?: boolean;
3539
+ allow_on_vault_error?: boolean;
3540
+ allow_on_detector_error?: boolean;
3541
+ };
3542
+ created_at: string;
3543
+ updated_at: string;
3544
+ }
3545
+ interface PrivacyCoreFinding {
3546
+ raw_path: string;
3547
+ detector_class: PrivacyDetectorClass;
3548
+ span_class: PrivacySpanClass;
3549
+ action: PrivacyAction;
3550
+ content_hash: string;
3551
+ placeholder_label?: string;
3552
+ }
3553
+ interface PrivacyFilterRequest {
3554
+ payload: unknown;
3555
+ policy: PrivacyPolicy | null;
3556
+ identity_id?: string;
3557
+ agent_id: string;
3558
+ destination_category: PrivacyDestinationCategory;
3559
+ audit_log?: AuditLog;
3560
+ event_id?: string;
3561
+ }
3562
+ type PrivacyFilterDecision = {
3563
+ status: "allowed";
3564
+ payload: unknown;
3565
+ findings: [];
3566
+ audit_payload: PrivacyAuditPayload;
3567
+ } | {
3568
+ status: "filtered";
3569
+ payload: unknown;
3570
+ findings: PrivacyCoreFinding[];
3571
+ audit_payload: PrivacyAuditPayload;
3572
+ } | {
3573
+ status: "denied";
3574
+ payload: undefined;
3575
+ findings: PrivacyCoreFinding[];
3576
+ audit_payload: PrivacyDeniedPayload;
3577
+ };
3578
+ interface PrivacyRehydrationRequest {
3579
+ response: unknown;
3580
+ policy: PrivacyPolicy | null;
3581
+ identity_id?: string;
3582
+ agent_id: string;
3583
+ destination_category: PrivacyDestinationCategory;
3584
+ audit_log?: AuditLog;
3585
+ event_id?: string;
3586
+ }
3587
+ type PrivacyRehydrationDecision = {
3588
+ status: "rehydrated";
3589
+ response: unknown;
3590
+ rehydrated_count: number;
3591
+ unresolvable_count: number;
3592
+ audit_payload: PrivacyAuditPayload;
3593
+ } | {
3594
+ status: "denied";
3595
+ response: unknown;
3596
+ rehydrated_count: 0;
3597
+ unresolvable_count: 0;
3598
+ audit_payload: PrivacyDeniedPayload;
3599
+ };
3600
+ declare class LocalPrivacyEngine {
3601
+ private vault;
3602
+ private hmacKey;
3603
+ constructor(vault: PrivacyPlaceholderVault, masterKey: Uint8Array);
3604
+ filterOutbound(request: PrivacyFilterRequest): Promise<PrivacyFilterDecision>;
3605
+ rehydrateResponse(request: PrivacyRehydrationRequest): Promise<PrivacyRehydrationDecision>;
3606
+ private filterNode;
3607
+ private filterString;
3608
+ private fieldDecisionsForFindings;
3609
+ private rehydrateNode;
3610
+ private rehydrateString;
3611
+ private assertPayloadWithinBounds;
3612
+ private deniedPayload;
3613
+ private keyedHash;
3614
+ }
3615
+
2584
3616
  /**
2585
3617
  * Sanctuary MCP Server — Proxy Router
2586
3618
  *
@@ -2603,6 +3635,27 @@ interface ProxyRouterOptions {
2603
3635
  contextGateFilter?: (toolName: string, args: Record<string, unknown>) => Promise<Record<string, unknown>>;
2604
3636
  /** Optional call governor for runtime governance */
2605
3637
  governor?: CallGovernor;
3638
+ /**
3639
+ * Optional v1.1 remote-bound privacy enforcement.
3640
+ *
3641
+ * When set, every proxied tool call is routed through
3642
+ * `engine.filterOutbound` before the upstream forward. The bound
3643
+ * `PrivacyPolicy` is resolved per-server via `policyResolver`, and the
3644
+ * destination category comes from the server's `destination_category`
3645
+ * field (defaulting to `tool-api` when absent).
3646
+ *
3647
+ * Fail-closed semantics:
3648
+ * - `policyResolver` returns null when no policy is bound for the server;
3649
+ * the privacy engine treats that as `fail_closed_no_policy` and denies.
3650
+ * - `policyResolver` rejecting (vault unreachable, decrypt failure, etc.)
3651
+ * is treated as `fail_closed_filter_error` and denies.
3652
+ * - Operator overrides on the policy (`operator_override.allow_on_*`)
3653
+ * are honored by the engine itself.
3654
+ */
3655
+ privacyEnforcement?: {
3656
+ engine: LocalPrivacyEngine;
3657
+ policyResolver: (server: string, identityId: string | undefined) => Promise<PrivacyPolicy | null>;
3658
+ };
2606
3659
  /** Optional callback after each proxy call decision (for dashboard feed) */
2607
3660
  onProxyCall?: (data: {
2608
3661
  tool: string;
@@ -2720,6 +3773,246 @@ declare class FilesystemStorage implements StorageBackend {
2720
3773
  totalSize(): Promise<number>;
2721
3774
  }
2722
3775
 
3776
+ /**
3777
+ * Sanctuary MCP Server — Key Derivation
3778
+ *
3779
+ * Two-tier key derivation:
3780
+ * 1. Master key from passphrase via Argon2id (memory-hard, GPU-resistant)
3781
+ * 2. Namespace keys from master key via HKDF-SHA256
3782
+ *
3783
+ * This ensures:
3784
+ * - Passphrase brute-force is expensive (Argon2id)
3785
+ * - Compromise of one namespace key doesn't expose others (HKDF domain separation)
3786
+ */
3787
+ /** Stored key derivation parameters (for re-deriving the master key) */
3788
+ interface KeyDerivationParams {
3789
+ /** Algorithm */
3790
+ alg: "argon2id";
3791
+ /** Salt (base64url) */
3792
+ salt: string;
3793
+ /** Memory cost in KiB */
3794
+ m: number;
3795
+ /** Time cost (iterations) */
3796
+ t: number;
3797
+ /** Parallelism */
3798
+ p: number;
3799
+ /** Output length in bytes */
3800
+ l: number;
3801
+ }
3802
+
3803
+ /**
3804
+ * Sanctuary v1.1 exit-bundle export/import implementation.
3805
+ *
3806
+ * The public bundle is a directory containing `manifest.json` and hashed JSON
3807
+ * artifacts. Private keys and passphrases are never emitted. Encrypted user
3808
+ * state can be re-keyed on import when the operator supplies source key
3809
+ * material and the destination has a signing identity.
3810
+ */
3811
+
3812
+ interface ExitEncryptedStateBundle {
3813
+ format: "SANCTUARY_EXIT_ENCRYPTED_STATE_V1";
3814
+ exported_at: string;
3815
+ key_source: "passphrase" | "recovery-key" | "unknown";
3816
+ source_key_derivation?: KeyDerivationParams;
3817
+ namespaces: string[];
3818
+ total_keys: number;
3819
+ contains_reserved_namespaces: false;
3820
+ entries: Array<{
3821
+ namespace: string;
3822
+ key: string;
3823
+ entry: StateEntry;
3824
+ }>;
3825
+ }
3826
+ interface ExitPublicIdentityArtifact {
3827
+ bundle: {
3828
+ format: "SANCTUARY_IDENTITY_BUNDLE_V1";
3829
+ publicKey: string;
3830
+ did: string;
3831
+ identity_id: string;
3832
+ label: string;
3833
+ key_type: "ed25519";
3834
+ key_protection: string;
3835
+ rotation_history: StoredIdentity["rotation_history"];
3836
+ exported_at: string;
3837
+ };
3838
+ signature: string;
3839
+ signed_by: string;
3840
+ }
3841
+ interface ExitPolicySetArtifact {
3842
+ format: "SANCTUARY_EXIT_POLICY_SET_V1";
3843
+ exported_at: string;
3844
+ principal_policy: PrincipalPolicy;
3845
+ config_summary: {
3846
+ version: string;
3847
+ state: SanctuaryConfig["state"];
3848
+ execution: SanctuaryConfig["execution"];
3849
+ disclosure: SanctuaryConfig["disclosure"];
3850
+ reputation: SanctuaryConfig["reputation"];
3851
+ privacy_filter: SanctuaryConfig["privacy_filter"];
3852
+ };
3853
+ }
3854
+ interface ExitAuditReceiptsArtifact {
3855
+ format: "SANCTUARY_AUDIT_RECEIPTS_V1";
3856
+ exported_at: string;
3857
+ total: number;
3858
+ individual_entry_signatures: false;
3859
+ entries: AuditEntry[];
3860
+ }
3861
+ interface ExitCommitmentsArtifact {
3862
+ format: "SANCTUARY_EXIT_COMMITMENTS_V1";
3863
+ exported_at: string;
3864
+ public_commitments: Array<{
3865
+ commitment_id: string;
3866
+ commitment: string;
3867
+ committed_at: string;
3868
+ revealed: boolean;
3869
+ revealed_at?: string;
3870
+ }>;
3871
+ unreadable_count: number;
3872
+ redacted_fields: ["value", "blinding_factor"];
3873
+ }
3874
+ interface ExitPlaceholderVaultMetadataArtifact {
3875
+ format: "SANCTUARY_PLACEHOLDER_VAULT_METADATA_V1";
3876
+ exported_at: string;
3877
+ entries: Array<Record<string, unknown>>;
3878
+ unreadable_count: number;
3879
+ redacted_fields: ["raw_value", "raw_path"];
3880
+ }
3881
+ interface ExportExitBundleOptions {
3882
+ bundleDir: string;
3883
+ storage: StorageBackend;
3884
+ masterKey: Uint8Array;
3885
+ identityManager: IdentityManager;
3886
+ auditLog: AuditLog;
3887
+ policy: PrincipalPolicy;
3888
+ config?: SanctuaryConfig;
3889
+ reputationStore?: ReputationStore;
3890
+ stateStoragePath?: string;
3891
+ stateNamespaces?: string[];
3892
+ keySource?: "passphrase" | "recovery-key" | "unknown";
3893
+ /**
3894
+ * When the export is gated by a Tier 1 approval flow (e.g. the hub's
3895
+ * fortress-scope export endpoint), the caller supplies the approval's
3896
+ * audit id here. The value is embedded in the manifest's
3897
+ * `export_approval_audit_id` field and as the `approval_id` of the L1
3898
+ * "exit_bundle_export" audit entry, tying the manifest to the operator's
3899
+ * actual approval rather than an internally-generated id (v1.0.2 (j)).
3900
+ * When omitted, the export self-generates an id.
3901
+ */
3902
+ exportApprovalAuditId?: string;
3903
+ }
3904
+ interface ExportExitBundleResult {
3905
+ bundle_dir: string;
3906
+ manifest: ExitBundleManifest;
3907
+ manifest_hash: string;
3908
+ artifact_count: number;
3909
+ unsupported_artifacts: string[];
3910
+ }
3911
+ interface ImportExitBundleOptions {
3912
+ bundleDir: string;
3913
+ storage: StorageBackend;
3914
+ masterKey: Uint8Array;
3915
+ identityManager: IdentityManager;
3916
+ auditLog: AuditLog;
3917
+ reputationStore?: ReputationStore;
3918
+ activate?: boolean;
3919
+ conflictResolution?: "skip" | "overwrite" | "version";
3920
+ sourcePassphrase?: string;
3921
+ sourceRecoveryKey?: string;
3922
+ sourceMasterKey?: Uint8Array;
3923
+ destinationSignerIdentityId?: string;
3924
+ }
3925
+ interface ExitBundleConflictReport {
3926
+ public_identity_exists: boolean;
3927
+ state_conflicts: Array<{
3928
+ namespace: string;
3929
+ key: string;
3930
+ }>;
3931
+ reputation_conflicts: string[];
3932
+ policy_set_exists: boolean;
3933
+ audit_receipts_exist: boolean;
3934
+ }
3935
+ interface ImportExitBundleResult {
3936
+ verified: boolean;
3937
+ activated: boolean;
3938
+ conflicts: ExitBundleConflictReport;
3939
+ state: {
3940
+ status: "not_requested" | "rekeyed" | "staged_requires_source_key" | "skipped_no_destination_signer";
3941
+ imported_keys: number;
3942
+ skipped_keys: number;
3943
+ skipped_invalid_sig: number;
3944
+ skipped_unknown_kid: number;
3945
+ conflicts: number;
3946
+ };
3947
+ reputation: {
3948
+ imported_attestations: number;
3949
+ invalid_attestations: number;
3950
+ unverifiable_attestations: number;
3951
+ };
3952
+ staged_artifacts: string[];
3953
+ warnings: string[];
3954
+ unsupported_artifacts: string[];
3955
+ }
3956
+ declare function exportExitBundle(opts: ExportExitBundleOptions): Promise<ExportExitBundleResult>;
3957
+ declare function importExitBundle(opts: ImportExitBundleOptions): Promise<ImportExitBundleResult>;
3958
+ declare function exitBundleManifestShape(): Record<string, unknown>;
3959
+
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
+ interface ExitBundleDetailedVerifierResult extends ExitBundleVerifierResult {
3969
+ manifest_path: string;
3970
+ manifest_hash: string | null;
3971
+ warnings: string[];
3972
+ unsupported_artifacts: string[];
3973
+ identity?: {
3974
+ signature_valid: boolean;
3975
+ identity_id?: string;
3976
+ did?: string;
3977
+ };
3978
+ audit?: {
3979
+ receipt_count: number;
3980
+ individual_signatures_verified: boolean;
3981
+ };
3982
+ reputation?: {
3983
+ bundle_signature_valid: boolean | "unverifiable";
3984
+ attestation_count: number;
3985
+ verified_attestations: number;
3986
+ invalid_attestations: number;
3987
+ unverifiable_attestations: number;
3988
+ };
3989
+ }
3990
+ interface LoadedExitArtifact<T = unknown> {
3991
+ entry: ExitBundleArtifactEntry;
3992
+ path: string;
3993
+ json: T;
3994
+ bytes: Uint8Array;
3995
+ }
3996
+ declare function readManifest(bundleDir: string): Promise<ExitBundleManifest>;
3997
+ declare function loadExitArtifact<T = unknown>(bundleDir: string, manifest: ExitBundleManifest, kind: ExitBundleArtifactKind): Promise<LoadedExitArtifact<T> | null>;
3998
+ declare function verifyExitBundle(bundleDir: string): Promise<ExitBundleDetailedVerifierResult>;
3999
+
4000
+ /**
4001
+ * `sanctuary exit` CLI.
4002
+ *
4003
+ * Operator-facing export/import/verifier path for SANCTUARY_EXIT_BUNDLE_V1.
4004
+ * Dashboard wizard work consumes the same module APIs later.
4005
+ */
4006
+
4007
+ interface ExitCommandArgs {
4008
+ argv: string[];
4009
+ out?: Writable;
4010
+ err?: Writable;
4011
+ stdin?: NodeJS.ReadableStream;
4012
+ env?: NodeJS.ProcessEnv;
4013
+ }
4014
+ declare function runExitCommand(args: ExitCommandArgs): Promise<number>;
4015
+
2723
4016
  /**
2724
4017
  * Sanctuary MCP Server — Principal Policy Loader
2725
4018
  *
@@ -2806,6 +4099,412 @@ interface SHRGeneratorOptions {
2806
4099
  */
2807
4100
  declare function generateSHR(identityId: string | undefined, opts: SHRGeneratorOptions): SignedSHR | string;
2808
4101
 
4102
+ /**
4103
+ * Sanctuary v1.1 Operator Hub API Constants
4104
+ *
4105
+ * Routes, prefixes, and small enums shared across the hub modules.
4106
+ * The hub API surface is consumed by the v1.1 dashboard UI (Prompt 8).
4107
+ *
4108
+ * Local-only invariant:
4109
+ * Every route here describes activity inside a single fortress on a single
4110
+ * operator's machine. Cross-fortress, fleet, and public-federation routes
4111
+ * are out of scope.
4112
+ */
4113
+
4114
+ /**
4115
+ * Inbox-resolve actions. The router rejects any other action token.
4116
+ */
4117
+ declare const HUB_INBOX_ACTIONS: readonly ["approve", "deny", "dismiss"];
4118
+ type HubInboxAction = (typeof HUB_INBOX_ACTIONS)[number];
4119
+ /**
4120
+ * Agent-control actions surfaced by `POST /api/hub/agents/:id/:action`.
4121
+ *
4122
+ * `pause` / `resume` / `restart` are control-plane operations that the
4123
+ * underlying harness may execute immediately; they are not Tier 1.
4124
+ *
4125
+ * `unwrap` and `lockdown` are Tier 1 operations per the Principal Policy
4126
+ * loader. Hub endpoints for these MUST enqueue an `approval_pending` inbox
4127
+ * item rather than execute directly. Tier 1 approval gates remain in force
4128
+ * from the dashboard. The dashboard never auto-approves Tier 1 work.
4129
+ */
4130
+ declare const HUB_AGENT_CONTROL_ACTIONS: readonly ["pause", "resume", "restart", "unwrap", "lockdown"];
4131
+ type HubAgentControlAction = (typeof HUB_AGENT_CONTROL_ACTIONS)[number];
4132
+
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"];
4135
+ type ChannelTemplateId = (typeof CHANNEL_TEMPLATE_IDS)[number];
4136
+
4137
+ /**
4138
+ * Sanctuary v1.1. Operator Hub API Types
4139
+ *
4140
+ * Service-deps shapes + small payload types not already defined in the
4141
+ * v1.1 contracts. The contract surface (HubInboxItem, LocalAgentRecord,
4142
+ * HubActivityFeedEntry, HubAgentStatusSnapshot) is the source of truth for
4143
+ * everything that crosses the API boundary; types in this file are
4144
+ * implementation glue.
4145
+ */
4146
+
4147
+ /**
4148
+ * Result returned from a synchronous (non-Tier-1) agent control action.
4149
+ */
4150
+ interface HubAgentControlResult {
4151
+ agent_id: string;
4152
+ prior_status: HubAgentStatus;
4153
+ new_status: HubAgentStatus;
4154
+ applied_at: string;
4155
+ }
4156
+ /**
4157
+ * Result returned from a Tier-1 agent control action that has been
4158
+ * deferred to operator approval. The hub returns the inbox item id so the
4159
+ * caller can poll or subscribe; nothing executes until the operator
4160
+ * resolves the inbox item.
4161
+ */
4162
+ interface HubTier1ApprovalEnqueuedResult {
4163
+ agent_id: string;
4164
+ inbox_item_id: string;
4165
+ status: "approval_pending";
4166
+ /**
4167
+ * Same enum as
4168
+ * `HubApprovalPendingItem.operation_category`. Surfaced separately so
4169
+ * non-inbox callers (CLI scripts, test harnesses) can act on the
4170
+ * category without a second fetch.
4171
+ */
4172
+ operation_category: HubApprovalPendingItem["operation_category"];
4173
+ }
4174
+ /**
4175
+ * Result returned from a fortress-scope Tier-1 action that has been
4176
+ * deferred to operator approval. Distinguished from the per-agent shape by
4177
+ * the absence of `agent_id` and the presence of `fortress_scope: true`.
4178
+ * The Tier 1 handler iterates fortress-wide on approval; nothing executes
4179
+ * until the operator resolves the inbox item.
4180
+ */
4181
+ interface HubTier1FortressApprovalEnqueuedResult {
4182
+ inbox_item_id: string;
4183
+ status: "approval_pending";
4184
+ operation_category: HubApprovalPendingItem["operation_category"];
4185
+ /** Always true. Distinguishes from the per-agent shape. */
4186
+ fortress_scope: true;
4187
+ }
4188
+ /**
4189
+ * Result returned from `fortressExportBundle` after a fortress-scope
4190
+ * exit-bundle export approval lands. Surfaced both in the inbox item's
4191
+ * `resolution_payload` and (separately) in an activity feed entry.
4192
+ */
4193
+ interface HubFortressExportResult {
4194
+ bundle_dir: string;
4195
+ manifest_hash: string;
4196
+ artifact_count: number;
4197
+ }
4198
+ /**
4199
+ * Generic capability check delegated to the underlying harness controller.
4200
+ * The hub never performs the harness-side action itself; it asks the
4201
+ * controller to apply a transition and reports the new status back.
4202
+ *
4203
+ * Each callback returns the agent's new status after the action lands.
4204
+ * The controller MUST throw if it cannot apply the action; the hub maps
4205
+ * those throws to HubCapabilityError or HubConflictError.
4206
+ */
4207
+ interface HubAgentController {
4208
+ pause(agentId: string): Promise<HubAgentStatus>;
4209
+ resume(agentId: string): Promise<HubAgentStatus>;
4210
+ restart(agentId: string): Promise<HubAgentStatus>;
4211
+ /**
4212
+ * Apply an operator-approved unwrap. Called only after a Tier 1
4213
+ * approval lands through the inbox flow. Implementations SHOULD treat
4214
+ * unwrap as durable (cocoon teardown + registry deregister).
4215
+ */
4216
+ unwrap(agentId: string): Promise<HubAgentStatus>;
4217
+ /**
4218
+ * Apply an operator-approved lockdown. Called only after a Tier 1
4219
+ * approval lands. Implementations SHOULD treat lockdown as a hard-stop
4220
+ * (deny all egress, freeze gates) until explicitly cleared.
4221
+ */
4222
+ lockdown(agentId: string): Promise<HubAgentStatus>;
4223
+ /**
4224
+ * Apply an operator-approved policy bind. Called only after a Tier 1
4225
+ * approval lands.
4226
+ */
4227
+ 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
+ */
4233
+ bindChannelTemplate(agentId: string, templateId: ChannelTemplateId): Promise<void>;
4234
+ }
4235
+ /**
4236
+ * Source-side input shapes the inbox aggregator pulls from. Each callback
4237
+ * returns recent occurrences pre-shaped as the underlying contract type;
4238
+ * the aggregator wraps them into HubInboxItem header form.
4239
+ *
4240
+ * Source callbacks are deliberately narrow; the hub does not know how to
4241
+ * compute privacy events or budget thresholds. Other workstreams own those
4242
+ * computations and feed them in through these callbacks.
4243
+ */
4244
+ interface HubInboxSources {
4245
+ /**
4246
+ * Currently unresolved Tier 1 / Tier 2 approval requests. Items already
4247
+ * in the hub-side inbox store are deduplicated by `item_id` against these.
4248
+ */
4249
+ listPendingApprovals: () => HubApprovalPendingItem[];
4250
+ /**
4251
+ * Recent egress denials. The hub surfaces each as a blocked-egress inbox
4252
+ * card. The caller is responsible for trimming to the recent window.
4253
+ */
4254
+ listRecentBlockedEgress: () => HubBlockedEgressItem[];
4255
+ /**
4256
+ * Recent privacy events the operator should attend to. The hub does NOT
4257
+ * surface every privacy event; it surfaces the ones that the privacy
4258
+ * core has already promoted to operator attention (denied, error, plus
4259
+ * filtered events flagged by the bound policy).
4260
+ */
4261
+ listRecentPrivacyEvents: () => HubPrivacyEventItem[];
4262
+ /**
4263
+ * Active budget warnings (soft-warn or hard-cap). Closed warnings are
4264
+ * not returned.
4265
+ */
4266
+ listActiveBudgetWarnings: () => HubBudgetWarningItem[];
4267
+ /**
4268
+ * Active recovery prompts (passphrase reset due, exit drill recommended,
4269
+ * keychain rebind required, etc.).
4270
+ */
4271
+ listActiveRecoveryPrompts: () => HubRecoveryPromptItem[];
4272
+ /**
4273
+ * Recent agent-error events.
4274
+ */
4275
+ listRecentAgentErrors: () => HubAgentErrorItem[];
4276
+ }
4277
+ interface HubActivitySources {
4278
+ /**
4279
+ * Underlying audit log the activity feed projects from.
4280
+ */
4281
+ auditLog: AuditLog;
4282
+ /**
4283
+ * Identity id the dashboard is currently scoped to. Activity entries are
4284
+ * filtered to this identity; v1.1 is single-operator; the parameter is
4285
+ * here so v1.2 can scope per-identity without breaking the API.
4286
+ */
4287
+ identityId: string;
4288
+ }
4289
+ /**
4290
+ * Lightweight summary of a bound policy. Suitable for UI cards. Raw policy
4291
+ * bytes never appear here.
4292
+ */
4293
+ interface HubPolicySummary {
4294
+ policy_id: string;
4295
+ display_label: string;
4296
+ /** ISO8601 timestamp the policy was bound. */
4297
+ bound_at: string;
4298
+ /** Number of agents currently bound to this policy. */
4299
+ agent_count: number;
4300
+ /** Channel template id the policy was compiled from, if applicable. */
4301
+ channel_template_id?: ChannelTemplateId;
4302
+ }
4303
+ /**
4304
+ * Lightweight summary of a budget bucket. Suitable for UI cards.
4305
+ */
4306
+ interface HubBudgetSummary {
4307
+ bucket_id: string;
4308
+ /** Display label drawn from the policy compile step. */
4309
+ display_label: string;
4310
+ unit: string;
4311
+ cap: number;
4312
+ used: number;
4313
+ /** Soft-warn threshold fraction (0..1). */
4314
+ soft_warn?: number;
4315
+ /** ISO8601 timestamp of last refresh. */
4316
+ last_refreshed_at: string;
4317
+ }
4318
+ interface HubPolicyAndBudgetSources {
4319
+ listPolicySummaries: () => HubPolicySummary[];
4320
+ listBudgetSummaries: () => HubBudgetSummary[];
4321
+ }
4322
+ /**
4323
+ * Read interface for the local agent registry. The hub does not invent
4324
+ * agent records; it pulls them from this source. Backend implementations
4325
+ * may persist records to disk or recompute them per call.
4326
+ */
4327
+ interface HubAgentRegistrySource {
4328
+ list(filter?: LocalAgentRegistryFilter): LocalAgentRecord[];
4329
+ get(agentId: string): LocalAgentRecord | null;
4330
+ /**
4331
+ * Update a single field on a record. The hub uses this to flip status
4332
+ * after a synchronous control action lands. Returns the updated record;
4333
+ * throws if the agent does not exist.
4334
+ */
4335
+ updateStatus(agentId: string, status: HubAgentStatus, statusReasonClass?: LocalAgentRecord["status_reason_class"]): LocalAgentRecord;
4336
+ /**
4337
+ * Bind a different policy on a record. Used after an approved Tier 1
4338
+ * policy_change action lands.
4339
+ */
4340
+ updatePolicyBinding(agentId: string, policyId: string): LocalAgentRecord;
4341
+ /**
4342
+ * Bind a different channel template on a record. Non-Tier-1.
4343
+ */
4344
+ updateChannelTemplateBinding(agentId: string, templateId: ChannelTemplateId): LocalAgentRecord;
4345
+ }
4346
+ interface HubServiceDeps {
4347
+ /** Operator identity id this hub is scoped to. */
4348
+ identityId: string;
4349
+ /** Stable fortress id. */
4350
+ fortressId: string;
4351
+ /** Local agent registry source. */
4352
+ agentRegistry: HubAgentRegistrySource;
4353
+ /** Inbox aggregation sources. */
4354
+ inboxSources: HubInboxSources;
4355
+ /** Activity feed sources. */
4356
+ activitySources: HubActivitySources;
4357
+ /** Policy + budget summary sources. */
4358
+ policyBudgetSources: HubPolicyAndBudgetSources;
4359
+ /** Underlying agent controller for control endpoints. */
4360
+ agentController: HubAgentController;
4361
+ /**
4362
+ * Optional fortress-scope exit-bundle export callback. Invoked only
4363
+ * after a fortress-scope Tier 1 approval lands. The callback owns its
4364
+ * own dependency wiring (storage, masterKey, identityManager, auditLog,
4365
+ * policy, reputationStore, state namespaces); the hub layer remains
4366
+ * crypto-agnostic. When omitted, the fortress export endpoint returns
4367
+ * `HubCapabilityError`.
4368
+ *
4369
+ * The hub passes the inbox item's id as `approvalAuditId` so the
4370
+ * callback can thread it into `exportExitBundle({ exportApprovalAuditId })`,
4371
+ * tying the manifest's `export_approval_audit_id` to the operator's
4372
+ * actual approval flow rather than an internally-generated value
4373
+ * (closes v1.0.2 backlog item (j)). The argument is optional for
4374
+ * backwards compatibility with existing callbacks.
4375
+ */
4376
+ fortressExportBundle?: (approvalAuditId?: string) => Promise<HubFortressExportResult>;
4377
+ /**
4378
+ * Clock override for deterministic timestamp emission in tests.
4379
+ * Defaults to `() => new Date()` when omitted.
4380
+ */
4381
+ now?: () => Date;
4382
+ }
4383
+
4384
+ /**
4385
+ * Sanctuary v1.1. Operator Hub Service
4386
+ *
4387
+ * Public API the router calls. Owns no domain logic: it composes the
4388
+ * agent registry source, the inbox aggregator + store, the activity feed
4389
+ * projection, and the agent controller.
4390
+ *
4391
+ * Tier 1 enforcement contract:
4392
+ * Hub control endpoints for `unwrap`, `lockdown`, and `policy_change` MUST
4393
+ * NOT execute synchronously. The hub enqueues an `approval_pending` inbox
4394
+ * item carrying the operation_category and binds the controller call to
4395
+ * the inbox-store resolution handler. Only operator approval through the
4396
+ * inbox path causes the controller call to fire. The dashboard never
4397
+ * auto-approves Tier 1 work.
4398
+ */
4399
+
4400
+ declare class HubService {
4401
+ private deps;
4402
+ private inboxStore;
4403
+ constructor(deps: HubServiceDeps);
4404
+ private now;
4405
+ private nowIso;
4406
+ listInbox(): HubInboxItem[];
4407
+ resolveInboxItem(itemId: string, action: HubInboxAction): Promise<HubInboxItem>;
4408
+ listAgents(filter?: LocalAgentRegistryFilter): LocalAgentRecord[];
4409
+ getAgent(agentId: string): LocalAgentRecord;
4410
+ getAgentStatusSnapshot(agentId: string): HubAgentStatusSnapshot;
4411
+ controlAgent(agentId: string, action: HubAgentControlAction): Promise<HubAgentControlResult | HubTier1ApprovalEnqueuedResult>;
4412
+ /**
4413
+ * Capability gate. Returns silently when the action is supported by the
4414
+ * harness; throws HubCapabilityError when not.
4415
+ */
4416
+ private assertCapability;
4417
+ /**
4418
+ * Build a Tier 1 inbox item for an unwrap/lockdown/policy_change action
4419
+ * and bind the controller call to its resolution.
4420
+ */
4421
+ private enqueueTier1ControlAction;
4422
+ /**
4423
+ * Bind a different policy on an agent. Tier 1: enqueues an approval
4424
+ * pending inbox item rather than executing synchronously.
4425
+ */
4426
+ bindAgentPolicy(agentId: string, policyId: string): HubTier1ApprovalEnqueuedResult;
4427
+ /**
4428
+ * Bind a different channel template. Not Tier 1; channel templates are
4429
+ * compositions over the four canonical slots and bind synchronously.
4430
+ */
4431
+ bindAgentChannelTemplate(agentId: string, rawTemplateId: unknown): Promise<LocalAgentRecord>;
4432
+ /**
4433
+ * Enqueue a fortress-scope Tier 1 lockdown. One inbox item is created;
4434
+ * on operator approval the handler iterates `agentController.lockdown`
4435
+ * over every wrapped agent for the bound identity, captures partial
4436
+ * failures as per-agent `agent_error` inbox items, and emits a single
4437
+ * `lifecycle` activity entry. Stacked fortress lockdowns are rejected
4438
+ * with `HubConflictError` until the prior pending item resolves.
4439
+ */
4440
+ enqueueFortressLockdown(): HubTier1FortressApprovalEnqueuedResult;
4441
+ /**
4442
+ * Enqueue a fortress-scope Tier 1 exit-bundle export. One inbox item is
4443
+ * created; on operator approval the handler invokes
4444
+ * `fortressExportBundle()` once at fortress scope, attaches
4445
+ * `bundle_dir` + `manifest_hash` + `artifact_count` to the inbox item's
4446
+ * `resolution_payload`, and emits a `lifecycle` activity entry.
4447
+ * Stacked exports are rejected with `HubConflictError`.
4448
+ */
4449
+ enqueueFortressExportBundle(): HubTier1FortressApprovalEnqueuedResult;
4450
+ /**
4451
+ * Reject stacked fortress-scope Tier 1 items. The check scans the inbox
4452
+ * store for an unresolved `approval_pending` item with the same
4453
+ * `operation_category` and no `agent_id` (the fortress-scope marker).
4454
+ */
4455
+ private assertNoPendingFortressTier1;
4456
+ listActivity(filter: {
4457
+ since?: string;
4458
+ limit?: number;
4459
+ agent_id?: string;
4460
+ category?: HubActivityFeedEntry["category"];
4461
+ }): Promise<HubActivityFeedEntry[]>;
4462
+ listPolicySummaries(): HubPolicySummary[];
4463
+ listBudgetSummaries(): HubBudgetSummary[];
4464
+ /**
4465
+ * Validate that a caller-supplied filter does not request cross-fortress
4466
+ * scope; v1.1 hub is single-fortress; the filter rejects fortress-bridging
4467
+ * fields outright; v1.3 federation will introduce an alternate surface.
4468
+ */
4469
+ static assertLocalOnlyFilter(filter: Record<string, unknown> | null): void;
4470
+ /**
4471
+ * Test/inspection helper. Not part of the public service surface.
4472
+ */
4473
+ inboxStoreSize(): number;
4474
+ }
4475
+
4476
+ /**
4477
+ * v1.1 Server Wiring (v1.1.1 hotfix)
4478
+ *
4479
+ * v1.1.0 shipped the v1.1 module suite (dashboard / hub API / exit bundle
4480
+ * endpoints / coordination) but no entry-point server imported any of it.
4481
+ * This module builds the canonical HubService construction the dashboard
4482
+ * entry points share so the routes light up at boot without forcing each
4483
+ * caller to know the deps shape.
4484
+ *
4485
+ * The wiring is deliberately minimal at v1.1.1:
4486
+ *
4487
+ * - Local agent registry starts empty. v1.2 will populate it from
4488
+ * `discoverTenants()` and the wrapped harness manifest. v1.1.1 ships
4489
+ * the API surface so existing operator scripts can hit it; the data
4490
+ * plane is the next conversation.
4491
+ * - Inbox sources return empty arrays. The privacy chokepoint already
4492
+ * emits audit events through PR #69 / PR #71; the inbox aggregator is
4493
+ * the v1.2 work to project those into operator cards.
4494
+ * - Activity feed reads from the real audit log. This is the one source
4495
+ * that's already complete in v1.1.0 and just needs to be plugged in.
4496
+ * - Agent controller errors on every action. v1.1.1 cannot honestly
4497
+ * pause / unwrap / lockdown anything because no agent registry yet
4498
+ * exists; the wiring returns `HubCapabilityError` rather than lying
4499
+ * about what shipped.
4500
+ */
4501
+
4502
+ interface V11Bindings {
4503
+ hubService: HubService;
4504
+ identityId: string;
4505
+ fortressId: string;
4506
+ }
4507
+
2809
4508
  /**
2810
4509
  * Sanctuary MCP Server — Principal Dashboard
2811
4510
  *
@@ -2889,6 +4588,12 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2889
4588
  * policy change.
2890
4589
  */
2891
4590
  private _autoAuthLocalhost;
4591
+ /**
4592
+ * v1.1 routes (dashboard HTML at /v1.1, hub API at /api/hub/*) are
4593
+ * mounted additively when set. Legacy routes at / continue to serve
4594
+ * regardless. Default route flip is deferred to v1.2.
4595
+ */
4596
+ private v11Bindings;
2892
4597
  constructor(config: DashboardConfig);
2893
4598
  /**
2894
4599
  * Inject dependencies after construction.
@@ -2910,6 +4615,26 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2910
4615
  * Exposed via /api/status so the frontend can show an appropriate banner.
2911
4616
  */
2912
4617
  setStandaloneMode(standalone: boolean): void;
4618
+ /**
4619
+ * v1.1.1 hotfix: bind the v1.1 dashboard + hub API to this dashboard
4620
+ * instance. After binding, requests to `/v1.1` serve the v1.1 HTML and
4621
+ * requests under `/api/hub/*` route through the hub API. Legacy routes
4622
+ * at `/` and `/api/*` keep their pre-v1.1 behavior (additive mount).
4623
+ *
4624
+ * Pass `null` to detach the bindings (used by tests and during shutdown).
4625
+ */
4626
+ setV11Bindings(bindings: V11Bindings | null): void;
4627
+ /**
4628
+ * v1.1 dispatch entry point. Called from `handleRequest` before the
4629
+ * legacy route table. Returns true when the request was served by v1.1
4630
+ * routes; false to fall through to legacy routing.
4631
+ *
4632
+ * Auth gating: the v1.1 dashboard HTML is served unconditionally (the
4633
+ * client script handles its own auth dance). Hub API routes run through
4634
+ * the same auth contract as legacy `/api/*` routes via the AuthConfig
4635
+ * passed to `handleHubRoute`.
4636
+ */
4637
+ private dispatchV11;
2913
4638
  /**
2914
4639
  * v0.10.2: enable (or disable) the loopback auto-auth fast path. See
2915
4640
  * {@link _autoAuthLocalhost} for the rationale and threat model. Callers
@@ -2985,6 +4710,7 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
2985
4710
  */
2986
4711
  private pruneRateLimits;
2987
4712
  private handleRequest;
4713
+ private handleLegacyRequest;
2988
4714
  /**
2989
4715
  * SEC-012: Exchange a long-lived auth token (in Authorization header)
2990
4716
  * for a short-lived session ID. The session ID can be used in URL
@@ -3631,6 +5357,12 @@ interface UpstreamServerStatus {
3631
5357
  tool_count: number;
3632
5358
  error?: string;
3633
5359
  }
5360
+ interface PrivacySummary {
5361
+ filtered_events: number;
5362
+ filtered_spans: number;
5363
+ classes: Record<string, number>;
5364
+ last_filtered_at: string | null;
5365
+ }
3634
5366
  interface ProtectionSnapshot {
3635
5367
  overall: {
3636
5368
  status: OverallStatus;
@@ -3647,6 +5379,7 @@ interface ProtectionSnapshot {
3647
5379
  activity: ActivityEntry[];
3648
5380
  pending_approvals: PendingApproval[];
3649
5381
  audit: AuditEntry[];
5382
+ privacy: PrivacySummary;
3650
5383
  upstream_servers: UpstreamServerStatus[];
3651
5384
  mode: "co-located" | "standalone";
3652
5385
  server_version: string;
@@ -3700,8 +5433,19 @@ interface ApprovalHandlers {
3700
5433
  allow: (id: string) => Promise<boolean>;
3701
5434
  deny: (id: string) => Promise<boolean>;
3702
5435
  }
5436
+ /**
5437
+ * SSE event taxonomy.
5438
+ *
5439
+ * v1.0 event names: `snapshot` (initial hydration), `activity` (audit feed
5440
+ * append), `approval` (pending approval surfaced).
5441
+ *
5442
+ * v1.1 added: `inbox` (HubInboxItem replace-or-prepend by item_id) and
5443
+ * `agent_status` (HubAgentStatusSnapshot replace by agent_id). The hub
5444
+ * service emits these via the existing `publish` interface; the producer
5445
+ * wires every name verbatim through `event: <type>` SSE frames.
5446
+ */
3703
5447
  interface StreamEvent {
3704
- type: "snapshot" | "activity" | "approval";
5448
+ type: "snapshot" | "activity" | "approval" | "inbox" | "agent_status";
3705
5449
  data: unknown;
3706
5450
  }
3707
5451
 
@@ -3739,6 +5483,18 @@ interface DashboardHandle {
3739
5483
  publishActivity: (entry: ActivityEntry) => void;
3740
5484
  /** Push a new pending approval (already added by the approval channel). */
3741
5485
  publishApproval: (approval: PendingApproval) => void;
5486
+ /**
5487
+ * Push a v1.1 hub inbox item update. Producers (HubService) call this
5488
+ * on inbox writes (Tier 1 enqueue, Tier 1 resolve, source-pulled item
5489
+ * surfaced). Consumers replace-or-prepend by `item_id`.
5490
+ */
5491
+ publishInbox: (item: unknown) => void;
5492
+ /**
5493
+ * Push a v1.1 per-agent status update. Producers call this when the
5494
+ * agent registry's `updateStatus` transitions. Consumers replace by
5495
+ * `agent_id`.
5496
+ */
5497
+ publishAgentStatus: (snapshot: unknown) => void;
3742
5498
  }
3743
5499
  declare function startDashboardServer(options: DashboardServerOptions): Promise<DashboardHandle>;
3744
5500
 
@@ -3835,4 +5591,4 @@ declare function createSanctuaryServer(options?: {
3835
5591
  storage?: StorageBackend;
3836
5592
  }): Promise<SanctuaryServer>;
3837
5593
 
3838
- 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 FederationCapabilities, type FederationPeer, FederationRegistry, type FieldClassification, type FieldFilterResult, FilesystemStorage, type GateResult, HERO_COPY, type HandshakeChallenge, type HandshakeCompletion, type HandshakeResponse, type HandshakeResult, InMemoryModelProvenanceStore, InjectionDetector, type InjectionDetectorConfig, type InjectionSignal, type L1Status, type L2Status, type L3Status, type L4Status, 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, filterContext, generateAttestation, generateSHR, generateSystemPrompt, getProtectionSnapshot, getTemplate, initiateHandshake, listTemplateIds, loadConfig, loadPrincipalPolicy, recommendPolicy, renderDashboardHTML, resolveTier, respondToHandshake, signPayload, startDashboard, startDashboardServer, tierDistribution, verifyAttestation, verifyBridgeCommitment, verifyCompletion, verifyPedersenCommitment, verifyProofOfKnowledge, verifyRangeProof, verifySHR, verifySignature };
5594
+ 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 };