@securecoms/agent-bridge 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -3
- package/dist/attachments.d.ts +13 -0
- package/dist/bin.js +1974 -389
- package/dist/bridge-agent.d.ts +238 -9
- package/dist/consumer-grants.d.ts +28 -0
- package/dist/errors.d.ts +1 -1
- package/dist/grants/binding.d.ts +2 -1
- package/dist/grants/effective.d.ts +8 -0
- package/dist/grants/manifest.d.ts +14 -0
- package/dist/grants/mcp-catalog.d.ts +23 -0
- package/dist/grants/mcp-issuance.d.ts +34 -0
- package/dist/grants/mcp-policy.d.ts +75 -0
- package/dist/grants/mcp-section.d.ts +94 -0
- package/dist/grants/owner-turn.d.ts +40 -0
- package/dist/grants/tool-status.d.ts +79 -0
- package/dist/http-server.d.ts +1 -0
- package/dist/index.js +1910 -327
- package/dist/mcp-catalog-message.d.ts +79 -0
- package/dist/mcp-catalog-rate.d.ts +35 -0
- package/dist/outbox.d.ts +42 -0
- package/dist/reply-intent.d.ts +1 -1
- package/dist/service/runtime-config-dir.d.ts +55 -0
- package/dist/tool-status-sender.d.ts +71 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +1 -1
- package/package.json +2 -1
package/dist/bridge-agent.d.ts
CHANGED
|
@@ -44,10 +44,12 @@ import { VOICE_CALL_START_MARKER } from "./voice/prompt-compiler.js";
|
|
|
44
44
|
import { type CallBufferTurn } from "./voice/call-buffer.js";
|
|
45
45
|
import { type VaultListNode, type VaultReadExpectations } from "./vault.js";
|
|
46
46
|
import type { AgentActionNodeType, AgentActionNodeStatus, AgentActionNodeDetail } from "@agentvault/connect-core";
|
|
47
|
-
import { removeManagedLlmSource, writeManagedFiles, writeManifestPolicy } from "./service/runtime-config-dir.js";
|
|
47
|
+
import { removeManagedLlmSource, writeManagedFiles, writeManifestPolicy, writeMcpPolicy } from "./service/runtime-config-dir.js";
|
|
48
48
|
import { type ReplyCredit } from "./reply-credit.js";
|
|
49
49
|
import { bindRuntimeConfig, type FeatureSet } from "./grants/binding.js";
|
|
50
50
|
import type { RuntimeReplyReceipt } from "./reply-receipts.js";
|
|
51
|
+
import { type McpCatalog } from "./grants/mcp-catalog.js";
|
|
52
|
+
import { type McpCatalogLister } from "./mcp-catalog-message.js";
|
|
51
53
|
import type { SearchProvider } from "./grants/search-providers.js";
|
|
52
54
|
export type AgentAudience = "owner_dm" | "team_room" | "partner_room" | "other_human_dm" | "agent_to_agent" | "unknown";
|
|
53
55
|
export type BridgeToolGrant =
|
|
@@ -169,6 +171,13 @@ export interface BridgeAuthorization {
|
|
|
169
171
|
* for a harness socket and the HTTP/SSE paths (review m-3).
|
|
170
172
|
*/
|
|
171
173
|
readonly memory_write?: true;
|
|
174
|
+
/**
|
|
175
|
+
* MCP client Task 16 (OQ-2 ruling, audit M-4): true ONLY on an owner-DM turn whose verified owner check read the DM's
|
|
176
|
+
* leaves as exactly the pinned owner and the agent (relayLeaf === false), and only while at least one `mcp:` grant is
|
|
177
|
+
* in tool_grants (renarrowManifestGrants and withMcpCapability drop it with the last one). The runtime offers an
|
|
178
|
+
* `mcp:` tool only with it. NOT a tool grant.
|
|
179
|
+
*/
|
|
180
|
+
readonly relay_free?: true;
|
|
172
181
|
}
|
|
173
182
|
/**
|
|
174
183
|
* Spec §8.2: what ws-server tells applyMemoryNote about the socket a
|
|
@@ -357,9 +366,10 @@ export declare const ROOM_VOICE_GUIDANCE_WITH_RESPONDER = "You are in a group vo
|
|
|
357
366
|
* `voiceDefaultResponder` (never a body-supplied claim).
|
|
358
367
|
*/
|
|
359
368
|
export declare function selectVoiceGuidance(isDm: boolean | undefined, voiceDefaultResponder: boolean | undefined): string;
|
|
360
|
-
/** Owner grants plan 4 Task 11: TEST SEAM for the refetch's writers and binder (call order, throws). Production omits it. */
|
|
361
369
|
export interface GrantWriters {
|
|
362
370
|
writeManifestPolicy?: typeof writeManifestPolicy;
|
|
371
|
+
/** MCP Task 16. */
|
|
372
|
+
writeMcpPolicy?: typeof writeMcpPolicy;
|
|
363
373
|
writeManagedFiles?: typeof writeManagedFiles;
|
|
364
374
|
bindRuntimeConfig?: typeof bindRuntimeConfig;
|
|
365
375
|
removeManagedLlmSource?: typeof removeManagedLlmSource;
|
|
@@ -372,6 +382,15 @@ export interface BridgeAgentDeps {
|
|
|
372
382
|
* UNIFIED_MEMORY_PART1 decides (switch.arch.test.ts pins both).
|
|
373
383
|
*/
|
|
374
384
|
unifiedMemoryPart1?: boolean;
|
|
385
|
+
/**
|
|
386
|
+
* MCP client Task 16: TEST SEAM ONLY. The catalog the manifest verifier and mcp-policy.json use. Production (bin.ts)
|
|
387
|
+
* never passes it, so the compiled MCP_CATALOG (Phase 1: {linear}, Task 22) decides what MCP is grantable.
|
|
388
|
+
*/
|
|
389
|
+
mcpCatalog?: McpCatalog;
|
|
390
|
+
/** PR-B2 audit M-2: TEST SEAM ONLY. The H-D delivered-turn record's bound (default MCP_DELIVERED_TRACK_MAX). */
|
|
391
|
+
mcpDeliveredTrackMax?: number;
|
|
392
|
+
/** PR-B2 follow-up test seam: the backoff of the mcp-policy.json write retry (the last value repeats). */
|
|
393
|
+
mcpPolicyRetryDelaysMs?: readonly number[];
|
|
375
394
|
/**
|
|
376
395
|
* Unified memory: TEST SEAM. Production builds the real worker from dist/memory-worker.js (Task 27 pins that bin.ts
|
|
377
396
|
* never passes it).
|
|
@@ -507,6 +526,13 @@ export interface BridgeAgentDeps {
|
|
|
507
526
|
* tests to avoid real file IO / assert pin persistence directly.
|
|
508
527
|
*/
|
|
509
528
|
vaultOwnerStore?: OwnerAuthorityStore;
|
|
529
|
+
/**
|
|
530
|
+
* Security fix 2026-10-30 (option A): the ONLY directory `/v1/vault-write`
|
|
531
|
+
* and `/v1/send-attachment` read from. Defaults to
|
|
532
|
+
* `join(dirname(cfg.storePath), "vault-outbox")`, or null (every upload
|
|
533
|
+
* refused) for an in-memory store. Override in tests.
|
|
534
|
+
*/
|
|
535
|
+
vaultOutboxDir?: string | null;
|
|
510
536
|
}
|
|
511
537
|
/**
|
|
512
538
|
* Voice repeats (a), owner report 2026-10-02: one spoken question reached the
|
|
@@ -750,12 +776,9 @@ export declare class SendForbiddenError extends Error {
|
|
|
750
776
|
readonly channelId: string;
|
|
751
777
|
constructor(channelId: string);
|
|
752
778
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
* not internal there. Dropped. A SendForbiddenError, so both transports treat
|
|
757
|
-
* it as terminal (send_forbidden: the runtime acks, nothing redelivers).
|
|
758
|
-
*/
|
|
779
|
+
export declare class McpReplyWithheldError extends SendForbiddenError {
|
|
780
|
+
constructor(channelId: string);
|
|
781
|
+
}
|
|
759
782
|
export declare class RoomClassChangedSendError extends SendForbiddenError {
|
|
760
783
|
constructor(channelId: string);
|
|
761
784
|
}
|
|
@@ -1199,6 +1222,34 @@ export declare class BridgeAgent {
|
|
|
1199
1222
|
private readonly roomDefaultGrants;
|
|
1200
1223
|
/** Owner grants (spec §7.4): the two manifest switches, read once at construction. */
|
|
1201
1224
|
private readonly ownerDmManifestGrants;
|
|
1225
|
+
/**
|
|
1226
|
+
* MCP client Task 16 (OQ-16, coordinator ruling D-7): SECURECOMS_MCP_GRANTS, read ONCE at construction. "off" writes
|
|
1227
|
+
* kill_switch (deleting); any value other than unset/"on"/"off" is suspended (non-deleting, one content-free warn).
|
|
1228
|
+
*/
|
|
1229
|
+
private readonly mcpHostSwitch;
|
|
1230
|
+
/** MCP Task 16 test seam (BridgeAgentDeps.mcpCatalog); undefined in production, so MCP_CATALOG decides. */
|
|
1231
|
+
private readonly mcpCatalog;
|
|
1232
|
+
/**
|
|
1233
|
+
* MCP Task 16 (OQ-1; PR-B1 audit M-2): the two raw /files fields from the LAST fetch. Any failed or rejected fetch
|
|
1234
|
+
* sets `fetched: false` (no_config, non-deleting): the last values are never kept to keep an `ok` document alive.
|
|
1235
|
+
*/
|
|
1236
|
+
private mcpConfig;
|
|
1237
|
+
/**
|
|
1238
|
+
* MCP Task 16: the mcp-policy.json document this process last WROTE (the file the runtime reads), or null before the
|
|
1239
|
+
* first write and after a failed one (fail closed: issuance reads only a document known to be on disk).
|
|
1240
|
+
*/
|
|
1241
|
+
private mcpPolicyWritten;
|
|
1242
|
+
private mcpPolicyWrittenJson;
|
|
1243
|
+
/** PR-B2 follow-up: a failed write is retried on this backoff (the last value repeats) until one lands or close(). */
|
|
1244
|
+
private readonly mcpPolicyRetryDelaysMs;
|
|
1245
|
+
private mcpPolicyRetryTimer;
|
|
1246
|
+
private mcpPolicyRetryAttempt;
|
|
1247
|
+
/** MCP Task 18: the loopback catalog listers of the attached runtime sockets that declared mcp_client_v1 (ws-server.ts). */
|
|
1248
|
+
private readonly mcpCatalogListers;
|
|
1249
|
+
/** MCP Task 18 (OQ-15): per server 1 in flight, 3 / 60 s, 20 / hour; per agent 40 / hour. Memory only. */
|
|
1250
|
+
private readonly mcpCatalogRate;
|
|
1251
|
+
/** MCP Task 18: answers in progress (outside the ingest cycle). Observable by tests; close() does not wait on them. */
|
|
1252
|
+
private readonly mcpCatalogWork;
|
|
1202
1253
|
private readonly roomManifestGrants;
|
|
1203
1254
|
/**
|
|
1204
1255
|
* Audit M-B4: DM channels whose roster was epoch-fresh at THIS ingest
|
|
@@ -1207,6 +1258,32 @@ export declare class BridgeAgent {
|
|
|
1207
1258
|
private readonly ownerDmCycleFresh;
|
|
1208
1259
|
private toolGrantManifest;
|
|
1209
1260
|
private toolGrantCounter;
|
|
1261
|
+
/**
|
|
1262
|
+
* Tool status Phase 0 (spec §3.1, §3.2; plan Task 8): the latest owner-DM last-turn record, in memory only (no store
|
|
1263
|
+
* table: after a restart there is none until the next typed owner-DM turn). ADVISORY: nothing reads it to decide a
|
|
1264
|
+
* grant. Content-free: enums, booleans, one 15-minute bucket `at` (ruling M-2), one counter, built-in names from the
|
|
1265
|
+
* allowlist. This is the latest record, open bucket included; what leaves the host is the sender's closed record.
|
|
1266
|
+
*/
|
|
1267
|
+
private ownerDmLastTurn;
|
|
1268
|
+
/**
|
|
1269
|
+
* Test seams (Task 8; ruling M-2): the cause function, the bridge clock the record and the sender read, and the
|
|
1270
|
+
* sender's timer and jitter source.
|
|
1271
|
+
*/
|
|
1272
|
+
private toolStatusCause;
|
|
1273
|
+
private toolStatusNow;
|
|
1274
|
+
private toolStatusSetTimer;
|
|
1275
|
+
private toolStatusClearTimer;
|
|
1276
|
+
private toolStatusRandom;
|
|
1277
|
+
/**
|
|
1278
|
+
* Phase 0 Task 9 (spec §3.5), ruling M-2: the post of the record to the narrow route, only just after a 15-minute
|
|
1279
|
+
* UTC bucket boundary, and only a record whose bucket has closed.
|
|
1280
|
+
*/
|
|
1281
|
+
private readonly toolStatusSender;
|
|
1282
|
+
/**
|
|
1283
|
+
* MCP Phase 1 Task 14 (OQ-3 ruling item 3): the sticky R-3 flag, mirrored from the sealed store. Starts false and is
|
|
1284
|
+
* set true at start() by the store (or by a failed read, fail closed). Only applyToolGrantManifest changes it after.
|
|
1285
|
+
*/
|
|
1286
|
+
private mcpSeen;
|
|
1210
1287
|
private manifestState;
|
|
1211
1288
|
/** Failed owner-turn checks since the last successful runtime-config report (§7.7). */
|
|
1212
1289
|
private ownerVerifyFailures;
|
|
@@ -1520,6 +1597,9 @@ export declare class BridgeAgent {
|
|
|
1520
1597
|
private readonly uploadBlobImpl;
|
|
1521
1598
|
/** Track 5: directory where received attachments are saved. */
|
|
1522
1599
|
private readonly attachmentsDirImpl;
|
|
1600
|
+
private readonly vaultOutboxDirImpl;
|
|
1601
|
+
/** Audit 2026-10-30 HIGH 2: the outbox pinned at start(); null = every upload refused. */
|
|
1602
|
+
private outboxPin;
|
|
1523
1603
|
/** B7: persistence for the vault kekset's owner-pin TOFU state. */
|
|
1524
1604
|
private readonly vaultOwnerStoreImpl;
|
|
1525
1605
|
/** B7: last-seen kekset generation per enclave, enforced monotonic across
|
|
@@ -1569,6 +1649,26 @@ export declare class BridgeAgent {
|
|
|
1569
1649
|
*/
|
|
1570
1650
|
refetchRuntimeConfig(): Promise<void>;
|
|
1571
1651
|
private refetchRuntimeConfigOnce;
|
|
1652
|
+
/** MCP Task 16 (OQ-1): the bridge's in-force entitlement reading; not fetched (or failed) is suspended. */
|
|
1653
|
+
private mcpEntitlementInForce;
|
|
1654
|
+
/** PR-B1 audit M-2: forget the last /files fields and rewrite mcp-policy.json (no_config: no grants, no deletion). */
|
|
1655
|
+
private noteMcpConfigUnfetched;
|
|
1656
|
+
/** MCP Task 16: the live inputs of mcp-policy.json (the pinned manifest in force only). */
|
|
1657
|
+
private mcpPolicyInput;
|
|
1658
|
+
/**
|
|
1659
|
+
* MCP Task 16 (spec §12.1): recompute mcp-policy.json and write it when it changed. The written document is what MCP
|
|
1660
|
+
* issuance and release-time re-narrowing read, so it is cleared BEFORE the write and set only after it lands: a failed
|
|
1661
|
+
* write leaves no document in force here (no MCP grant), and the next trigger tries again. Never throws.
|
|
1662
|
+
*/
|
|
1663
|
+
private recomputeMcpPolicy;
|
|
1664
|
+
/** A document is in force: no pending retry, and the next failure starts the backoff from the first delay. */
|
|
1665
|
+
private clearMcpPolicyRetry;
|
|
1666
|
+
/**
|
|
1667
|
+
* PR-B2 follow-up: one pending retry at a time. The retry recomputes from the state THEN (never re-writes a stale
|
|
1668
|
+
* document); any other trigger that writes successfully meanwhile cancels it. Nothing is in force on the bridge side
|
|
1669
|
+
* until a write lands (mcpPolicyWritten stays null).
|
|
1670
|
+
*/
|
|
1671
|
+
private scheduleMcpPolicyRetry;
|
|
1572
1672
|
/**
|
|
1573
1673
|
* Plan 4 Task 11: the refetch under a manifest in force. Returns false when the binding failed (H-4),
|
|
1574
1674
|
* after the memory was applied; the caller then writes nothing else. Throws only from a file write, with
|
|
@@ -2033,6 +2133,41 @@ export declare class BridgeAgent {
|
|
|
2033
2133
|
* there so a retry can resend the exact bytes).
|
|
2034
2134
|
*/
|
|
2035
2135
|
private publishBody;
|
|
2136
|
+
/**
|
|
2137
|
+
* MCP Task 16 (round 2 H-D): message ids whose DELIVERED authorization (after release-time re-narrowing) carried an
|
|
2138
|
+
* `mcp:` grant, with their channel and whether the runtime acked them. Recorded at the buffer exit, the one place
|
|
2139
|
+
* every consumer's copy leaves from (over-recording a turn a non-capable consumer then received without the grant only
|
|
2140
|
+
* adds a check). Kept after the ack, so a late frame citing the turn is still checked. Bounded: the oldest ACKED entry
|
|
2141
|
+
* goes first.
|
|
2142
|
+
*/
|
|
2143
|
+
private readonly mcpDeliveredTurns;
|
|
2144
|
+
private readonly mcpDeliveredTrackMax;
|
|
2145
|
+
/**
|
|
2146
|
+
* Record `m` when its authorization carries an `mcp:` grant. Returns false ONLY when `m` is a new turn, the record is
|
|
2147
|
+
* at its bound and no entry is acked: the caller (the buffer exit) must then issue no mcp: grant (audit M-2, fail
|
|
2148
|
+
* closed). `rebuild` (PR-B2 audit C-1 (b): a restored runtime-inbox row, or a replayed turn) records unconditionally:
|
|
2149
|
+
* those grants were issued before, so the record may exceed its bound by at most the runtime inbox's size.
|
|
2150
|
+
*/
|
|
2151
|
+
private noteMcpDelivered;
|
|
2152
|
+
/**
|
|
2153
|
+
* H-D: whether a frame to `channelId` must pass the relay-free check: it answers a recorded MCP turn (any channel:
|
|
2154
|
+
* a frame of that turn sent elsewhere is checked against ITS channel), or it cites no turn while an MCP turn is
|
|
2155
|
+
* unacked in the same channel.
|
|
2156
|
+
*/
|
|
2157
|
+
private mcpReplyGuarded;
|
|
2158
|
+
/**
|
|
2159
|
+
* H-D, synchronous: read the channel's local leaves from the warm manager in one step and tie the read to the
|
|
2160
|
+
* envelope's SEALED group and epoch, so the leaf set is exactly the membership that can open these bytes. Owner and
|
|
2161
|
+
* agent only: pass. Anyone else: withhold. The tree moved past the seal on the first attempt: StaleEpochError, which
|
|
2162
|
+
* sendWithSelfHeal answers with its one converge-and-re-seal (the existing semantics); still unprovable on the retry,
|
|
2163
|
+
* or no warm manager, no pin, or any throw: withhold (fail closed). Read-only; never touches the group.
|
|
2164
|
+
*
|
|
2165
|
+
* Scope (PR-B2 audit item 5): this guards the `send` path (publishBody) only. `status` detail, the `action_*` run
|
|
2166
|
+
* frames, `/v1/send-attachment` and summary frames are NOT checked; they are safe for MCP turns only because the
|
|
2167
|
+
* runtime emits none of them (pinned by connect-agent-runtime/src/outbound-frames.arch.test.ts). A runtime that starts
|
|
2168
|
+
* emitting any of them needs this guard extended to that frame first.
|
|
2169
|
+
*/
|
|
2170
|
+
private assertMcpReplyRelayFree;
|
|
2036
2171
|
/**
|
|
2037
2172
|
* Audit M-B1 (controller ruling): a reply to a room-granted turn is sealed
|
|
2038
2173
|
* only at the epoch the room was classified at. On a move, the room is
|
|
@@ -2334,12 +2469,17 @@ export declare class BridgeAgent {
|
|
|
2334
2469
|
* checks, so the HTTP boundary maps ALL vault trust failures to one code.
|
|
2335
2470
|
*/
|
|
2336
2471
|
private getVaultSession;
|
|
2337
|
-
/** Read `path`
|
|
2472
|
+
/** Read `path` from the vault outbox (security fix 2026-10-30; see
|
|
2473
|
+
* ./outbox.ts), encrypt + wrap its DEK under the vault's
|
|
2338
2474
|
* current KEK generation, and create+upload+commit it as a new vault
|
|
2339
2475
|
* file node named `name` (which may differ from `path`'s basename). */
|
|
2340
2476
|
vaultWrite(enclaveId: string, vaultChannelId: string | null | undefined, path: string, name: string): Promise<{
|
|
2341
2477
|
node_id: string;
|
|
2342
2478
|
}>;
|
|
2479
|
+
/** Audit 2026-10-30 MED 5: an uploaded file leaves the outbox (no plaintext lingers). Content-free log. */
|
|
2480
|
+
private consumeUploaded;
|
|
2481
|
+
/** Audit 2026-10-30 MED 3: the pinned outbox path, served only on the authenticated GET /v1/vault-outbox. */
|
|
2482
|
+
vaultOutboxPath(): string | null;
|
|
2343
2483
|
/** Download + verify + decrypt a vault file node. Returns the plaintext
|
|
2344
2484
|
* base64-encoded (the loopback boundary is JSON-only, mirroring how
|
|
2345
2485
|
* useVault.downloadFile returns `{name, bytes}` in-memory — here the
|
|
@@ -3042,6 +3182,13 @@ export declare class BridgeAgent {
|
|
|
3042
3182
|
* never changes a grant in step 1. A save that throws keeps the old state.
|
|
3043
3183
|
*/
|
|
3044
3184
|
private applyToolGrantManifest;
|
|
3185
|
+
/**
|
|
3186
|
+
* MCP Task 14: the R-3 hold Task 17 filters on and Task 15 writes as `r3_hold`. True while the manifest in force signs
|
|
3187
|
+
* an owner_dm `mcp:` string, or while the sticky flag is set (an old client's re-sign cannot lift it, audit C-1).
|
|
3188
|
+
*/
|
|
3189
|
+
r3Hold(): boolean;
|
|
3190
|
+
/** Persist then mirror. Setting mirrors even on a failed write; clearing mirrors only after the write (fail closed). */
|
|
3191
|
+
private setMcpSeen;
|
|
3045
3192
|
private logManifest;
|
|
3046
3193
|
/** Classify one inbound turn at the agent's CURRENT epoch for its channel. */
|
|
3047
3194
|
private authorizeTurn;
|
|
@@ -3086,6 +3233,20 @@ export declare class BridgeAgent {
|
|
|
3086
3233
|
* once, outside server control, is an open design follow-up.
|
|
3087
3234
|
*/
|
|
3088
3235
|
private authorizationFor;
|
|
3236
|
+
/**
|
|
3237
|
+
* Tool status Phase 0 (spec §3.2, §3.5; plan Task 8; round 1 H-2, M-4; round 2 H-4): build the candidate record and
|
|
3238
|
+
* keep the previous one (and its `at`) when the change tuple is the same, so `at` means "last changed". Never throws
|
|
3239
|
+
* into the authorization path. Never logs a channel id, sender id or tool name.
|
|
3240
|
+
*/
|
|
3241
|
+
private noteOwnerDmTurn;
|
|
3242
|
+
/** R2-M1: the served manifest names another owner than the pin; the held record belongs to the old owner. */
|
|
3243
|
+
private dropOwnerDmLastTurn;
|
|
3244
|
+
/**
|
|
3245
|
+
* Phase 0 Task 9 (spec §3.5): POST the record, body = the record only, to the narrow agent-key route. The backend
|
|
3246
|
+
* resolves the identity from the device credential; no identity, channel or sender id is in the URL or the body.
|
|
3247
|
+
* Returns the status class only; never throws.
|
|
3248
|
+
*/
|
|
3249
|
+
private postToolStatus;
|
|
3089
3250
|
/**
|
|
3090
3251
|
* One content-free INFO line per authorization decision (every call site of
|
|
3091
3252
|
* authorizationFor stages exactly one runtime turn). It says WHY a turn got
|
|
@@ -3160,6 +3321,28 @@ export declare class BridgeAgent {
|
|
|
3160
3321
|
* client's own wait already renders "Summary unavailable" — so there is
|
|
3161
3322
|
* nothing to catch here besides the send itself.
|
|
3162
3323
|
*/
|
|
3324
|
+
/**
|
|
3325
|
+
* MCP client Task 18 (spec §8 item 2; audit M-3, H-3; OQ-2, OQ-15). Accept an `mcp_catalog_request` only when: its
|
|
3326
|
+
* body is exactly the §8 shape; it is a direct frame (never a voice channel's) in a channel the server marks as a DM
|
|
3327
|
+
* that IS the signed owner DM; verifyOwnerTurn says `verified` (the pinned owner's signature, the signed DM, the leaf
|
|
3328
|
+
* check, not a replay) with `relayLeaf === false`; mcp-policy.json as written is `ok` with this server `ok`; a runtime
|
|
3329
|
+
* socket that declared mcp_client_v1 is attached; and the rate limits admit it. The replay row is written HERE
|
|
3330
|
+
* (M-3: the request never reaches stageRuntimeInbox, where it is otherwise written), for every verified request, before
|
|
3331
|
+
* anything is answered; the ownerReplayPending entry is removed in `finally`. A failure to record answers nothing.
|
|
3332
|
+
* Never throws. The answer runs outside the ingest cycle, so the row is acked now.
|
|
3333
|
+
*/
|
|
3334
|
+
private acceptMcpCatalogRequest;
|
|
3335
|
+
/** The signed server's vendor and the bridge catalog row's issuer, when mcp-policy.json as WRITTEN is `ok` with it `ok`. */
|
|
3336
|
+
private mcpCatalogTarget;
|
|
3337
|
+
/** Ask the runtime, re-check, build (384 KB cap) and send. Never throws. */
|
|
3338
|
+
private answerMcpCatalogRequest;
|
|
3339
|
+
/**
|
|
3340
|
+
* The `mcp_catalog` reply: a system_event envelope with a body-level type (the sendSummary shape), on the shared
|
|
3341
|
+
* self-heal seam and the one publish chokepoint. H-D applies to EVERY attempt (it carries vendor-supplied tool
|
|
3342
|
+
* descriptions): after the seal, before the POST, the leaves at the SEALED group and epoch must be exactly the owner
|
|
3343
|
+
* and the agent, or the frame is withheld. One message id across the retry.
|
|
3344
|
+
*/
|
|
3345
|
+
private sendMcpCatalog;
|
|
3163
3346
|
private handleSummarize;
|
|
3164
3347
|
/**
|
|
3165
3348
|
* A thin adapter over the daemon's one-shot completion seam. Voice-enabled
|
|
@@ -3292,6 +3475,23 @@ export declare class BridgeAgent {
|
|
|
3292
3475
|
* audience that is still signed, else dropped. Only narrows; an unchanged message is returned by reference.
|
|
3293
3476
|
*/
|
|
3294
3477
|
private renarrowManifestGrants;
|
|
3478
|
+
/**
|
|
3479
|
+
* MCP Task 16 (audit H-2; round 2 LOW), after the base re-narrowing: a buffered `mcp:` grant survives release only
|
|
3480
|
+
* when, NOW, the turn is still a plain owner-DM turn in the signed DM channel, the host switch reads "on", the
|
|
3481
|
+
* entitlement reading is in force, the name is not in skills.disabled, the mcp-policy.json document last written
|
|
3482
|
+
* lists the server ok with the tool, and a FRESH synchronous read of the DM's local leaves holds exactly the pinned
|
|
3483
|
+
* owner and the agent (MlsGroupKeyProvider.leafSnapshotCached: one `state` object, no await; null fails closed). Any
|
|
3484
|
+
* other mcp: grant is dropped, never kept as a fallback. relay_free is dropped whenever no mcp: grant survives.
|
|
3485
|
+
* Read-only: never touches the MLS group. Only narrows; returns `m` by reference when nothing changes.
|
|
3486
|
+
*/
|
|
3487
|
+
private renarrowMcpGrants;
|
|
3488
|
+
/**
|
|
3489
|
+
* MCP Task 16 (OQ-2; audit H-2, round 2 H-D): a FRESH read of the channel's local leaf set holds exactly the pinned
|
|
3490
|
+
* owner and this agent. Synchronous (one manager `state`, so the leaves belong to one epoch); a cold manager or any
|
|
3491
|
+
* throw reads as NOT relay-free.
|
|
3492
|
+
*/
|
|
3493
|
+
private ownerDmRelayFreeNow;
|
|
3494
|
+
private renarrowManifestGrantsBase;
|
|
3295
3495
|
private takeBufferedRaw;
|
|
3296
3496
|
/**
|
|
3297
3497
|
* Phase 3 F2: per-channel ordered delivery for the /v1/ws runtime consumer.
|
|
@@ -3540,6 +3740,35 @@ export declare class BridgeAgent {
|
|
|
3540
3740
|
private lastReportedUtilityTools;
|
|
3541
3741
|
noteSearchProvidersSocket(token: object, declared: boolean): void;
|
|
3542
3742
|
clearSearchProvidersSocket(token: object): void;
|
|
3743
|
+
/** MCP Task 16 (spec §15): the same AND-over-sockets state for mcp_client_v1, kept per socket. */
|
|
3744
|
+
private readonly mcpClientSockets;
|
|
3745
|
+
noteMcpClientSocket(token: object, declared: boolean): void;
|
|
3746
|
+
clearMcpClientSocket(token: object): void;
|
|
3747
|
+
/** MCP Task 18: ws-server registers (or, with null, removes) a socket's loopback catalog lister. */
|
|
3748
|
+
noteMcpCatalogLister(token: object, lister: McpCatalogLister | null): void;
|
|
3749
|
+
/**
|
|
3750
|
+
* MCP Task 19 (spec §12.3): "ok" when every attached runtime socket declared mcp_client_v1 (the AND, which harness
|
|
3751
|
+
* sockets hold false: a harness socket counts in the AND and never declares it); else "harness_consumer" when the
|
|
3752
|
+
* latest hello was a harness; else "missing".
|
|
3753
|
+
*/
|
|
3754
|
+
private reportedMcpCapability;
|
|
3755
|
+
/**
|
|
3756
|
+
* MCP Task 19 (spec §12.3): at most 8 `{server_id, vendor, state}` for the signed servers of the manifest in force,
|
|
3757
|
+
* from mcp-policy.json as WRITTEN. Only the states the bridge itself knows: top `kill_switch` and `not_entitled` give
|
|
3758
|
+
* that state per server; top `ok` gives `ok` for a server the bridge lists `ok` and `malformed` for one it refused.
|
|
3759
|
+
* Every other top state (no capability, suspended, no config, switch off, section absent, malformed section) gives no
|
|
3760
|
+
* row. The runtime-only states (needs_login, login_expired, definition_changed, server_version_unsupported,
|
|
3761
|
+
* server_unreachable) have no runtime-to-bridge report path in Phase 1. `vendor` is reported only when it is a key of
|
|
3762
|
+
* the bridge's compiled catalog; a signed vendor outside it gives no row. Never a tool name, URL or issuer.
|
|
3763
|
+
*/
|
|
3764
|
+
private reportedMcpServers;
|
|
3765
|
+
/**
|
|
3766
|
+
* MCP Task 16: every attached runtime socket declared mcp_client_v1; no socket attached means absent (false).
|
|
3767
|
+
* PR-B2 follow-up (documented): a HARNESS socket counts in this AND. ws-server notes every socket that said hello,
|
|
3768
|
+
* and a harness hello is always noted `false` (harnessClaimed never resets), so one attached harness connector holds
|
|
3769
|
+
* the capability false for files, the report and mcp-policy.json (top `no_capability`) while it is attached.
|
|
3770
|
+
*/
|
|
3771
|
+
mcpClientCapable(): boolean;
|
|
3543
3772
|
noteUtilityToolsSocket(token: object, declared: boolean): void;
|
|
3544
3773
|
clearUtilityToolsSocket(token: object): void;
|
|
3545
3774
|
/** U6: every attached runtime socket declared utility_tools_v1; no socket attached means absent (false). */
|
|
@@ -28,6 +28,8 @@ export declare const MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
|
|
|
28
28
|
export declare const SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
|
|
29
29
|
/** Weather/currency spec §8.1 (U6): the /v1/ws hello feature of a runtime that knows weather_forecast and currency_convert. Pinned to the runtime's by capability-parity.test.ts. */
|
|
30
30
|
export declare const UTILITY_TOOLS_FEATURE = "utility_tools_v1";
|
|
31
|
+
/** MCP client spec §15 (plan Task 16): the /v1/ws hello feature of a runtime that runs the read-only MCP client. Never a TOOL_FEATURE value. Pinned to the runtime's by capability-parity.test.ts. */
|
|
32
|
+
export declare const MCP_CLIENT_FEATURE = "mcp_client_v1";
|
|
31
33
|
export declare const CONSTANT_TOOL_NAMES: Readonly<Record<"owner_dm" | "team_room", readonly string[]>>;
|
|
32
34
|
/**
|
|
33
35
|
* Spec §7.9: a consumer that did not declare manifest_grants_v1 (runtime 0.12.2, a harness,
|
|
@@ -64,3 +66,29 @@ export declare function withSearchCapability(m: BridgeMessage, consumerHasFeatur
|
|
|
64
66
|
* policy file and TOOL_FEATURE. Returns `m` by reference when nothing drops; never mutates `m`.
|
|
65
67
|
*/
|
|
66
68
|
export declare function withUtilityCapability(m: BridgeMessage, consumerHasFeature: boolean): BridgeMessage;
|
|
69
|
+
/** The exact prefix of an owner-signed MCP grant name (`mcp:<server_id>/<tool>`). */
|
|
70
|
+
export declare const MCP_GRANT_PREFIX = "mcp:";
|
|
71
|
+
/**
|
|
72
|
+
* Spec §15 per-delivery MCP filter, on every delivery path after withUtilityCapability, judged by the consumer's OWN
|
|
73
|
+
* hello. An `mcp:` grant survives only for a consumer that declared mcp_client_v1, on a turn whose audience is
|
|
74
|
+
* `owner_dm` and that carries no relay_speaker mark (any value: the mark disqualifies, T-10). Otherwise every `mcp:`
|
|
75
|
+
* grant is dropped, marked or unmarked (a harness, an older runtime and every HTTP path never receive one, T-14).
|
|
76
|
+
* Every other grant and field is unchanged. Only narrows; returns `m` by reference when nothing drops; never mutates.
|
|
77
|
+
*/
|
|
78
|
+
export declare function withMcpCapability(m: BridgeMessage, consumerHasFeature: boolean): BridgeMessage;
|
|
79
|
+
/**
|
|
80
|
+
* R-3 "holds" keep set, the bridge copy (spec §10, audit M-1): an ALLOW-list, so a future built-in is withdrawn by
|
|
81
|
+
* default while the hold is on. Pinned equal to the runtime's (Task 12b) by consumer-grants.mcp.test.ts, and over
|
|
82
|
+
* MANIFEST_KNOWN_TOOLS.owner_dm it withdraws exactly fetch_url, web_extract, http_request and sql_query.
|
|
83
|
+
*/
|
|
84
|
+
export declare const R3_KEEP_TOOLS: ReadonlySet<string>;
|
|
85
|
+
/**
|
|
86
|
+
* Spec §10 R-3 "holds" filter, on every delivery path to every consumer. `hold` is the agent's R-3 hold
|
|
87
|
+
* (BridgeAgent.r3Hold(): the in-force manifest's signedMcpPresent, computed on the raw verified owner_dm array, or the
|
|
88
|
+
* sticky mcp_seen flag). It is message-independent on purpose: it never reads this message's grants (which
|
|
89
|
+
* withMcpCapability, entitlement, kill switches or skills.disabled may already have emptied), so no narrowing step can
|
|
90
|
+
* lift it (audit C-2, OQ-13). While it holds, every owner-DM turn (audience owner_dm, or the owner-relayed voice turn
|
|
91
|
+
* marked relay_speaker "owner") keeps only R3_KEEP_TOOLS and `mcp:` grants. Other audiences are unchanged. Only
|
|
92
|
+
* narrows; returns `m` by reference when nothing drops; never mutates.
|
|
93
|
+
*/
|
|
94
|
+
export declare function withR3Holds(m: BridgeMessage, hold: boolean): BridgeMessage;
|
package/dist/errors.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* WebSocket returns `{type:"error", error, message, detail?}`. A client gets the
|
|
7
7
|
* same `error` code regardless of which transport it used. (A2A §9.5/§10.6 pattern.)
|
|
8
8
|
*/
|
|
9
|
-
export type BridgeErrorCode = "unauthorized" | "forbidden_origin" | "not_found" | "invalid_request" | "invalid_json" | "body_too_large" | "channel_pending" | "rate_limited" | "agent_paused" | "send_forbidden" | "group_superseded" | "unknown_action_run" | "vault_integrity_failed" | "vault_ref_changed" | "session_superseded" | "reply_credit_unknown" | "internal";
|
|
9
|
+
export type BridgeErrorCode = "unauthorized" | "forbidden_origin" | "not_found" | "invalid_request" | "invalid_json" | "body_too_large" | "channel_pending" | "rate_limited" | "agent_paused" | "send_forbidden" | "group_superseded" | "unknown_action_run" | "vault_integrity_failed" | "vault_ref_changed" | "session_superseded" | "reply_credit_unknown" | "path_outside_outbox" | "internal";
|
|
10
10
|
export interface BridgeErrorSpec {
|
|
11
11
|
/** HTTP status this code maps to (the WS binding ignores it). */
|
|
12
12
|
http: number;
|
package/dist/grants/binding.d.ts
CHANGED
|
@@ -69,7 +69,8 @@ export declare function legacyAssignToken(d: {
|
|
|
69
69
|
* unknown name in manifest-policy.json as an unreadable file and drops EVERY manifest grant, so a tool listed here is
|
|
70
70
|
* written to the policy file (and delivered) only when every attached runtime socket declared its feature.
|
|
71
71
|
*/
|
|
72
|
-
|
|
72
|
+
/** MCP Task 16: "mcp_client_v1" is a Feature (capabilityFeatures) but never a TOOL_FEATURE value (pinned by capability-parity.test.ts). */
|
|
73
|
+
export type Feature = "search_providers_v2" | "utility_tools_v1" | "mcp_client_v1";
|
|
73
74
|
export type FeatureSet = ReadonlySet<Feature>;
|
|
74
75
|
/** No socket attached, or none declared anything: fail closed. */
|
|
75
76
|
export declare const NO_FEATURES: FeatureSet;
|
|
@@ -39,9 +39,17 @@ export interface EffectiveInput {
|
|
|
39
39
|
};
|
|
40
40
|
readonly roomSearchCapped: boolean;
|
|
41
41
|
}
|
|
42
|
+
/** Phase 0 Task 6 (spec §3.2, round 2 M-12): why a signed tool was dropped owner-side, pushed at the line that drops it. */
|
|
43
|
+
export type WithheldWhy = "disabled_by_owner" | "no_endpoint" | "no_database";
|
|
44
|
+
export interface WithheldTool {
|
|
45
|
+
readonly tool: string;
|
|
46
|
+
readonly why: WithheldWhy;
|
|
47
|
+
}
|
|
42
48
|
export interface EffectiveGrant {
|
|
43
49
|
readonly grants: readonly ReadGrant[];
|
|
44
50
|
readonly source: GrantSource;
|
|
45
51
|
readonly withheldBySearchCap: boolean;
|
|
52
|
+
/** Signed tools the manifest branch dropped, in ceiling order, frozen. [] on every other branch. The team-room search cap is not an owner-side reason (it keeps withheldBySearchCap). */
|
|
53
|
+
readonly withheld: readonly WithheldTool[];
|
|
46
54
|
}
|
|
47
55
|
export declare function effectiveToolGrants(i: EffectiveInput): EffectiveGrant;
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { type SearchProvider } from "./search-providers.js";
|
|
10
10
|
import { type OwnerGrantPin } from "./pin.js";
|
|
11
|
+
import { type SignedMcp } from "./mcp-section.js";
|
|
12
|
+
import { type McpCatalog } from "./mcp-catalog.js";
|
|
11
13
|
export declare const TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
|
|
12
14
|
export declare const MANIFEST_STATES: readonly ["ok", "unchanged", "no_pin", "no_manifest", "malformed", "unsupported_algo", "bad_signature", "wrong_agent", "wrong_owner", "rollback", "unknown_entry", "kept_last_good"];
|
|
13
15
|
export type ManifestState = (typeof MANIFEST_STATES)[number];
|
|
@@ -33,6 +35,17 @@ export interface ToolGrantManifest {
|
|
|
33
35
|
readonly databases: Readonly<Record<string, SignedDatabase>>;
|
|
34
36
|
/** Plan 4 Task 7 (spec §8.1, §7.10): the signed values the config binding compares. Required. */
|
|
35
37
|
readonly config: SignedConfig;
|
|
38
|
+
/**
|
|
39
|
+
* MCP Phase 1 Task 14 (spec §12.2, §13a). `mcp` is the parsed signed section, or null when the key is absent or the
|
|
40
|
+
* section is malformed (`mcpKey` tells them apart, OQ-3 ruling). A malformed section never makes the manifest malformed.
|
|
41
|
+
*/
|
|
42
|
+
readonly mcp: SignedMcp | null;
|
|
43
|
+
readonly mcpKey: "absent" | "present";
|
|
44
|
+
/**
|
|
45
|
+
* R-3 raw predicate (audit C-2): some string in the signature-verified `grants.owner_dm` starts with `mcp:`, read after
|
|
46
|
+
* step 5 and BEFORE step 6, parseMcp, the ceiling and the catalog, so none of them can lift R-3.
|
|
47
|
+
*/
|
|
48
|
+
readonly signedMcpPresent: boolean;
|
|
36
49
|
}
|
|
37
50
|
export interface SignedEndpoint {
|
|
38
51
|
readonly baseUrl: string;
|
|
@@ -75,4 +88,5 @@ export declare function verifyToolGrantManifest(raw: unknown, ctx: {
|
|
|
75
88
|
pin: OwnerGrantPin;
|
|
76
89
|
agentIdentityId: string;
|
|
77
90
|
storedCounter: number | null;
|
|
91
|
+
mcpCatalog?: McpCatalog;
|
|
78
92
|
}): ManifestVerdict;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bridge's copy of the compiled MCP catalog (spec §11, §12.2 "the vendor and tool are in the bridge's catalog copy").
|
|
3
|
+
* Phase 1 holds exactly one row, Linear (Task 22; Eric 2026-10-30, "ship Linear only"), identical to the runtime's
|
|
4
|
+
* (`connect-agent-runtime/src/mcp/catalog.ts`) value for value. No vendor value is ever written from memory; each cites
|
|
5
|
+
* its source below. `mcp-catalog.parity.test.ts` and `mcp-lists.parity.test.ts` pin this copy to the runtime's byte for
|
|
6
|
+
* byte, so the two cannot drift. How a row is added, changed or withdrawn: docs/runbooks/mcp-catalog-update.md.
|
|
7
|
+
*/
|
|
8
|
+
/** The runtime's `CatalogRow`, field for field (OQ-6 ruling: the #1308 §11 fields exactly). */
|
|
9
|
+
export interface McpCatalogRow {
|
|
10
|
+
displayName?: string;
|
|
11
|
+
host: string;
|
|
12
|
+
readOnlyUrl: string;
|
|
13
|
+
readTools: readonly string[];
|
|
14
|
+
scopes: readonly string[];
|
|
15
|
+
redirectPort?: 47823;
|
|
16
|
+
issuer: string;
|
|
17
|
+
authorizationEndpoint: string;
|
|
18
|
+
tokenEndpoint: string;
|
|
19
|
+
revocationEndpoint?: string;
|
|
20
|
+
docs: Readonly<Record<string, string>>;
|
|
21
|
+
}
|
|
22
|
+
export type McpCatalog = Readonly<Record<string, McpCatalogRow>>;
|
|
23
|
+
export declare const MCP_CATALOG: McpCatalog;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client Phase 1, Task 16 (spec §12.2 "issued"; OQ-1, OQ-2 rulings; audit H-3, M-4): which `mcp:` grants an
|
|
3
|
+
* owner-DM turn is issued. Pure; the bridge calls it right after effectiveToolGrants, on the owner-DM branch only.
|
|
4
|
+
*
|
|
5
|
+
* issued = { mcp:s/t in manifest.grants.owner_dm (step 6 already applied the code ceiling and the catalog) }
|
|
6
|
+
* only when ALL of:
|
|
7
|
+
* effectiveToolGrants returned source "manifest" (so the turn was manifest-eligible: never a fallback);
|
|
8
|
+
* the owner verdict is `verified` with relayLeaf === false (OQ-2: exactly owner and agent leaves);
|
|
9
|
+
* the bridge's in-force entitlement reading is "entitled" (OQ-1);
|
|
10
|
+
* SECURECOMS_MCP_GRANTS reads "on" (D-7: off and suspended issue nothing);
|
|
11
|
+
* SECURECOMS_OWNER_DM_MANIFEST_GRANTS is on;
|
|
12
|
+
* the mcp-policy.json document the bridge last WROTE is top `ok` and lists the server `ok` with the tool;
|
|
13
|
+
* minus every name in skills.disabled.
|
|
14
|
+
*
|
|
15
|
+
* Fail closed throughout: an absent relayLeaf, an absent policy, a missing server or tool all issue nothing.
|
|
16
|
+
*/
|
|
17
|
+
import type { GrantSource, ReadGrant } from "./effective.js";
|
|
18
|
+
import type { ToolGrantManifest } from "./manifest.js";
|
|
19
|
+
import type { McpEntitlementReading, McpHostSwitch } from "./mcp-policy.js";
|
|
20
|
+
import type { OwnerTurnVerdict } from "./owner-turn.js";
|
|
21
|
+
import type { McpPolicyDoc } from "../service/runtime-config-dir.js";
|
|
22
|
+
export interface McpIssuanceInput {
|
|
23
|
+
readonly source: GrantSource;
|
|
24
|
+
readonly verdict: OwnerTurnVerdict | undefined;
|
|
25
|
+
readonly entitlement: McpEntitlementReading;
|
|
26
|
+
readonly hostSwitch: McpHostSwitch;
|
|
27
|
+
readonly ownerDmManifestSwitchOn: boolean;
|
|
28
|
+
readonly disabled: ReadonlySet<string>;
|
|
29
|
+
readonly manifest: ToolGrantManifest | null;
|
|
30
|
+
readonly policy: McpPolicyDoc | null;
|
|
31
|
+
}
|
|
32
|
+
/** True when the written policy lists `serverId` as `ok` with `tool` among its tools. */
|
|
33
|
+
export declare function mcpPolicyAllows(policy: McpPolicyDoc | null, serverId: string, tool: string): boolean;
|
|
34
|
+
export declare function mcpIssuance(i: McpIssuanceInput): readonly ReadGrant[];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP client Phase 1, Task 15 (spec §6.5, §12.1; OQ-1 and OQ-3 rulings; round 2 H-B): the `mcp-policy.json` document.
|
|
3
|
+
*
|
|
4
|
+
* Pure, except mcpGrantsHostSwitch's one start-time warn. The bridge writes the result with writeMcpPolicy BEFORE it
|
|
5
|
+
* issues any MCP grant (Task 16 wires it). The runtime deletes a token only on `ok` for a server named in `remove`
|
|
6
|
+
* (and not listed), `not_entitled` or `kill_switch` (deletion.ts; a host typo is therefore `suspended`, never
|
|
7
|
+
* `kill_switch`). The rules that matter most here:
|
|
8
|
+
* - C-NEW-1 (2026-10-30): `remove` is copied ONLY from the signed `mcp.removed` of the manifest in force, and only into
|
|
9
|
+
* the `ok` document; every other document writes `remove: []`. Absence from `servers` never deletes, so a stale
|
|
10
|
+
* view re-signed by the owner cannot launder a deletion. No record of an earlier section is kept;
|
|
11
|
+
* - a server the bridge refuses for its own reasons stays LISTED as `refused`;
|
|
12
|
+
* - a verified manifest with NO `mcp` key is `section_absent`, never top `ok` with empty servers (OQ-3). An explicit
|
|
13
|
+
* signed `"mcp": {"servers": {}}` is top `ok` with empty servers and deletes nothing; `{"servers": {}, "removed": "*"}`
|
|
14
|
+
* is remove-all.
|
|
15
|
+
*
|
|
16
|
+
* `r3_hold` (= signedMcpPresent || mcp_seen) is in EVERY document, empty-servers ones included (round 2 H-B).
|
|
17
|
+
*
|
|
18
|
+
* Precedence, first match wins (the spec fixes no order; this one puts the host's own switch first, then what the bridge
|
|
19
|
+
* cannot know, then the backend's entitlement, then local switches, then the manifest, then the consumer):
|
|
20
|
+
* kill_switch | host suspended (D-7), no_config, not_entitled | suspended, switch_off, no_manifest, section_absent,
|
|
21
|
+
* malformed, no_capability, ok
|
|
22
|
+
*/
|
|
23
|
+
import type { ToolGrantManifest } from "./manifest.js";
|
|
24
|
+
import { type McpCatalog } from "./mcp-catalog.js";
|
|
25
|
+
import type { McpPolicyDoc } from "../service/runtime-config-dir.js";
|
|
26
|
+
import { type ServiceLog } from "../service-log.js";
|
|
27
|
+
/**
|
|
28
|
+
* Architect skew ruling (2026-10-30): this bridge declares, in EVERY hello reply (ws-server.ts), that it is the sole
|
|
29
|
+
* writer of mcp-policy.json: it refuses backend delivery of that name (runtime-config-dir.ts isLocalOnlyName). The
|
|
30
|
+
* runtime deletes tokens and offers MCP tools from the file only on a turn from a declaring socket (connect-agent-runtime
|
|
31
|
+
* data-plane.ts MCP_POLICY_WRITER_FEATURE). Unconditional: a statement about this build, not about the runtime or the
|
|
32
|
+
* switches (the host kill switch travels IN the file and must keep deleting). Never declare it from a build where the
|
|
33
|
+
* backend can write that name (ws-server.test.ts same-build row).
|
|
34
|
+
*/
|
|
35
|
+
export declare const MCP_POLICY_WRITER_FEATURE = "mcp_policy_writer_v1";
|
|
36
|
+
export type McpEntitlementReading = "entitled" | "not_entitled" | "suspended";
|
|
37
|
+
/**
|
|
38
|
+
* OQ-1 ruling (ADR G). Only `true`/"entitled" is in force and only `false`/"not_entitled" is the deleting downgrade.
|
|
39
|
+
* Anything else (missing, unknown, disagreeing, non-boolean) is `suspended`: no grants, no deletion.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mcpEntitlementReading(mcpEntitled: unknown, mcpCause: unknown): McpEntitlementReading;
|
|
42
|
+
/**
|
|
43
|
+
* Host kill switch SECURECOMS_MCP_GRANTS (OQ-16; coordinator ruling D-7). Tri-state, so a typo can never delete:
|
|
44
|
+
* unset or exactly "on" -> "on"; exactly "off" -> "off" (writes `kill_switch`, a deleting state);
|
|
45
|
+
* any other value -> "suspended" (writes top `suspended`: no grants, no deletion).
|
|
46
|
+
*/
|
|
47
|
+
export type McpHostSwitch = "on" | "off" | "suspended";
|
|
48
|
+
/**
|
|
49
|
+
* Read once at bridge start by the caller. A suspended value logs ONE content-free warn per call carrying the enum,
|
|
50
|
+
* never the raw value (it may be anything an operator typed).
|
|
51
|
+
*/
|
|
52
|
+
export declare function mcpGrantsHostSwitch(env: Readonly<Record<string, string | undefined>>, log?: ServiceLog): McpHostSwitch;
|
|
53
|
+
export interface McpPolicyInput {
|
|
54
|
+
/** mcpGrantsHostSwitch(), read once at bridge start. "off" writes `kill_switch`; "suspended" writes `suspended`. */
|
|
55
|
+
readonly hostSwitch: McpHostSwitch;
|
|
56
|
+
/** The last /runtime-config/files result: not yet fetched (or failed), or the two raw fields from a successful fetch. */
|
|
57
|
+
readonly config: {
|
|
58
|
+
readonly fetched: false;
|
|
59
|
+
} | {
|
|
60
|
+
readonly fetched: true;
|
|
61
|
+
readonly mcp_entitled: unknown;
|
|
62
|
+
readonly mcp_cause: unknown;
|
|
63
|
+
};
|
|
64
|
+
/** SECURECOMS_OWNER_DM_MANIFEST_GRANTS on. */
|
|
65
|
+
readonly ownerDmManifestSwitchOn: boolean;
|
|
66
|
+
/** The verified manifest in force, or null. */
|
|
67
|
+
readonly manifest: ToolGrantManifest | null;
|
|
68
|
+
/** `mcp_client_v1` on every attached runtime socket of the identity (absent with none). */
|
|
69
|
+
readonly capability: boolean;
|
|
70
|
+
/** The sticky flag from the sealed store (Task 14). */
|
|
71
|
+
readonly mcpSeen: boolean;
|
|
72
|
+
/** Test seam; the compiled bridge catalog by default. */
|
|
73
|
+
readonly catalog?: McpCatalog;
|
|
74
|
+
}
|
|
75
|
+
export declare function mcpPolicyFor(i: McpPolicyInput): McpPolicyDoc;
|