@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 +20 -3
- package/README.md +4 -3
- package/aaes.d.mts +5 -5
- package/aaes.mjs +106 -21
- package/package.json +1 -1
- package/wire.d.ts +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.2.0 (2026-10-22, prepared and tagged, not published)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
50
|
+
## Published package and expanded contract
|
|
50
51
|
|
|
51
|
-
See [SDK release policy](../RELEASE.md) and [complete operation/scope table](../CONTRACT.md). npm
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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.
|
|
1261
|
-
//
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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 ??
|
|
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 <
|
|
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.
|
|
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
|
|
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; };
|