@aaes-ai/govern-sdk 0.1.3 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,8 +1,25 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.3 candidate
3
+ ## 0.2.0 (2026-10-22, prepared and tagged, not published)
4
4
 
5
- - The package name is `@aaes-ai/govern-sdk`. npm still lists `@aaes-ai/sdk` through 0.1.2 until the rename release.
5
+ Breaking changes (hence 0.2.0 under the SDK release policy):
6
+
7
+ - An omitted daemon spend cap now reads as `spendCapMinor: null` with `spendCapMinorPresent: false` on grants, presented grants and federated grants and scopes. 0.1.3 reported an omitted cap as `0`, which could not be told apart from an explicit zero cap. An explicit zero stays `0` with `spendCapMinorPresent: true`. The declarations now type the field as `number | null`.
8
+ - `action()` refuses before sending when the capability has no published risk tier for the caller, and asks the caller to refresh capabilities or request access. 0.1.3 sent such a request without a tier.
9
+ - Read filters accept `limit: 0`, which means the daemon default, as omitting `limit` does.
10
+
11
+ Fixes:
12
+
13
+ - Brokered and resumed brokered calls check the connector outcome. An allowed decision whose outcome is missing or malformed raises `AaesError` saying the effect may have run, instead of returning an empty outcome.
14
+ - A federated grant must name the requested intent, a marker receipt hash, and the requested capability (optionally pinned as `capability@version`) and task. The sealed record's intent id is no longer filled in from the request.
15
+ - The bearer token must be a string of printable ASCII characters. Payload numbers must be finite. Capability and catalogue entries must be JSON objects.
16
+ - Response bodies are released after a redirect refusal or a failed read.
17
+ - Wire types: receipts may have class `deferred`; model usage may carry `estimated: true`; execution steps accept `share` and `transfer`; the model list response types are added; license and usage summaries may carry `null` objects.
18
+
19
+ ## 0.1.3 (2026-10-18)
20
+
21
+ - Published on npm as `@aaes-ai/govern-sdk`. Earlier releases through 0.1.2 remain under `@aaes-ai/sdk`.
22
+ - This version was published by hand from a maintainer login at 15:34 UTC, as the package's required first publish, not by `sdk-publish.yml`. No `sdk/typescript/v0.1.3` tag or gated run exists for it, and it has no provenance. Its registry integrity (`sha512-iBcfEA4lYKhYJ47dx+ZtmGDJjqNp7oPAnPULzsoarP2mmU8tOmwKowifwV3VahlMFCx9aWpPgmNNv3uqoQcOaw==`) equals `npm pack` of commit `0193b9374`, the commit tagged `sdk/python/v0.1.2`.
6
23
  - Minor-unit amounts above `Number.MAX_SAFE_INTEGER` are refused before sending, since the caller's exact amount may already be lost as a double.
7
24
  - A daemon-published spend cap (`spend_cap_minor`, federated `spend_cap.minor`) must be a nonnegative safe integer and `latency_added_ms` a finite nonnegative number; malformed values raise `AaesError` instead of being coerced, matching the Python and Go clients. An omitted cap still reads as `0`.
8
25
 
@@ -12,7 +29,7 @@
12
29
 
13
30
  ## 0.1.1
14
31
 
15
- - Packaging-only patch, published through the sdk-publish.yml npm trusted publisher with provenance. No functional changes; the shipped files are unchanged apart from the version field.
32
+ - Packaging-only patch, published through the sdk-publish.yml npm trusted publisher without provenance (npm provenance requires a public source repository). No functional changes; the shipped files are unchanged apart from the version field.
16
33
  - Verified the 0.1.0 tarball already ships `README.md` (extracted by the registry), so no packaging change was required for npm's README rendering.
17
34
 
18
35
  ## 0.1.0 — candidate
package/README.md CHANGED
@@ -5,7 +5,8 @@ AAES (Autonomous Agentic Enterprise Systems) is an AI governance platform for th
5
5
  ES module package `@aaes-ai/govern-sdk`, dependency-free runtime, with complete public TypeScript declarations and wire response types. Node 18+.
6
6
 
7
7
  ```bash
8
- npm install ./sdk/typescript # install local candidate
8
+ npm install @aaes-ai/govern-sdk
9
+ # For local development: npm install ./sdk/typescript
9
10
  node sdk/typescript/aaes.test.mjs # node:test, nothing to install
10
11
  ```
11
12
 
@@ -46,9 +47,9 @@ Full guide: [docs/guides/INTEGRATION.md](../../docs/guides/INTEGRATION.md).
46
47
 
