@suveren/gateway 0.13.0 → 0.13.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/README.md CHANGED
@@ -89,7 +89,7 @@ No credentials. No signing keys. Just the scope of what the agent is allowed to
89
89
 
90
90
  ## Quick Start
91
91
 
92
- Pick whichever you have on hand — both produce the same gateway.
92
+ Pick whichever fits — all three produce the same gateway. For company laptops managed by IT, use Option C.
93
93
 
94
94
  ### Option A — Docker
95
95
 
@@ -99,7 +99,7 @@ Requires [Docker](https://docs.docker.com/get-docker/).
99
99
  docker run -d --name suveren-gateway \
100
100
  -p 7400:3000 -p 7430:3030 \
101
101
  -v $HOME/.suveren:/app/data \
102
- ghcr.io/humanagencyprotocol/suveren-gateway
102
+ ghcr.io/suverenai/suveren-gateway
103
103
  ```
104
104
 
105
105
  Open `http://localhost:7400`. The MCP server is at `http://localhost:7430`.
@@ -130,9 +130,23 @@ suveren-gateway restart
130
130
  PowerShell 5.1, which is what ships with Windows. Use `;` there if you want
131
131
  them on one line.)
132
132
 
133
+ ### Option C — Windows installer (company laptops)
134
+
135
+ For laptops managed by a company's IT: no admin rights, no Node.js and no access to the public npm registry needed. Every [GitHub Release](https://github.com/suverenai/suveren-gateway/releases) from 0.13.0 on carries:
136
+
137
+ | File | What it is |
138
+ |---|---|
139
+ | `suveren-gateway-<version>-windows-unsigned.msi` | Per-user installer: the gateway, its own Node.js and the tested connector versions — nothing is downloaded at runtime |
140
+ | `suveren-gateway-<version>-windows-signing-kit.zip` | The unpacked contents, the installer source and `build-signed.ps1`, so IT can sign every file with its own certificate and rebuild the `.msi` |
141
+ | `SHA256SUMS` | Checksums of both files |
142
+
143
+ **The installer is not signed by us.** IT checks where it comes from (`sha256sum -c SHA256SUMS` and `gh attestation verify <file> -R suverenai/suveren-gateway`, which shows the exact commit it was built from), signs it, and distributes it, e.g. with Intune. IT can preset the Authority Server, simulation mode, proxy and certificate centrally; the employee cannot change them, and updates come from IT. Step by step: [Windows IT guide](docs/windows-it-guide.md) · settings: [Managed settings](docs/managed-settings.md).
144
+
145
+ Open `http://localhost:3400` (Start menu: *Suveren Gateway*). The MCP server is at `http://localhost:3430`.
146
+
133
147
  ### Connecting an MCP client
134
148
 
135
- Either path exposes the same MCP transports — use the port from the path you chose (7430 for Docker, 3430 for npm):
149
+ Every option exposes the same MCP transports — use the port from the option you chose (7430 for Docker, 3430 for npm and the Windows installer):
136
150
 
