@sanctuary-framework/mcp-server 1.1.0 → 1.1.2

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.ts CHANGED
@@ -2204,6 +2204,21 @@ type PrivacyAction = (typeof PRIVACY_ACTIONS)[number];
2204
2204
  */
2205
2205
  declare const PRIVACY_HASH_ALGS: readonly ["hmac-sha256-fortress-keyed-v1"];
2206
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];
2207
2222
  /**
2208
2223
  * Exit-bundle manifest version. v1 is the only valid value at v1.1 ship.
2209
2224
  * v1.x bumps require coordinator-approved manifest amendment.
@@ -2909,6 +2924,410 @@ interface PrivacyRehydratedPayload extends PrivacyAuditPayloadHeader {
2909
2924
  */
2910
2925
  type PrivacyAuditPayload = PrivacyFilteredPayload | PrivacyAllowedPayload | PrivacyDeniedPayload | PrivacyErrorPayload | PrivacyRehydratedPayload;
2911
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
+
2912
3331
  /**
2913
3332
  * Sanctuary v1.1 — Exit Bundle Manifest Contract
2914
3333
  *
@@ -3680,6 +4099,412 @@ interface SHRGeneratorOptions {
3680
4099
  */
3681
4100
  declare function generateSHR(identityId: string | undefined, opts: SHRGeneratorOptions): SignedSHR | string;
3682
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
+
3683
4508
  /**
3684
4509
  * Sanctuary MCP Server — Principal Dashboard
3685
4510
  *
@@ -3763,6 +4588,12 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
3763
4588
  * policy change.
3764
4589
  */
3765
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;
3766
4597
  constructor(config: DashboardConfig);
3767
4598
  /**
3768
4599
  * Inject dependencies after construction.
@@ -3784,6 +4615,26 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
3784
4615
  * Exposed via /api/status so the frontend can show an appropriate banner.
3785
4616
  */
3786
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;
3787
4638
  /**
3788
4639
  * v0.10.2: enable (or disable) the loopback auto-auth fast path. See
3789
4640
  * {@link _autoAuthLocalhost} for the rationale and threat model. Callers
@@ -3859,6 +4710,7 @@ declare class DashboardApprovalChannel implements ApprovalChannel {
3859
4710
  */
3860
4711
  private pruneRateLimits;
3861
4712
  private handleRequest;
4713
+ private handleLegacyRequest;
3862
4714
  /**
3863
4715
  * SEC-012: Exchange a long-lived auth token (in Authorization header)
3864
4716
  * for a short-lived session ID. The session ID can be used in URL
@@ -4643,6 +5495,23 @@ interface DashboardHandle {
4643
5495
  * `agent_id`.
4644
5496
  */
4645
5497
  publishAgentStatus: (snapshot: unknown) => void;
5498
+ /**
5499
+ * v1.1.2 hotfix (Finding V): bind v1.1 hub bindings to this dashboard
5500
+ * instance so /v1.1, /api/hub/*, and /api/identities serve the v1.1
5501
+ * surface. Pass null to detach. PR #82 wired these routes only on the
5502
+ * principal-policy DashboardApprovalChannel; the wrap-auto operator
5503
+ * dashboard (this server) needed the same wiring for the wrap-emitted
5504
+ * URL to expose the v1.1 surfaces the v1.1.1 release notes claim.
5505
+ */
5506
+ setV11Bindings: (bindings: V11Bindings | null) => void;
5507
+ /**
5508
+ * v1.1.2: enable loopback auto-auth for /api/hub/* + /api/identities.
5509
+ * When true, requests from 127.0.0.1 / ::1 bypass the bearer-token
5510
+ * check (mirrors the principal-policy dashboard's _autoAuthLocalhost
5511
+ * flag). Set true when the dashboard binds to a loopback host and the
5512
+ * caller has independently authenticated the operator.
5513
+ */
5514
+ setV11LoopbackAutoAuth: (enabled: boolean) => void;
4646
5515
  }
4647
5516
  declare function startDashboardServer(options: DashboardServerOptions): Promise<DashboardHandle>;
4648
5517