47
48
  A 503 response does not prove that the provider performed no action. Repeat only the identical request with its original effect key; never change the key to force a retry. The daemon retains the reservation and dispatch claim for uncertain outcomes and may answer the repeat with 409. It permits another dispatch only after proving that the previous attempt was never dispatched.
48
49
 
49
- ## Candidate package and expanded contract
50
+ ## Published package and expanded contract
50
51
 
51
- See [SDK release policy](../RELEASE.md) and [complete operation/scope table](../CONTRACT.md). npm lists `@aaes-ai/sdk` through 0.1.2. This tree's package name is `@aaes-ai/govern-sdk`.
52
+ See [SDK release policy](../RELEASE.md) and [complete operation/scope table](../CONTRACT.md). Version 0.1.3 was published on npm as `@aaes-ai/govern-sdk` on 18 October 2026, by a manual first publish rather than the tag workflow (see the changelog). Version 0.2.0 is prepared and tagged `sdk/typescript/v0.2.0`, but the tag workflow's publish run failed, so 0.2.0 is not on npm. Publishing it needs a re-run of that workflow on the tag, or a new version. Earlier releases through 0.1.2 remain under `@aaes-ai/sdk`. Check npm for later releases.
52
53
 
53
54
  Endpoint transport: bearer credentials require HTTPS. HTTP is accepted only
54
55
  for explicit loopback addresses (`localhost`, `127.0.0.0/8`, or `::1`); DNS