137
151
  ```
138
152
  Streamable HTTP: POST http://localhost:<port>/mcp
@@ -263,6 +277,8 @@ for the full command reference.
263
277
  | [Authorization Flow](docs/authorization-flow.md) | Data flow, gate wizard, tool execution, agent context |
264
278
  | [Security Model](docs/security.md) | Enforcement layers, verification, encryption, fail-closed design |
265
279
  | [Development](docs/development.md) | Local setup, env vars, Docker, testing |
280
+ | [Windows IT guide](docs/windows-it-guide.md) | Verify, sign and distribute the Windows installer; network and updates |
281
+ | [Managed settings](docs/managed-settings.md) | Settings IT presets and locks (registry or policy file) |
266
282
 
267
283
  ---
268
284
 
@@ -2202,7 +2202,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2202
2202
  import { z as z2 } from "zod";
2203
2203
 
2204
2204
  // src/lib/mandate-brief.ts
2205
- import { getProfile } from "@hap/core";
2205
+ import { getProfile as getProfile3 } from "@hap/core";
2206
2206
 
2207
2207
  // src/lib/consumption.ts
2208
2208
  import { boundActionTypes } from "@hap/core";
@@ -2296,87 +2296,16 @@ function getContextForBrief(dataDir2) {
2296
2296
  return { brief: truncated, truncated: true };
2297
2297
  }
2298
2298
 
2299
- // src/lib/mandate-brief.ts
2300
- function shortProfileName(profileId) {
2301
- const withoutVersion = profileId.replace(/@.*$/, "");
2302
- const parts = withoutVersion.split("/");
2303
- return parts[parts.length - 1];
2304
- }
2305
- function countToolsByGating(profileId, integrationManager2) {
2306
- if (!integrationManager2) return { gated: 0, readOnly: 0 };
2307
- const allTools = integrationManager2.getAllTools();
2308
- let gated = 0;
2309
- let readOnly = 0;
2310
- for (const tool of allTools) {
2311
- if (!tool.gating || !tool.gating.profile) {
2312
- readOnly++;
2313
- } else if (tool.gating.profile === profileId || tool.gating.profile === shortProfileName(profileId)) {
2314
- if (tool.gating.staticExecution?.action_type === "read" && Object.keys(tool.gating.executionMapping ?? {}).length === 0) {
2315
- readOnly++;
2316
- } else {
2317
- gated++;
2318
- }
2319
- }
2320
- }
2321
- return { gated, readOnly };
2299
+ // src/lib/simulation-mode.ts
2300
+ function isSimulationMode() {
2301
+ const policySimulation = readPolicy().policy.simulation;
2302
+ if (policySimulation !== void 0) return policySimulation;
2303
+ return process.env.SUVEREN_SIMULATION === "1";
2322
2304
  }
2323
- function buildMandateBrief(opts) {
2324
- const { authorizations, executionLog, integrationManager: integrationManager2, contextDir } = opts;
2325
- const lines = [
2326
- "You are connected to Suveren \u2014 the gateway that gates every privileged tool call you make.",
2327
- "Suveren implements the bounded-authority model from the open Human Agency Protocol (HAP).",
2328
- "You have bounded authorities granted by human decision owners.",
2329
- "You MUST stay within these bounds \u2014 the Gatekeeper will reject actions that exceed them."
2330
- ];
2331
- const { brief: contextBrief } = getContextForBrief(contextDir);
2332
- if (contextBrief) {
2333
- lines.push("");
2334
- lines.push("=== CONTEXT ===");
2335
- lines.push("");
2336
- lines.push(contextBrief);
2337
- }
2338
- const active = authorizations.filter((a) => a.complete);
2339
- const pending = authorizations.filter((a) => !a.complete);
2340
- const now = Math.floor(Date.now() / 1e3);
2341
- if (active.length > 0) {
2342
- lines.push("");
2343
- lines.push("=== ACTIVE AUTHORITIES ===");
2344
- lines.push("");
2345
- for (const auth of active) {
2346
- const earliestExpiry = Math.min(...auth.attestations.map((a) => a.expiresAt));
2347
- const remainingMin = Math.max(0, Math.round((earliestExpiry - now) / 60));
2348
- const shortName = shortProfileName(auth.profileId);
2349
- const version = auth.profileId.match(/@(.+)$/)?.[1] ?? "";
2350
- const shortId = version ? `${shortName}@${version}` : shortName;
2351
- const boundsDesc = Object.entries(auth.frame).filter(([key]) => key !== "profile" && key !== "path").map(([key, value]) => `${key}:${value}`).join(" \xB7 ");
2352
- const parts = [];
2353
- if (boundsDesc) parts.push(boundsDesc);
2354
- if (executionLog) {
2355
- const profile = getProfile(auth.profileId) ?? getProfile(shortName);
2356
- const consumption = getConsumptionState(auth, executionLog, profile);
2357
- const compact = formatConsumptionCompact(consumption);
2358
- if (compact) parts.push(compact);
2359
- }
2360
- const { gated, readOnly } = countToolsByGating(auth.profileId, integrationManager2);
2361
- if (gated > 0 || readOnly > 0) parts.push(`${gated} gated tools`);
2362
- parts.push(`${remainingMin} min remaining`);
2363
- lines.push(`[${shortId}] ${parts.join(" \xB7 ")}`);
2364
- lines.push(` \u2192 list-authorizations(domain: "${shortName}") for intent & details`);
2365
- lines.push("");
2366
- }
2367
- }
2368
- if (pending.length > 0) {
2369
- lines.push("=== PENDING (awaiting attestations) ===");
2370
- lines.push("");
2371
- for (const auth of pending) {
2372
- const missing = auth.requiredDomains.filter((d) => !auth.attestedDomains.includes(d));
2373
- lines.push(`[${auth.path}] ${auth.profileId} \u2014 needs: ${missing.join(", ")}`);
2374
- }
2375
- lines.push("");
2376
- }
2377
- lines.push("When you receive a task, call list-authorizations(domain) to load full details for the relevant domain.");
2378
- return lines.join("\n");
2305
+ function manifestIsSimulated(manifest) {
2306
+ return Boolean(manifest?.simulation);
2379
2307
  }
2308
+ var SIMULATION_BLOCK_REASON = "blocked: simulation mode \u2014 real systems are off";
2380
2309
 
2381
2310
  // src/lib/locked-notice.ts
2382
2311
  function uiUrl() {
@@ -2426,9 +2355,6 @@ function lockedNotice(action, reason = "restart") {
2426
2355
  }
2427
2356
  }
2428
2357
 
2429
- // src/tools/authorizations.ts
2430
- import { getProfile as getProfile4 } from "@hap/core";
2431
-
2432
2358
  // src/lib/receipt-footer.ts
2433
2359
  import { deriveIdentityLine } from "@hap/core";
2434
2360
  var AS_BASE = (process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai").replace(/\/$/, "");
@@ -2490,10 +2416,10 @@ import {
2490
2416
  selectBoundFields,
2491
2417
  isFieldBinding,
2492
2418
  bindingAppliesTo,
2493
- getProfile as getProfile2
2419
+ getProfile
2494
2420
  } from "@hap/core";
2495
2421
  function getContentBinding(profileId) {
2496
- return getProfile2(profileId)?.content_binding;
2422
+ return getProfile(profileId)?.content_binding;
2497
2423
  }
2498
2424
  function computeContentBinding(profileId, tool, toolArgs, actionType) {
2499
2425
  const binding = getContentBinding(profileId);
@@ -2913,7 +2839,7 @@ function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
2913
2839
  }
2914
2840
 
2915
2841
  // src/lib/tool-proxy.ts
2916
- import { getProfile as getProfile3, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
2842
+ import { getProfile as getProfile2, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
2917
2843
  import { readFile } from "fs/promises";
2918
2844
  import { extname } from "path";
2919
2845
  import { randomUUID } from "crypto";
@@ -2978,17 +2904,6 @@ function getAllManifests() {
2978
2904
  return Array.from(manifests.values());
2979
2905
  }
2980
2906
 
2981
- // src/lib/simulation-mode.ts
2982
- function isSimulationMode() {
2983
- const policySimulation = readPolicy().policy.simulation;
2984
- if (policySimulation !== void 0) return policySimulation;
2985
- return process.env.SUVEREN_SIMULATION === "1";
2986
- }
2987
- function manifestIsSimulated(manifest) {
2988
- return Boolean(manifest?.simulation);
2989
- }
2990
- var SIMULATION_BLOCK_REASON = "blocked: simulation mode \u2014 real systems are off";
2991
-
2992
2907
  // src/lib/tool-proxy.ts
2993
2908
  var IMAGE_MIME = {
2994
2909
  ".jpg": "image/jpeg",
@@ -3050,7 +2965,7 @@ function profileMatches(profileId, shortName) {
3050
2965
  return profileId === shortName || profileId.includes("/" + shortName + "@") || profileId.endsWith("/" + shortName);
3051
2966
  }
3052
2967
  function countBoundsFor(profileId, actionType) {
3053
- const fields = getProfile3(profileId)?.boundsSchema?.fields;
2968
+ const fields = getProfile2(profileId)?.boundsSchema?.fields;
3054
2969
  if (!fields) return null;
3055
2970
  return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
3056
2971
  const applies = boundActionTypes2(name, def);
@@ -3110,7 +3025,7 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
3110
3025
  return async () => ({
3111
3026
  content: [{
3112
3027
  type: "text",
3113
- text: `Refused: "${tool.namespacedName}" talks to a real system, but this gateway is running in simulation mode (real systems are off). No ticket was requested.`
3028
+ text: `Refused: "${tool.namespacedName}" is not available. No ticket was requested.`
3114
3029
  }],
3115
3030
  isError: true
3116
3031
  });
@@ -3131,6 +3046,17 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
3131
3046
  };
3132
3047
  };
3133
3048
  }
3049
+ function isStaleMandateRefusal(err) {
3050
+ const errors = err.body?.errors;
3051
+ const code = errors?.[0]?.code;
3052
+ if (err.statusCode === 403 && (code === "ATTESTATION_REVOKED" || code === "ATTESTATION_EXPIRED")) {
3053
+ return true;
3054
+ }
3055
+ if (err.statusCode === 404 && code === "ATTESTATION_NOT_FOUND") {
3056
+ return true;
3057
+ }
3058
+ return false;
3059
+ }
3134
3060
  function createGatedToolHandlerInner(tool, integrationManager2, state2) {
3135
3061
  if (!tool.gating || !tool.gating.profile) {
3136
3062
  return async () => {
@@ -3202,7 +3128,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
3202
3128
  );
3203
3129
  }
3204
3130
  }
3205
- const readProfile = readAdapter ? getProfile3(matchingAuths[0].profileId) : void 0;
3131
+ const readProfile = readAdapter ? getProfile2(matchingAuths[0].profileId) : void 0;
3206
3132
  const boundsSchema = readProfile?.boundsSchema;
3207
3133
  const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
3208
3134
  const hasAgeDimension = Boolean(readAdapter?.ageField);
@@ -3358,7 +3284,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
3358
3284
  };
3359
3285
  }
3360
3286
  {
3361
- const profileDef = getProfile3(profile);
3287
+ const profileDef = getProfile2(profile);
3362
3288
  const registry = profileDef?.boundsSchema?.actionTypes;
3363
3289
  if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
3364
3290
  return {
@@ -3415,23 +3341,24 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
3415
3341
  isError: true
3416
3342
  };
3417
3343
  }
3418
- const contextKeys = getProfile3(passers[0].profileId)?.contextSchema?.keyOrder ?? [];
3419
- const selection = selectAuthorization(
3420
- contextKeys,
3421
- passers.map((a) => ({
3422
- id: a.authorizationId,
3423
- auth: a,
3424
- context: a.context ?? {},
3425
- requiresApproval: (a.deferredCommitmentDomains ?? []).length > 0
3426
- }))
3427
- );
3428
- const auth = selection.chosen.auth;
3429
- if (selection.superseded.length > 0) {
3430
- console.error(
3431
- `[Suveren MCP] selection(${tool.namespacedName}): chose ${auth.authorizationId} (${selection.reason}) over [${selection.superseded.map((s) => s.id).join(", ")}]`
3344
+ const contextKeys = getProfile2(passers[0].profileId)?.contextSchema?.keyOrder ?? [];
3345
+ return attemptWithCandidates(passers);
3346
+ async function attemptWithCandidates(candidates) {
3347
+ const selection = selectAuthorization(
3348
+ contextKeys,
3349
+ candidates.map((a) => ({
3350
+ id: a.authorizationId,
3351
+ auth: a,
3352
+ context: a.context ?? {},
3353
+ requiresApproval: (a.deferredCommitmentDomains ?? []).length > 0
3354
+ }))
3432
3355
  );
3433
- }
3434
- {
3356
+ const auth = selection.chosen.auth;
3357
+ if (selection.superseded.length > 0) {
3358
+ console.error(
3359
+ `[Suveren MCP] selection(${tool.namespacedName}): chose ${auth.authorizationId} (${selection.reason}) over [${selection.superseded.map((s) => s.id).join(", ")}]`
3360
+ );
3361
+ }
3435
3362
  const authzId = auth.authorizationId;
3436
3363
  if (isCommitmentDowngrade(auth)) {
3437
3364
  return {
@@ -3544,6 +3471,24 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
3544
3471
  isError: true
3545
3472
  };
3546
3473
  }
3474
+ if (err instanceof SPReceiptError && isStaleMandateRefusal(err)) {
3475
+ state2.cache.invalidate(auth.authorizationId);
3476
+ const code = err.body?.errors?.[0]?.code ?? "unknown";
3477
+ const remaining = candidates.filter((c) => c.authorizationId !== auth.authorizationId);
3478
+ if (remaining.length === 0) {
3479
+ console.error(
3480
+ `[Suveren MCP] fallback(${tool.namespacedName}): mandate ${auth.authorizationId} invalid (${code}) \u2014 no remaining candidates`
3481
+ );
3482
+ return {
3483
+ content: [{ type: "text", text: `Blocked by SP: ${err.message}` }],
3484
+ isError: true
3485
+ };
3486
+ }
3487
+ console.error(
3488
+ `[Suveren MCP] fallback(${tool.namespacedName}): mandate ${auth.authorizationId} invalid (${code}) \u2192 retrying with [${remaining.map((c) => c.authorizationId).join(", ")}]`
3489
+ );
3490
+ return attemptWithCandidates(remaining);
3491
+ }
3547
3492
  if (err instanceof SPReceiptError && err.statusCode === 409) {
3548
3493
  const spBody = err.body;
3549
3494
  let pendingApprovers = spBody.approvers ?? [];
@@ -3700,7 +3645,106 @@ function buildProxiedToolDescription(tool, state2) {
3700
3645
  return `[Suveren: ${profile} \u2014 ${tag}] ${tool.description}`;
3701
3646
  }
3702
3647
 
3648
+ // src/lib/agent-visibility.ts
3649
+ function agentVisibleAuthorizations(authorizations, integrationManager2) {
3650
+ if (!isSimulationMode()) return authorizations;
3651
+ if (!integrationManager2) return [];
3652
+ const runningProfiles = /* @__PURE__ */ new Set();
3653
+ for (const tool of integrationManager2.getAllTools()) {
3654
+ const profile = tool.gating?.profile;
3655
+ if (profile) runningProfiles.add(profile);
3656
+ }
3657
+ if (runningProfiles.size === 0) return [];
3658
+ return authorizations.filter(
3659
+ (auth) => [...runningProfiles].some((profile) => profileMatches(auth.profileId, profile))
3660
+ );
3661
+ }
3662
+
3663
+ // src/lib/mandate-brief.ts
3664
+ function shortProfileName(profileId) {
3665
+ const withoutVersion = profileId.replace(/@.*$/, "");
3666
+ const parts = withoutVersion.split("/");
3667
+ return parts[parts.length - 1];
3668
+ }
3669
+ function countToolsByGating(profileId, integrationManager2) {
3670
+ if (!integrationManager2) return { gated: 0, readOnly: 0 };
3671
+ const allTools = integrationManager2.getAllTools();
3672
+ let gated = 0;
3673
+ let readOnly = 0;
3674
+ for (const tool of allTools) {
3675
+ if (!tool.gating || !tool.gating.profile) {
3676
+ readOnly++;
3677
+ } else if (tool.gating.profile === profileId || tool.gating.profile === shortProfileName(profileId)) {
3678
+ if (tool.gating.staticExecution?.action_type === "read" && Object.keys(tool.gating.executionMapping ?? {}).length === 0) {
3679
+ readOnly++;
3680
+ } else {
3681
+ gated++;
3682
+ }
3683
+ }
3684
+ }
3685
+ return { gated, readOnly };
3686
+ }
3687
+ function buildMandateBrief(opts) {
3688
+ const { executionLog, integrationManager: integrationManager2, contextDir } = opts;
3689
+ const authorizations = agentVisibleAuthorizations(opts.authorizations, integrationManager2);
3690
+ const lines = [
3691
+ "You are connected to Suveren \u2014 the gateway that gates every privileged tool call you make.",
3692
+ "Suveren implements the bounded-authority model from the open Human Agency Protocol (HAP).",
3693
+ "You have bounded authorities granted by human decision owners.",
3694
+ "You MUST stay within these bounds \u2014 the Gatekeeper will reject actions that exceed them."
3695
+ ];
3696
+ const { brief: contextBrief } = getContextForBrief(contextDir);
3697
+ if (contextBrief) {
3698
+ lines.push("");
3699
+ lines.push("=== CONTEXT ===");
3700
+ lines.push("");
3701
+ lines.push(contextBrief);
3702
+ }
3703
+ const active = authorizations.filter((a) => a.complete);
3704
+ const pending = authorizations.filter((a) => !a.complete);
3705
+ const now = Math.floor(Date.now() / 1e3);
3706
+ if (active.length > 0) {
3707
+ lines.push("");
3708
+ lines.push("=== ACTIVE AUTHORITIES ===");
3709
+ lines.push("");
3710
+ for (const auth of active) {
3711
+ const earliestExpiry = Math.min(...auth.attestations.map((a) => a.expiresAt));
3712
+ const remainingMin = Math.max(0, Math.round((earliestExpiry - now) / 60));
3713
+ const shortName = shortProfileName(auth.profileId);
3714
+ const version = auth.profileId.match(/@(.+)$/)?.[1] ?? "";
3715
+ const shortId = version ? `${shortName}@${version}` : shortName;
3716
+ const boundsDesc = Object.entries(auth.frame).filter(([key]) => key !== "profile" && key !== "path").map(([key, value]) => `${key}:${value}`).join(" \xB7 ");
3717
+ const parts = [];
3718
+ if (boundsDesc) parts.push(boundsDesc);
3719
+ if (executionLog) {
3720
+ const profile = getProfile3(auth.profileId) ?? getProfile3(shortName);
3721
+ const consumption = getConsumptionState(auth, executionLog, profile);
3722
+ const compact = formatConsumptionCompact(consumption);
3723
+ if (compact) parts.push(compact);
3724
+ }
3725
+ const { gated, readOnly } = countToolsByGating(auth.profileId, integrationManager2);
3726
+ if (gated > 0 || readOnly > 0) parts.push(`${gated} gated tools`);
3727
+ parts.push(`${remainingMin} min remaining`);
3728
+ lines.push(`[${shortId}] ${parts.join(" \xB7 ")}`);
3729
+ lines.push(` \u2192 list-authorizations(domain: "${shortName}") for intent & details`);
3730
+ lines.push("");
3731
+ }
3732
+ }
3733
+ if (pending.length > 0) {
3734
+ lines.push("=== PENDING (awaiting attestations) ===");
3735
+ lines.push("");
3736
+ for (const auth of pending) {
3737
+ const missing = auth.requiredDomains.filter((d) => !auth.attestedDomains.includes(d));
3738
+ lines.push(`[${auth.path}] ${auth.profileId} \u2014 needs: ${missing.join(", ")}`);
3739
+ }
3740
+ lines.push("");
3741
+ }
3742
+ lines.push("When you receive a task, call list-authorizations(domain) to load full details for the relevant domain.");
3743
+ return lines.join("\n");
3744
+ }
3745
+
3703
3746
  // src/tools/authorizations.ts
3747
+ import { getProfile as getProfile4 } from "@hap/core";
3704
3748
  function shortProfileName2(profileId) {
3705
3749
  const withoutVersion = profileId.replace(/@.*$/, "");
3706
3750
  const parts = withoutVersion.split("/");
@@ -3761,7 +3805,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
3761
3805
  if (!state2.spClient.isUnlocked()) {
3762
3806
  return { content: [{ type: "text", text: lockedNotice("list authorizations", state2.spClient.getLockReason() ?? "restart") }] };
3763
3807
  }
3764
- const authorizations = state2.getEnrichedAuthorizations();
3808
+ const authorizations = agentVisibleAuthorizations(state2.getEnrichedAuthorizations(), integrationManager2);
3765
3809
  const now = Math.floor(Date.now() / 1e3);
3766
3810
  const domain = args?.domain;
3767
3811
  if (authorizations.length === 0) {
@@ -205,10 +205,29 @@ type BoundType =
205
205
  * Per-transaction cap. The execution context field named in `of` must
206
206
  * satisfy `execution[of] <= bound` for the current call. No cumulative
207
207
  * tracking. Used by: amount_max, recipient_max, booking_duration_max, etc.
208
+ *
209
+ * `requiredFor` (v0.8+) closes the converse: without it, a call that does
210
+ * not carry `of` at all is simply skipped — a 5,000 cap refuses 6,000 but
211
+ * permits a call that declares no value whatsoever, which is indistinguishable
212
+ * from an unenforced bound. The same hole, and the same fix, as
213
+ * `FieldConstraint.requiredFor` on a scope constraint (below): values drawn
214
+ * from the profile's `boundsSchema.actionTypes` registry. For a listed
215
+ * `action_type`, an execution whose context lacks `of` — or whose value is
216
+ * not a finite number — MUST be refused rather than skipped. For an
217
+ * unlisted (or when `requiredFor` is absent) action type, today's
218
+ * skip-on-absence behaviour is unchanged. Meaningful only on
219
+ * `per_transaction`; the other `BoundType` kinds have no per-call "value
220
+ * exposed or not" question — `cumulative_sum`/`cumulative_count` always read
221
+ * from the execution log regardless of what this call declares, and `enum`
222
+ * is a capability flag, not a runtime value — so the field does not exist
223
+ * on those variants and a profile that puts it there has it silently
224
+ * ignored by every enforcement point (TypeScript also refuses it when the
225
+ * profile is authored against this type rather than raw JSON).
208
226
  */
209
227
  {
210
228
  kind: 'per_transaction';
211
229
  of: string;
230
+ requiredFor?: string[];
212
231
  }
213
232
  /**
214
233
  * Cumulative sum within a time window. The SP maintains a running sum
@@ -503,6 +522,16 @@ interface AgentProfile {
503
522
  boundsSchema?: {
504
523
  keyOrder: string[];
505
524
  fields: Record<string, ProfileBoundsField>;
525
+ /**
526
+ * v0.5+ registry of the action types this profile recognizes (e.g.
527
+ * `["send", "delete", "setup"]`). Every `execution.action_type` the
528
+ * Authority Server and gateway accept for this profile MUST be a member
529
+ * when the registry is declared (protocol.md → Bounds Schema, rule 2);
530
+ * it is also what a `per_transaction` bound's `requiredFor` draws from.
531
+ * Absent on profiles published before the registry existed — membership
532
+ * is then uncheckable, not a violation.
533
+ */
534
+ actionTypes?: readonly string[];
506
535
  };
507
536
  /**
508
537
  * v0.4 context schema — defines the execution context parameters (e.g., currency, action_type).
@@ -983,6 +1012,25 @@ declare function validateContextParams(params: AgentContextParams, profile: Agen
983
1012
  valid: boolean;
984
1013
  errors: string[];
985
1014
  };
1015
+ /**
1016
+ * Authoring-time check for `per_transaction` bound `requiredFor` (v0.8+):
1017
+ * entries must name a real `action_type` from the profile's declared
1018
+ * `boundsSchema.actionTypes` registry, and the field is only meaningful on
1019
+ * `per_transaction` bounds (see `BoundType`'s doc comment for why the other
1020
+ * kinds don't carry it).
1021
+ *
1022
+ * This is NOT wired into `registerProfile` or any enforcement path — nothing
1023
+ * in hap-core validates profile JSON at load time today (the pre-existing
1024
+ * `requiredFor` on a context/scope constraint has the same gap), so adding
1025
+ * an automatic refusal here would be new behaviour beyond this change's
1026
+ * scope. Call it explicitly from an authoring tool, a hap-profiles CI check,
1027
+ * or a test, to catch a `requiredFor` entry that can never match (misspelled
1028
+ * action type) or is attached to a bound kind that silently ignores it.
1029
+ *
1030
+ * Returns an empty array when the profile has no boundsSchema or nothing to
1031
+ * check — never throws.
1032
+ */
1033
+ declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
986
1034
  /**
987
1035
  * Thrown when a value cannot be canonicalized at all — currently only for raw
988
1036
  * LF/CR inside a value. Carries the protocol error code so callers can map it
@@ -1353,4 +1401,4 @@ interface ReceiptPayload {
1353
1401
  */
1354
1402
  declare function verifyReceiptSignature(receipt: ReceiptPayload, publicKeyHex: string): Promise<void>;
1355
1403
 
1356
- export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, type SigningSurface, type Subject, type SubjectValidation, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
1404
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, type SigningSurface, type Subject, type SubjectValidation, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateBoundsRequiredFor, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
@@ -205,10 +205,29 @@ type BoundType =
205
205
  * Per-transaction cap. The execution context field named in `of` must
206
206
  * satisfy `execution[of] <= bound` for the current call. No cumulative
207
207
  * tracking. Used by: amount_max, recipient_max, booking_duration_max, etc.
208
+ *
209
+ * `requiredFor` (v0.8+) closes the converse: without it, a call that does
210
+ * not carry `of` at all is simply skipped — a 5,000 cap refuses 6,000 but
211
+ * permits a call that declares no value whatsoever, which is indistinguishable
212
+ * from an unenforced bound. The same hole, and the same fix, as
213
+ * `FieldConstraint.requiredFor` on a scope constraint (below): values drawn
214
+ * from the profile's `boundsSchema.actionTypes` registry. For a listed
215
+ * `action_type`, an execution whose context lacks `of` — or whose value is
216
+ * not a finite number — MUST be refused rather than skipped. For an
217
+ * unlisted (or when `requiredFor` is absent) action type, today's
218
+ * skip-on-absence behaviour is unchanged. Meaningful only on
219
+ * `per_transaction`; the other `BoundType` kinds have no per-call "value
220
+ * exposed or not" question — `cumulative_sum`/`cumulative_count` always read
221
+ * from the execution log regardless of what this call declares, and `enum`
222
+ * is a capability flag, not a runtime value — so the field does not exist
223
+ * on those variants and a profile that puts it there has it silently
224
+ * ignored by every enforcement point (TypeScript also refuses it when the
225
+ * profile is authored against this type rather than raw JSON).
208
226
  */
209
227
  {
210
228
  kind: 'per_transaction';
211
229
  of: string;
230
+ requiredFor?: string[];
212
231
  }
213
232
  /**
214
233
  * Cumulative sum within a time window. The SP maintains a running sum
@@ -503,6 +522,16 @@ interface AgentProfile {
503
522
  boundsSchema?: {
504
523
  keyOrder: string[];
505
524
  fields: Record<string, ProfileBoundsField>;
525
+ /**
526
+ * v0.5+ registry of the action types this profile recognizes (e.g.
527
+ * `["send", "delete", "setup"]`). Every `execution.action_type` the
528
+ * Authority Server and gateway accept for this profile MUST be a member
529
+ * when the registry is declared (protocol.md → Bounds Schema, rule 2);
530
+ * it is also what a `per_transaction` bound's `requiredFor` draws from.
531
+ * Absent on profiles published before the registry existed — membership
532
+ * is then uncheckable, not a violation.
533
+ */
534
+ actionTypes?: readonly string[];
506
535
  };
507
536
  /**
508
537
  * v0.4 context schema — defines the execution context parameters (e.g., currency, action_type).
@@ -983,6 +1012,25 @@ declare function validateContextParams(params: AgentContextParams, profile: Agen
983
1012
  valid: boolean;
984
1013
  errors: string[];
985
1014
  };
1015
+ /**
1016
+ * Authoring-time check for `per_transaction` bound `requiredFor` (v0.8+):
1017
+ * entries must name a real `action_type` from the profile's declared
1018
+ * `boundsSchema.actionTypes` registry, and the field is only meaningful on
1019
+ * `per_transaction` bounds (see `BoundType`'s doc comment for why the other
1020
+ * kinds don't carry it).
1021
+ *
1022
+ * This is NOT wired into `registerProfile` or any enforcement path — nothing
1023
+ * in hap-core validates profile JSON at load time today (the pre-existing
1024
+ * `requiredFor` on a context/scope constraint has the same gap), so adding
1025
+ * an automatic refusal here would be new behaviour beyond this change's
1026
+ * scope. Call it explicitly from an authoring tool, a hap-profiles CI check,
1027
+ * or a test, to catch a `requiredFor` entry that can never match (misspelled
1028
+ * action type) or is attached to a bound kind that silently ignores it.
1029
+ *
1030
+ * Returns an empty array when the profile has no boundsSchema or nothing to
1031
+ * check — never throws.
1032
+ */
1033
+ declare function validateBoundsRequiredFor(profile: AgentProfile): string[];
986
1034
  /**
987
1035
  * Thrown when a value cannot be canonicalized at all — currently only for raw
988
1036
  * LF/CR inside a value. Carries the protocol error code so callers can map it
@@ -1353,4 +1401,4 @@ interface ReceiptPayload {
1353
1401
  */
1354
1402
  declare function verifyReceiptSignature(receipt: ReceiptPayload, publicKeyHex: string): Promise<void>;
1355
1403
 
1356
- export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, type SigningSurface, type Subject, type SubjectValidation, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };
1404
+ export { type AgentBoundsParams, type AgentContextParams, type AgentFrameParams, type AgentProfile, type ApprovalObject, type Attestation, type AttestationHeader, type AttestationPayload, type BoundType, CanonicalValueError, type ContentBinding, ContentBindingError, type ContentBindingErrorCode, type CumulativeFieldDef, type CumulativeWindow, type DeclaredFieldDef, type ExecutionContextFieldDef, type ExecutionLogEntry, type ExecutionLogQuery, type ExecutionMappingTransform, type ExecutionMappingValue, type ExecutionPath, type FieldConstraint, type FieldUnit, type GateQuestion, type GatekeeperError, type GatekeeperRequest, type GatekeeperResult, type MandateBinding, MandateError, type MandateProjection, type OwnerMandate, type ProfileBoundsField, type ProfileContextField, type ProfileFrameField, type ProfileToolGating, type ProfileToolGatingEntry, type ReceiptPayload, type ResolvedDomain, type SigningSurface, type Subject, type SubjectValidation, approvalSigningBytes, attestationId, bindingAppliesTo, boundActionTypes, buildMandateProjection, canonicalBounds, canonicalContext, canonicalFrame, canonicalize, canonicalizeText, checkAttestationExpiry, clearProfiles, computeBoundsHash, computeContentHash, computeContextHash, computeFieldsContentHash, computeFrameHash, computeIntentDisclosureHash, computeIntentHash, contentCanonicalBytes, decodeAttestationBlob, decodeDidKey, deriveIdentityLine, encodeAttestationBlob, encodeDidKey, frameHash, getAllProfiles, getProfile, intentDisclosureCanonicalBytes, isFieldBinding, isKeyBearingDid, isV4Attestation, listProfiles, mandateSigningBytes, registerProfile, selectBoundFields, signApproval, signMandateProjection, validateBoundsParams, validateBoundsRequiredFor, validateContextParams, validateFrameParams, validateSubject, verify, verifyApproval, verifyAttestation, verifyAttestationSignature, verifyAttestationV4, verifyBoundsHash, verifyContextHash, verifyFrameHash, verifyOwnerMandate, verifyOwnerMandates, verifyReceiptSignature };