package/aaes.d.mts CHANGED
@@ -26,12 +26,12 @@ export class Refusal {
26
26
  export interface Grant {
27
27
  grantId: string; actorId: string; tenantId: string; capability: string; tier: string;
28
28
  taskId: string; issuedAt: string; expiresAt: string; ttlSeconds: number;
29
- spendCapMinor: number; spendCapCurrency: string; nonTransferable: boolean;
29
+ spendCapMinor: number | null; spendCapMinorPresent: boolean; spendCapCurrency: string; nonTransferable: boolean;
30
30
  budgetGrantId: string; issuer: string; signature: string;
31
31
  }
32
32
  export interface GrantPresent {
33
33
  grantId: string; actorId: string; tenantId: string; capability: string; tier: string;
34
- taskId: string; issuedAt: string; expiresAt: string; spendCapMinor: number; spendCapCurrency: string;
34
+ taskId: string; issuedAt: string; expiresAt: string; spendCapMinor: number | null; spendCapMinorPresent: boolean; spendCapCurrency: string;
35
35
  }
36
36
  export interface Receipt {
37
37
  receiptId: string; intentId: string; tenantId: string; taskId: string; approvalId: string;
@@ -117,12 +117,12 @@ export class FederatedMaterial {
117
117
  }
118
118
  export interface FederatedLocator { capability: string; endpoint: string; method: string; authority: string; resourceId: string }
119
119
  export interface FederatedRecord { intentId: string; mode: string; payloadSeen: boolean; markerReceiptHash: string; receiptId: string }
120
- export interface FederatedScope { capability: string; actions: string[]; resourceId: string; dataClass: string; tier: string; spendCapMinor: number; spendCapCurrency: string; scopeKey: string }
120
+ export interface FederatedScope { capability: string; actions: string[]; resourceId: string; dataClass: string; tier: string; spendCapMinor: number | null; spendCapMinorPresent: boolean; spendCapCurrency: string; scopeKey: string }
121
121
  export class FederatedGrant {
122
122
  constructor(fields?: Partial<FederatedGrant>);
123
123
  federationId: string; intentId: string; decisionGrantId: string; tenantId: string; actorId: string;
124
124
  capability: string; capabilityVersion: string; actions: string[]; resourceId: string; taskId: string;
125
- dataClass: string; tier: string; spendCapMinor: number; spendCapCurrency: string; scopeKey: string; backend: string;
125
+ dataClass: string; tier: string; spendCapMinor: number | null; spendCapMinorPresent: boolean; spendCapCurrency: string; scopeKey: string; backend: string;
126
126
  locator: FederatedLocator; issuedAt: string; expiresAt: string; ttlSeconds: number;
127
127
  material: FederatedMaterial; record: FederatedRecord;
128
128
  scope(): FederatedScope; toString(): string;
@@ -142,7 +142,7 @@ export interface TimeFilters extends PageFilters { since?: string; until?: strin
142
142
  export interface TaskFilters extends TimeFilters { state?: string; sponsor?: string; assignee?: string; overdue?: boolean }
143
143
  export interface ApprovalFilters extends TimeFilters { intentId?: string; taskId?: string; state?: 'pending' | 'all' }
144
144
  export interface EntitlementFilters extends PageFilters { actorId?: string; capability?: string; state?: 'all' | 'usable' | 'expired' | 'revoked' }
145
- export interface ReceiptFilters extends TimeFilters { intentId?: string; taskId?: string; effectClass?: 'denied' | 'minted_unused' | 'authorized' | 'executed'; custody?: string }
145
+ export interface ReceiptFilters extends TimeFilters { intentId?: string; taskId?: string; effectClass?: 'denied' | 'minted_unused' | 'authorized' | 'executed' | 'deferred'; custody?: string }
146
146
  export interface ManagerCardDecision { cardId: string; approvalId: string; payloadHash: string; policyId?: string; grant: boolean; reason?: string }
147
147
  export class Client {
148
148
  constructor(options?: ClientOptions);
package/aaes.mjs CHANGED
@@ -263,7 +263,8 @@ export class FederatedGrant {
263
263
  this.taskId = "";
264
264
  this.dataClass = "";
265
265
  this.tier = "";
266
- this.spendCapMinor = 0;
266
+ this.spendCapMinor = null;
267
+ this.spendCapMinorPresent = false;
267
268
  this.spendCapCurrency = "";
268
269
  this.scopeKey = "";
269
270
  this.backend = "";
@@ -286,6 +287,7 @@ export class FederatedGrant {
286
287
  dataClass: this.dataClass,
287
288
  tier: this.tier,
288
289
  spendCapMinor: this.spendCapMinor,
290
+ spendCapMinorPresent: this.spendCapMinorPresent,
289
291
  spendCapCurrency: this.spendCapCurrency,
290
292
  scopeKey: this.scopeKey,
291
293
  };
@@ -403,6 +405,9 @@ function scanPayload(value, depth, nodes) {
403
405
  if (depth > MAX_PAYLOAD_DEPTH) {
404
406
  throw new CallerRouteError(`aaes: payload nests deeper than ${MAX_PAYLOAD_DEPTH} levels; refusing`);
405
407
  }
408
+ if (typeof value === "number" && !Number.isFinite(value)) {
409
+ throw new CallerRouteError("aaes: payload numbers must be finite JSON values");
410
+ }
406
411
  if (Array.isArray(value)) {
407
412
  for (const child of value) scanPayload(child, depth + 1, nodes);
408
413
  return;
@@ -477,7 +482,14 @@ export class Client {
477
482
  if (base.includes("?") || base.includes("#")) {
478
483
  throw new AaesError("aaes: endpoint must not carry a query or fragment");
479
484
  }
480
- const secret = String(token ?? process.env[TOKEN_ENV] ?? "").trim();
485
+ const configured = token ?? process.env[TOKEN_ENV] ?? "";
486
+ if (typeof configured !== "string") {
487
+ throw new AaesError(`aaes: ${TOKEN_ENV} must be an ASCII bearer token`);
488
+ }
489
+ const secret = configured.trim();
490
+ if ([...secret].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) > 126)) {
491
+ throw new AaesError(`aaes: ${TOKEN_ENV} must not contain control or non-ASCII characters`);
492
+ }
481
493
  if (!secret) throw new AaesError(`aaes: a bearer token is required; pass token or set ${TOKEN_ENV}`);
482
494
  const doFetch = fetchImpl ?? globalThis.fetch;
483
495
  if (typeof doFetch !== "function") {
@@ -616,7 +628,7 @@ export class Client {
616
628
  // Connector observations can accompany a refusal after sealing or a
617
629
  // duplicate effect; they serve reconciliation, and the decision stays the
618
630
  // authority for what happened.
619
- return { decision, outcome: body.outcome ?? {} };
631
+ return { decision, outcome: brokeredOutcome(body, decision, PATH_BROKERED) };
620
632
  }
621
633
 
622
634
  /** POST /v1/brokered/resume: complete a deferred brokered action with the
@@ -642,7 +654,7 @@ export class Client {
642
654
  // Connector observations can accompany a refusal after sealing or a
643
655
  // duplicate effect; they serve reconciliation, and the decision stays the
644
656
  // authority for what happened.
645
- return { decision, outcome: body.outcome ?? {} };
657
+ return { decision, outcome: brokeredOutcome(body, decision, PATH_BROKERED_RESUME) };
646
658
  }
647
659
 
648
660
  /** POST /v1/grants/present: bind a stored grant to the authenticated caller.
@@ -745,6 +757,15 @@ export class Client {
745
757
  if (!grant.federationId || !grant.intentId) {
746
758
  throw new AaesError("aaes: the daemon returned a grant that names no federation id or intent id");
747
759
  }
760
+ if (String(grant.record.intentId ?? "").trim() !== String(grant.intentId ?? "").trim()) {
761
+ throw new AaesError("aaes: the daemon returned a grant whose sealed record does not name this intent");
762
+ }
763
+ if (!String(grant.record.markerReceiptHash ?? "").trim()) {
764
+ throw new AaesError("aaes: the daemon returned a federated grant with no marker receipt hash");
765
+ }
766
+ if (!federatedCapabilityMatches(capability, grant) || grant.taskId !== taskId) {
767
+ throw new AaesError("aaes: the daemon returned a grant whose capability or task is not the federated action requested");
768
+ }
748
769
  return { ok: true, allowed: true, reason: "", grant, refusal: null };
749
770
  }
750
771
 
@@ -913,6 +934,7 @@ export class Client {
913
934
  refuseUnknownKeys(ACTION_KEYS, action, "action");
914
935
  const capability = String(action.capability ?? "").trim();
915
936
  if (!capability) throw new AaesError("aaes: an action names a capability; none was given");
937
+ const amountMinor = requireMinorAmount(action.amountMinor, "amountMinor");
916
938
  const payload = guardPayload(action.payload);
917
939
  const entitled = await this.entitlement();
918
940
  const info = {
@@ -939,19 +961,22 @@ export class Client {
939
961
  external_refs: Array.isArray(action.externalRefs) && action.externalRefs.length
940
962
  ? action.externalRefs
941
963
  : undefined,
942
- amount_minor: requireMinorAmount(action.amountMinor, "amountMinor"),
964
+ amount_minor: amountMinor,
943
965
  amount_currency: String(action.amountCurrency ?? "").trim().toUpperCase() || undefined,
944
966
  grant_ttl_ms: action.grantTtlMs,
945
967
  payload,
946
968
  });
947
969
  const entry = entitled.find(capability);
948
- if (entry) {
949
- // The tier the daemon published for this caller. A caller cannot raise its
950
- // own tier, and the daemon refuses one below the registration.
951
- wire.tier = entry.riskTier;
952
- if (!wire.task_id) wire.task_id = entry.taskId;
953
- if (!wire.caller_task_id) wire.caller_task_id = entry.callerTaskId;
970
+ if (!entry || typeof entry.riskTier !== "string" || !entry.riskTier.trim()) {
971
+ throw new AaesError(
972
+ `aaes: no published risk tier for capability ${JSON.stringify(capability)}; refresh capabilities or request access before an action`,
973
+ );
954
974
  }
975
+ // The tier the daemon published for this caller. A caller cannot raise its
976
+ // own tier, and the daemon refuses one below the registration.
977
+ wire.tier = entry.riskTier;
978
+ if (!wire.task_id) wire.task_id = entry.taskId;
979
+ if (!wire.caller_task_id) wire.caller_task_id = entry.callerTaskId;
955
980
  return { wire: prune(wire), info };
956
981
  }
957
982
 
@@ -1126,6 +1151,7 @@ export class Client {
1126
1151
  throw new AaesError(`aaes: ${method} ${path}: ${err.message}`);
1127
1152
  }
1128
1153
  if (response.status >= 300 && response.status < 400) {
1154
+ await cancelBody(response);
1129
1155
  throw new AaesError(
1130
1156
  `aaes: refusing to follow a redirect to ${response.headers.get("location") ?? "another host"}: the daemon must answer at the configured endpoint`,
1131
1157
  );
@@ -1134,6 +1160,7 @@ export class Client {
1134
1160
  try {
1135
1161
  raw = await readBoundedResponse(response, method, path);
1136
1162
  } catch (err) {
1163
+ await cancelBody(response);
1137
1164
  // A body read fails the same way a failed fetch does: the read's own
1138
1165
  // refusals are already typed, and anything the stream or the abort
1139
1166
  // signal throws keeps its original error as the cause.
@@ -1173,6 +1200,35 @@ export class Client {
1173
1200
 
1174
1201
  // --- helpers ---------------------------------------------------------------
1175
1202
 
1203
+ function brokeredOutcome(body, decision, route) {
1204
+ const present = body !== null && typeof body === "object" && Object.prototype.hasOwnProperty.call(body, "outcome");
1205
+ const outcome = present ? body.outcome : undefined;
1206
+ if (outcome === undefined || outcome === null) {
1207
+ if (!decision.ok) return {};
1208
+ throw new AaesError(
1209
+ `aaes: POST ${route} was allowed and the connector may have executed the effect, but its outcome could not be decoded`,
1210
+ );
1211
+ }
1212
+ const typed = typeof outcome === "object" && !Array.isArray(outcome)
1213
+ && (!("executed" in outcome) || typeof outcome.executed === "boolean")
1214
+ && (!("rail_confirmed" in outcome) || typeof outcome.rail_confirmed === "boolean");
1215
+ if (typed) return outcome;
1216
+ if (decision.ok) {
1217
+ throw new AaesError(
1218
+ `aaes: POST ${route} was allowed and the connector may have executed the effect, but its outcome could not be decoded`,
1219
+ );
1220
+ }
1221
+ throw new AaesError(`aaes: POST ${route} outcome could not be decoded; the connector may have executed the effect`);
1222
+ }
1223
+
1224
+ async function cancelBody(response) {
1225
+ try {
1226
+ await response.body?.cancel();
1227
+ } catch {
1228
+ // The caller's error is the one that matters; this only releases the socket.
1229
+ }
1230
+ }
1231
+
1176
1232
  async function readBoundedResponse(response, method, path) {
1177
1233
  if (!response.body) return "";
1178
1234
  const reader = response.body.getReader();
@@ -1257,15 +1313,14 @@ function requireMinorAmount(value, field) {
1257
1313
  return amount;
1258
1314
  }
1259
1315
 
1260
- // minorUnitsFrom reads a daemon-published minor-unit cap. Omitted stays the
1261
- // documented zero (the daemon omits a zero cap); a present value must be a
1262
- // nonnegative safe JSON integer, matching the Python and Go clients.
1316
+ // minorUnitsFrom reads a daemon-published minor-unit cap. An omitted cap is
1317
+ // unknown (present false, value null). An explicit zero stays zero and present.
1263
1318
  function minorUnitsFrom(value, field) {
1264
- if (value === undefined) return 0;
1319
+ if (value === undefined) return { value: null, present: false };
1265
1320
  if (!Number.isSafeInteger(value) || value < 0) {
1266
1321
  throw new AaesError("aaes: " + field + " is not a nonnegative JSON integer");
1267
1322
  }
1268
- return value;
1323
+ return { value, present: true };
1269
1324
  }
1270
1325
 
1271
1326
  // finiteNumberFrom reads a daemon-published nonnegative number such as
@@ -1288,6 +1343,23 @@ function prune(value) {
1288
1343
  return out;
1289
1344
  }
1290
1345
 
1346
+ // The grant names the canonical capability and version separately; its locator
1347
+ // names that canonical id. A request may pin the pair as canonical@version.
1348
+ // Exact equality wins, so a capability id that itself contains "@" stays the
1349
+ // id; only then does the last "@" start a pin.
1350
+ function federatedCapabilityMatches(requested, grant) {
1351
+ const canonical = String(grant.capability ?? "").trim();
1352
+ const version = String(grant.capabilityVersion ?? "").trim();
1353
+ const locator = String(grant.locator?.capability ?? "").trim();
1354
+ if (!canonical || locator !== canonical) return false;
1355
+ if (requested === canonical) return true;
1356
+ const at = requested.lastIndexOf("@");
1357
+ if (at <= 0 || at === requested.length - 1) return false;
1358
+ const base = requested.slice(0, at).trim();
1359
+ const pin = requested.slice(at + 1).trim();
1360
+ return base === canonical && pin !== "" && pin === version;
1361
+ }
1362
+
1291
1363
  function explicitCapabilityPin(spelling, entries) {
1292
1364
  if (!spelling.includes("@")) return false;
1293
1365
  for (const entry of entries) {
@@ -1329,6 +1401,9 @@ function findRequestable(entries, spelling) {
1329
1401
  }
1330
1402
 
1331
1403
  function capabilityFrom(entry) {
1404
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
1405
+ throw new AaesError("aaes: capability is not a JSON object");
1406
+ }
1332
1407
  const capabilityId = entry.capability_id ?? "";
1333
1408
  const version = entry.version ?? "";
1334
1409
  const pinned = Boolean(entry.pinned);
@@ -1356,6 +1431,9 @@ function capabilityFrom(entry) {
1356
1431
  }
1357
1432
 
1358
1433
  function catalogueEntryFrom(entry) {
1434
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
1435
+ throw new AaesError("aaes: catalogue entry is not a JSON object");
1436
+ }
1359
1437
  const policy = entry.policy ?? {};
1360
1438
  return {
1361
1439
  capabilityId: entry.capability_id ?? "",
@@ -1382,6 +1460,7 @@ function grantFrom(body) {
1382
1460
  if (body.non_transferable !== true) {
1383
1461
  throw new AaesError("aaes: grant non_transferable must be JSON true");
1384
1462
  }
1463
+ const spendCap = minorUnitsFrom(body.spend_cap_minor, "spend_cap_minor");
1385
1464
  return {
1386
1465
  grantId: body.grant_id ?? "",
1387
1466
  actorId: body.actor_id ?? "",
@@ -1392,7 +1471,8 @@ function grantFrom(body) {
1392
1471
  issuedAt: body.issued_at ?? "",
1393
1472
  expiresAt: body.expires_at ?? "",
1394
1473
  ttlSeconds: body.ttl_seconds ?? 0,
1395
- spendCapMinor: minorUnitsFrom(body.spend_cap_minor, "spend_cap_minor"),
1474
+ spendCapMinor: spendCap.value,
1475
+ spendCapMinorPresent: spendCap.present,
1396
1476
  spendCapCurrency: body.spend_cap_currency ?? "",
1397
1477
  nonTransferable: true,
1398
1478
  budgetGrantId: body.budget_grant_id ?? "",
@@ -1402,6 +1482,7 @@ function grantFrom(body) {
1402
1482
  }
1403
1483
 
1404
1484
  function grantPresentFrom(body) {
1485
+ const spendCap = minorUnitsFrom(body.spend_cap_minor, "spend_cap_minor");
1405
1486
  return {
1406
1487
  grantId: body.grant_id ?? "",
1407
1488
  actorId: body.actor_id ?? "",
@@ -1411,7 +1492,8 @@ function grantPresentFrom(body) {
1411
1492
  taskId: body.task_id ?? "",
1412
1493
  issuedAt: body.issued_at ?? "",
1413
1494
  expiresAt: body.expires_at ?? "",
1414
- spendCapMinor: minorUnitsFrom(body.spend_cap_minor, "spend_cap_minor"),
1495
+ spendCapMinor: spendCap.value,
1496
+ spendCapMinorPresent: spendCap.present,
1415
1497
  spendCapCurrency: body.spend_cap_currency ?? "",
1416
1498
  };
1417
1499
  }
@@ -1501,6 +1583,7 @@ function federatedMaterialFrom(body = {}) {
1501
1583
  function federatedGrantFrom(body = {}, intentId = "") {
1502
1584
  const locator = body.locator ?? {};
1503
1585
  const record = body.record ?? {};
1586
+ const spendCap = minorUnitsFrom((body.spend_cap ?? {}).minor, "spend_cap.minor");
1504
1587
  if (record.payload_seen !== true && record.payload_seen !== false) {
1505
1588
  throw new AaesError(
1506
1589
  "aaes: the daemon returned a grant whose record omits payload_seen or did not send a JSON boolean",
@@ -1519,7 +1602,8 @@ function federatedGrantFrom(body = {}, intentId = "") {
1519
1602
  taskId: body.task_id ?? "",
1520
1603
  dataClass: body.data_class ?? "",
1521
1604
  tier: body.tier ?? "",
1522
- spendCapMinor: minorUnitsFrom((body.spend_cap ?? {}).minor, "spend_cap.minor"),
1605
+ spendCapMinor: spendCap.value,
1606
+ spendCapMinorPresent: spendCap.present,
1523
1607
  spendCapCurrency: (body.spend_cap ?? {}).currency ?? "",
1524
1608
  scopeKey: body.scope_key ?? "",
1525
1609
  backend: body.backend ?? "",
@@ -1535,7 +1619,7 @@ function federatedGrantFrom(body = {}, intentId = "") {
1535
1619
  ttlSeconds: body.ttl_seconds ?? 0,
1536
1620
  material: federatedMaterialFrom(body.material ?? {}),
1537
1621
  record: {
1538
- intentId: record.intent_id ?? intentId ?? "",
1622
+ intentId: record.intent_id ?? "",
1539
1623
  mode: record.mode ?? "",
1540
1624
  payloadSeen: record.payload_seen,
1541
1625
  markerReceiptHash: record.marker_receipt_hash ?? "",
@@ -1627,7 +1711,8 @@ function readQuery(filters, mapping) {
1627
1711
  const query = new URLSearchParams();
1628
1712
  for (const [key, value] of Object.entries(filters)) {
1629
1713
  if (value === undefined || value === null || value === "") continue;
1630
- if (key === "limit" && (!Number.isInteger(value) || value < 1 || value > 200)) throw new AaesError("aaes: limit must be an integer between 1 and 200");
1714
+ if (key === "limit" && (!Number.isInteger(value) || value < 0 || value > 200)) throw new AaesError("aaes: limit must be an integer between 0 and 200; pass 0 or omit it for the daemon default");
1715
+ if (key === "limit" && value === 0) continue;
1631
1716
  if (key === "overdue" && typeof value !== "boolean") throw new AaesError("aaes: overdue must be boolean");
1632
1717
  if (key !== "limit" && key !== "overdue" && typeof value !== "string") throw new AaesError("aaes: read filters must be strings");
1633
1718
  query.set(mapping[key], String(value));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aaes-ai/govern-sdk",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript/JavaScript SDK for AAES — the AI governance platform for the action layer: permissions, human approvals, and spending limits on routed agent actions",
5
5
  "license": "LicenseRef-AAES-Proprietary",
6
6
  "keywords": ["aaes", "ai-governance", "agentic-ai", "agent-governance", "ai-agents", "agent-permissions", "human-approval", "spending-limits", "audit-trail"],
package/wire.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  // Wire response shapes from api/openapi.yaml. These retain daemon field names.
2
2
  export type ModelRequest = { "model": string; "stream"?: boolean; };
3
- export type ModelUsage = { "input_tokens": number; "output_tokens": number; "total_tokens": number; "settled_minor": number; "currency": string; };
3
+ export type ModelUsage = { "input_tokens": number; "output_tokens": number; "total_tokens": number; "settled_minor": number; "currency": string; "estimated"?: true; };
4
+ export type ModelListOpenAI = { "object": "list"; "data": Array<{ "id": string; "object": "model"; "created": number; "owned_by": "aaes-govern"; }>; };
5
+ export type ModelListAnthropic = { "data": Array<{ "type": "model"; "id": string; "display_name": string; "created_at": string; }>; "has_more": false; "first_id": string | null; "last_id": string | null; };
4
6
  export type ModelResponse = Record<string, unknown>;
5
7
  export type AnthropicError = { "type": "error"; "error": { "type": "authentication_error" | "permission_error" | "rate_limit_error" | "request_too_large" | "invalid_request_error" | "api_error"; "message": string; }; };
6
8
  export type OpenAIError = { "error": { "message": string; "type": "invalid_request_error" | "permission_error" | "insufficient_quota" | "api_error"; "param"?: string | null; "code": "invalid_api_key" | "govern_refused" | "govern_approval_required" | "insufficient_quota" | "request_too_large" | "invalid_request" | "upstream_error" | "govern_unavailable"; }; };
7
9
  export type ExecutionConstraints = { "business_id": string; "purpose": string; "configuration_hash": string; "policy_bundle_hash": string; "actor_ids": Array<string>; "steps": Array<ExecutionStep>; };
8
- export type ExecutionStep = { "step_id": string; "capability_hash": string; "capability_id": string; "version": string; "action": "read" | "write" | "create" | "update" | "delete" | "send" | "execute" | "pay" | "deploy" | "invoke" | "approve"; "resource_id": string; "endpoint": string; "method": string; "payload_hash": string; "amount": { "minor": number; "currency"?: string; }; "require_scoped_delegation"?: boolean; "require_human_approval"?: boolean; "prerequisites"?: Array<string>; };
10
+ export type ExecutionStep = { "step_id": string; "capability_hash": string; "capability_id": string; "version": string; "action": "read" | "write" | "create" | "update" | "delete" | "send" | "execute" | "pay" | "deploy" | "invoke" | "approve" | "share" | "transfer"; "resource_id": string; "endpoint": string; "method": string; "payload_hash": string; "amount": { "minor": number; "currency"?: string; }; "require_scoped_delegation"?: boolean; "require_human_approval"?: boolean; "prerequisites"?: Array<string>; };
9
11
  export type ExecutionContext = { "root_task_id": string; "constraints": ExecutionConstraints; "constraints_hash": string; "reservations": Record<string, unknown>; };
10
12
  export type Refusal = { "reason": string; "remedy"?: string; };
11
13
  export type IntegrationCredentialRef = { "path": string; "version"?: string; "key"?: string; };
@@ -16,8 +18,8 @@ export type IntegrationRotateResponse = { "surface": string; "from": string; "to
16
18
  export type IntegrationRevokeRequest = { "surface": string; "reason"?: string; };
17
19
  export type IntegrationRevokeResponse = { "surface": string; "at": string; "authority": string; "reason": string; "note": string; };
18
20
  export type MeteringError = { "error": string; };
19
- export type LicenseStatus = { "state": string; "label": string; "reason": string; "warning": boolean; "persistent": boolean; "governance": string; "checked_at": string; "license": Record<string, unknown>; "usage": Record<string, unknown>; };
20
- export type UsageSummary = { "period": Record<string, unknown>; "index": "ok" | "unknown"; "governed_decisions": number; "records": Record<string, unknown>; "dedupe": "full" | "partial"; "retention_inside_period": boolean; "term": Record<string, unknown>; };
21
+ export type LicenseStatus = { "state": string; "label": string; "reason": string; "warning": boolean; "persistent": boolean; "governance": string; "checked_at": string; "license": Record<string, unknown> | null; "usage": Record<string, unknown> | null; };
22
+ export type UsageSummary = { "period": Record<string, unknown>; "index": "ok" | "unknown"; "governed_decisions": number; "records": Record<string, unknown>; "dedupe": "full" | "partial"; "retention_inside_period": boolean; "term": Record<string, unknown> | null; };
21
23
  export type IntegrationOpsError = { "error": string; };
22
24
  export type ActionRequest = { "tenant_id": string; "actor_id": string; "caller_task_id"?: string; "task_id"?: string; "effect_key_step"?: string; "capability": string; "tier": "R0" | "R1" | "R2" | "R3" | "R4" | number; "action"?: string; "correlation_id"?: string; "parent_intent_id"?: string; "contract_id"?: string; "model"?: string; "amount_minor": number; "amount_currency"?: string; "grant_ttl_ms"?: number; "provenance_status"?: "asserted" | "unknown"; "external_refs"?: Array<ExternalRef>; "payload"?: unknown; };
23
25
  export type ExternalRef = { "registry": string; "kind": string; "value": string; "source": "registered"; };
@@ -72,5 +74,5 @@ export type Approval = { "approval_id": string; "tenant_id": string; "task_id"?:
72
74
  export type ApprovalPage = { "items": Array<Approval>; "page": PageInfo; };
73
75
  export type Entitlement = { "request_id": string; "tenant_id": string; "actor_id": string; "capability": string; "actions"?: Array<string>; "resource_id"?: string; "data_class"?: string; "tier": string; "budget"?: { "minor"?: number; "currency"?: string; }; "state": "usable" | "expired" | "revoked"; "expires_at": string; "granted_by"?: string; "revoked_at"?: string; "revoked_by"?: string; };
74
76
  export type EntitlementPage = { "items": Array<Entitlement>; "page": PageInfo; };
75
- export type ReceiptView = { "execution_state"?: "claimed" | "not_dispatched"; "receipt_id": string; "intent_id": string; "tenant_id": string; "task_id"?: string; "approval_id"?: string; "custody"?: string; "grant_id"?: string; "effect_id"?: string; "effect"?: string; "class": "denied" | "minted_unused" | "authorized" | "executed"; "record_hash": string; "issued_at": string; "rail_confirmed": boolean; "confirmed_at"?: string; "reported_by"?: string; "reported_at"?: string; "observed_at"?: string; "reported_outcome"?: string; "note"?: string; "receipt_hash"?: string; "policy_bundle_hash": string; "input_hash": string; "engine_version": string; "counts_toward_coverage": boolean; "result_mode"?: "digest" | "inline_bounded" | "rendezvous"; "payload_returned"?: true; "result_resource_id"?: string; "model_usage"?: ModelUsage; };
77
+ export type ReceiptView = { "execution_state"?: "claimed" | "not_dispatched"; "receipt_id": string; "intent_id": string; "tenant_id": string; "task_id"?: string; "approval_id"?: string; "custody"?: string; "grant_id"?: string; "effect_id"?: string; "effect"?: string; "class": "denied" | "minted_unused" | "authorized" | "executed" | "deferred"; "record_hash": string; "issued_at": string; "rail_confirmed": boolean; "confirmed_at"?: string; "reported_by"?: string; "reported_at"?: string; "observed_at"?: string; "reported_outcome"?: string; "note"?: string; "receipt_hash"?: string; "policy_bundle_hash": string; "input_hash": string; "engine_version": string; "counts_toward_coverage": boolean; "result_mode"?: "digest" | "inline_bounded" | "rendezvous"; "payload_returned"?: true; "result_resource_id"?: string; "model_usage"?: ModelUsage; };
76
78
  export type ReceiptPage = { "items": Array<ReceiptView>; "page": PageInfo; };