xytara 2.2.0 → 2.4.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/RELEASE_NOTES.md +16 -10
- package/lib/account_auth.js +347 -1
- package/lib/capability_registry.js +572 -0
- package/lib/command_flow.js +20 -1
- package/lib/commerce_authority.js +449 -0
- package/lib/commerce_client.js +38 -0
- package/lib/commerce_economics.js +2471 -1
- package/lib/commerce_identity.js +578 -0
- package/lib/commerce_runtime.js +4 -0
- package/lib/identity_auth.js +175 -0
- package/lib/operator_intelligence.js +90 -0
- package/lib/partner_intelligence.js +105 -0
- package/package.json +1 -1
- package/server.js +1308 -59
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
# xytara 2.
|
|
1
|
+
# xytara 2.4.0 Release Notes
|
|
2
2
|
|
|
3
|
-
`xytara` 2.
|
|
3
|
+
`xytara` 2.4.0 is the foundation-convergence line for runtime wallet, authority, identity, trust, registry, and participation.
|
|
4
4
|
|
|
5
5
|
Highlights:
|
|
6
6
|
|
|
7
7
|
- direct one-line machine execution with `xytara-run`
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
8
|
+
- first-class wallet / ledger / lifecycle / reserve / commit / reversal inspection
|
|
9
|
+
- first-class authority bindings with scoped and bounded contract enforcement
|
|
10
|
+
- first-class machine-identity bindings, review, rotation, and operator bundle surfaces
|
|
11
|
+
- explicit capability-registry, pricing-policy, quote-policy, trust-input, and trust-layer packages
|
|
12
|
+
- multi-operator and external network participation packages with bounded admission flows
|
|
13
|
+
- credits-first reusable spend posture with durable runtime economics state
|
|
14
|
+
- native settlement-aware runtime inspection and proof-compatible handoff into `xoonya`
|
|
15
|
+
- partner-ready onboarding, launch docs, and public example paths remain in place
|
|
15
16
|
|
|
16
17
|
Recommended first checks:
|
|
17
18
|
|
|
@@ -19,10 +20,15 @@ Recommended first checks:
|
|
|
19
20
|
2. `xytara-release --candidate --summary`
|
|
20
21
|
3. `npm run verify:release-candidate`
|
|
21
22
|
4. `node examples/partner_launch_walkthrough.js`
|
|
22
|
-
5. inspect `/v1/
|
|
23
|
+
5. inspect `/v1/economics/accounts/:account_id/wallet-ledger-bundle`
|
|
24
|
+
6. inspect `/v1/economics/accounts/:account_id/authority-bundle`
|
|
25
|
+
7. inspect `/v1/economics/accounts/:account_id/machine-identity-operator-bundle`
|
|
26
|
+
8. inspect `/v1/capability-registry/package`
|
|
27
|
+
9. inspect `/v1/economics/accounts/:account_id/network-participation-package`
|
|
23
28
|
|
|
24
29
|
Recommended first docs:
|
|
25
30
|
|
|
26
31
|
- `PROGRAM_COMPLETE_RELEASE.md`
|
|
32
|
+
- `FINAL_CONTRACT.md`
|
|
27
33
|
- `WHY_XYTARA_XOONYA.md`
|
|
28
34
|
- `PARTNER_READY_PATH.md`
|
package/lib/account_auth.js
CHANGED
|
@@ -44,6 +44,84 @@ function buildCredentialPublicView(credential, includeToken = false) {
|
|
|
44
44
|
return view;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function buildAuthorityBindingPublicView(binding, credential, includeToken = false) {
|
|
48
|
+
if (!binding) return null;
|
|
49
|
+
const view = {
|
|
50
|
+
binding_id: binding.binding_id,
|
|
51
|
+
account_id: binding.account_id,
|
|
52
|
+
status: binding.status,
|
|
53
|
+
authority_kind: binding.authority_kind,
|
|
54
|
+
spend_posture: binding.spend_posture,
|
|
55
|
+
scope: binding.scope,
|
|
56
|
+
authority_scope: ensureArray(binding.authority_scope),
|
|
57
|
+
allowed_consequence_families: ensureArray(binding.allowed_consequence_families),
|
|
58
|
+
consequence_bounds: binding.consequence_bounds || {
|
|
59
|
+
max_commit_units: null,
|
|
60
|
+
max_reserve_units: null,
|
|
61
|
+
max_reversal_units: null
|
|
62
|
+
},
|
|
63
|
+
agent_id: binding.agent_id,
|
|
64
|
+
budget_id: binding.budget_id,
|
|
65
|
+
pack_id: binding.pack_id || null,
|
|
66
|
+
entitlement_id: binding.entitlement_id || null,
|
|
67
|
+
credential_id: binding.credential_id || null,
|
|
68
|
+
issued_by: binding.issued_by || null,
|
|
69
|
+
labels: ensureArray(binding.labels),
|
|
70
|
+
expires_at_iso: binding.expires_at_iso || null,
|
|
71
|
+
rotated_from_binding_id: binding.rotated_from_binding_id || null,
|
|
72
|
+
replaced_by_binding_id: binding.replaced_by_binding_id || null,
|
|
73
|
+
created_at_iso: binding.created_at_iso,
|
|
74
|
+
updated_at_iso: binding.updated_at_iso,
|
|
75
|
+
revoked_at_iso: binding.revoked_at_iso || null,
|
|
76
|
+
revoke_reason: binding.revoke_reason || null,
|
|
77
|
+
spend_credential: buildCredentialPublicView(credential, includeToken)
|
|
78
|
+
};
|
|
79
|
+
return view;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildAuthorityBindingContractSummary(binding, credential) {
|
|
83
|
+
if (!binding) return null;
|
|
84
|
+
const publicView = buildAuthorityBindingPublicView(binding, credential, false);
|
|
85
|
+
return {
|
|
86
|
+
summary_version: "xytara-authority-binding-contract-summary-v1",
|
|
87
|
+
binding_id: binding.binding_id,
|
|
88
|
+
account_id: binding.account_id,
|
|
89
|
+
authority_kind: binding.authority_kind,
|
|
90
|
+
spend_posture: binding.spend_posture,
|
|
91
|
+
authority_scope: ensureArray(binding.authority_scope),
|
|
92
|
+
allowed_consequence_families: ensureArray(binding.allowed_consequence_families),
|
|
93
|
+
consequence_bounds: publicView.consequence_bounds,
|
|
94
|
+
linked_credential_id: binding.credential_id || null,
|
|
95
|
+
contract_state: binding.status === "active" ? "active_contract" : "inactive_contract",
|
|
96
|
+
linked_surfaces: {
|
|
97
|
+
binding_ref: `/v1/account-auth/authority-bindings/${encodeURIComponent(binding.binding_id)}`,
|
|
98
|
+
spend_credential_ref: binding.credential_id ? `/v1/account-auth/spend-credentials/${encodeURIComponent(binding.credential_id)}` : null
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildAuthorityBindingRotationSummary(binding, credential) {
|
|
104
|
+
if (!binding) return null;
|
|
105
|
+
const active = binding.status === "active";
|
|
106
|
+
return {
|
|
107
|
+
summary_version: "xytara-authority-binding-rotation-summary-v1",
|
|
108
|
+
binding_id: binding.binding_id,
|
|
109
|
+
account_id: binding.account_id,
|
|
110
|
+
status: binding.status,
|
|
111
|
+
renewable_state: active ? "renewable" : "inactive",
|
|
112
|
+
rotatable_state: active ? "rotatable" : "inactive",
|
|
113
|
+
expires_at_iso: binding.expires_at_iso || null,
|
|
114
|
+
rotated_from_binding_id: binding.rotated_from_binding_id || null,
|
|
115
|
+
replaced_by_binding_id: binding.replaced_by_binding_id || null,
|
|
116
|
+
linked_credential_id: binding.credential_id || null,
|
|
117
|
+
linked_surfaces: {
|
|
118
|
+
binding_ref: `/v1/account-auth/authority-bindings/${encodeURIComponent(binding.binding_id)}`,
|
|
119
|
+
contract_summary_ref: `/v1/account-auth/authority-bindings/${encodeURIComponent(binding.binding_id)}/contract-summary`,
|
|
120
|
+
spend_credential_ref: binding.credential_id ? `/v1/account-auth/spend-credentials/${encodeURIComponent(binding.credential_id)}` : null
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
47
125
|
function isCredentialExpired(credential) {
|
|
48
126
|
if (!credential || !credential.expires_at_iso) return false;
|
|
49
127
|
const expiresAt = Date.parse(credential.expires_at_iso);
|
|
@@ -84,6 +162,57 @@ function createSpendCredential(state, body) {
|
|
|
84
162
|
return buildCredentialPublicView(credential, true);
|
|
85
163
|
}
|
|
86
164
|
|
|
165
|
+
function createAuthorityBinding(state, body) {
|
|
166
|
+
const payload = ensureObject(body);
|
|
167
|
+
const bindingId = normalizeString(payload.binding_id, `authority_binding_${state.accountAuthorityBindings.size + 1}`);
|
|
168
|
+
const credential = createSpendCredential(state, {
|
|
169
|
+
credential_id: normalizeString(payload.credential_id, `spend_for_${bindingId}`),
|
|
170
|
+
bearer_token: normalizeString(payload.bearer_token, null),
|
|
171
|
+
account_id: normalizeString(payload.account_id, "acct_demo"),
|
|
172
|
+
agent_id: normalizeString(payload.agent_id, null),
|
|
173
|
+
budget_id: normalizeString(payload.budget_id, null),
|
|
174
|
+
pack_id: normalizeString(payload.pack_id, null),
|
|
175
|
+
entitlement_id: normalizeString(payload.entitlement_id, null),
|
|
176
|
+
scope: normalizeString(payload.scope, "runtime_spend"),
|
|
177
|
+
labels: ensureArray(payload.labels),
|
|
178
|
+
status: normalizeString(payload.status, "active"),
|
|
179
|
+
issued_by: normalizeString(payload.issued_by, "operator"),
|
|
180
|
+
expires_at_iso: normalizeString(payload.expires_at_iso, null)
|
|
181
|
+
});
|
|
182
|
+
const createdAtIso = nowIso();
|
|
183
|
+
const binding = {
|
|
184
|
+
binding_id: bindingId,
|
|
185
|
+
account_id: credential.account_id,
|
|
186
|
+
credential_id: credential.credential_id,
|
|
187
|
+
authority_kind: normalizeString(payload.authority_kind, "delegated_runtime_spend"),
|
|
188
|
+
spend_posture: credential.spend_posture,
|
|
189
|
+
scope: credential.scope,
|
|
190
|
+
authority_scope: ensureArray(payload.authority_scope).map((value) => String(value || "").trim()).filter(Boolean),
|
|
191
|
+
allowed_consequence_families: ensureArray(payload.allowed_consequence_families).map((value) => String(value || "").trim()).filter(Boolean),
|
|
192
|
+
consequence_bounds: {
|
|
193
|
+
max_commit_units: Number.isFinite(Number(payload.max_commit_units)) ? Number(payload.max_commit_units) : null,
|
|
194
|
+
max_reserve_units: Number.isFinite(Number(payload.max_reserve_units)) ? Number(payload.max_reserve_units) : null,
|
|
195
|
+
max_reversal_units: Number.isFinite(Number(payload.max_reversal_units)) ? Number(payload.max_reversal_units) : null
|
|
196
|
+
},
|
|
197
|
+
agent_id: credential.agent_id,
|
|
198
|
+
budget_id: credential.budget_id,
|
|
199
|
+
pack_id: credential.pack_id || null,
|
|
200
|
+
entitlement_id: credential.entitlement_id || null,
|
|
201
|
+
status: normalizeString(payload.status, "active"),
|
|
202
|
+
issued_by: normalizeString(payload.issued_by, "operator"),
|
|
203
|
+
labels: ensureArray(payload.labels),
|
|
204
|
+
expires_at_iso: credential.expires_at_iso,
|
|
205
|
+
rotated_from_binding_id: normalizeString(payload.rotated_from_binding_id, null),
|
|
206
|
+
replaced_by_binding_id: normalizeString(payload.replaced_by_binding_id, null),
|
|
207
|
+
created_at_iso: createdAtIso,
|
|
208
|
+
updated_at_iso: createdAtIso,
|
|
209
|
+
revoked_at_iso: null,
|
|
210
|
+
revoke_reason: null
|
|
211
|
+
};
|
|
212
|
+
state.accountAuthorityBindings.set(bindingId, binding);
|
|
213
|
+
return buildAuthorityBindingPublicView(binding, state.accountSpendCredentials.get(credential.credential_id), true);
|
|
214
|
+
}
|
|
215
|
+
|
|
87
216
|
function listSpendCredentials(state, filters) {
|
|
88
217
|
const payload = ensureObject(filters);
|
|
89
218
|
const accountId = normalizeString(payload.account_id, null);
|
|
@@ -102,6 +231,116 @@ function getSpendCredential(state, credentialId) {
|
|
|
102
231
|
return buildCredentialPublicView(state.accountSpendCredentials.get(credentialId), false);
|
|
103
232
|
}
|
|
104
233
|
|
|
234
|
+
function listAuthorityBindings(state, filters) {
|
|
235
|
+
const payload = ensureObject(filters);
|
|
236
|
+
const accountId = normalizeString(payload.account_id, null);
|
|
237
|
+
const status = normalizeString(payload.status, null);
|
|
238
|
+
return Array.from(state.accountAuthorityBindings.values())
|
|
239
|
+
.filter((binding) => {
|
|
240
|
+
if (accountId && binding.account_id !== accountId) return false;
|
|
241
|
+
if (status && binding.status !== status) return false;
|
|
242
|
+
return true;
|
|
243
|
+
})
|
|
244
|
+
.map((binding) => buildAuthorityBindingPublicView(binding, state.accountSpendCredentials.get(binding.credential_id), false));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getAuthorityBinding(state, bindingId) {
|
|
248
|
+
if (!bindingId || !state.accountAuthorityBindings.has(bindingId)) return null;
|
|
249
|
+
const binding = state.accountAuthorityBindings.get(bindingId);
|
|
250
|
+
return buildAuthorityBindingPublicView(binding, state.accountSpendCredentials.get(binding.credential_id), false);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getAuthorityBindingContractSummary(state, bindingId) {
|
|
254
|
+
if (!bindingId || !state.accountAuthorityBindings.has(bindingId)) return null;
|
|
255
|
+
const binding = state.accountAuthorityBindings.get(bindingId);
|
|
256
|
+
return buildAuthorityBindingContractSummary(binding, state.accountSpendCredentials.get(binding.credential_id));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getAuthorityBindingRotationSummary(state, bindingId) {
|
|
260
|
+
if (!bindingId || !state.accountAuthorityBindings.has(bindingId)) return null;
|
|
261
|
+
const binding = state.accountAuthorityBindings.get(bindingId);
|
|
262
|
+
return buildAuthorityBindingRotationSummary(binding, state.accountSpendCredentials.get(binding.credential_id));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function evaluateAuthorityBindingAction(binding, action, units) {
|
|
266
|
+
const target = binding && typeof binding === "object" ? binding : null;
|
|
267
|
+
const numericUnits = Number.isFinite(Number(units)) ? Number(units) : 0;
|
|
268
|
+
const allowedScopes = target ? ensureArray(target.authority_scope) : [];
|
|
269
|
+
const allowedFamilies = target ? ensureArray(target.allowed_consequence_families) : [];
|
|
270
|
+
const bounds = target && target.consequence_bounds ? target.consequence_bounds : {};
|
|
271
|
+
|
|
272
|
+
if (!target) {
|
|
273
|
+
return { ok: true };
|
|
274
|
+
}
|
|
275
|
+
if (target.status !== "active") {
|
|
276
|
+
return { ok: false, reason: "authority_binding_inactive", binding_id: target.binding_id };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const scopeMap = {
|
|
280
|
+
delegated_credit_spend: "runtime.execute",
|
|
281
|
+
reserve: "economics.reserve",
|
|
282
|
+
release: "economics.release",
|
|
283
|
+
reverse: "economics.reverse"
|
|
284
|
+
};
|
|
285
|
+
const familyMap = {
|
|
286
|
+
delegated_credit_spend: "commit",
|
|
287
|
+
reserve: "reserve",
|
|
288
|
+
release: "release",
|
|
289
|
+
reverse: "reverse"
|
|
290
|
+
};
|
|
291
|
+
const boundKeyMap = {
|
|
292
|
+
delegated_credit_spend: "max_commit_units",
|
|
293
|
+
reserve: "max_reserve_units",
|
|
294
|
+
reverse: "max_reversal_units"
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const requiredScope = scopeMap[action] || null;
|
|
298
|
+
if (requiredScope && allowedScopes.length > 0 && !allowedScopes.includes(requiredScope)) {
|
|
299
|
+
return {
|
|
300
|
+
ok: false,
|
|
301
|
+
reason: "authority_scope_not_allowed",
|
|
302
|
+
binding_id: target.binding_id,
|
|
303
|
+
required_scope: requiredScope,
|
|
304
|
+
authority_scope: allowedScopes
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const requiredFamily = familyMap[action] || null;
|
|
309
|
+
if (requiredFamily && allowedFamilies.length > 0 && !allowedFamilies.includes(requiredFamily)) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
reason: "authority_consequence_family_not_allowed",
|
|
313
|
+
binding_id: target.binding_id,
|
|
314
|
+
required_consequence_family: requiredFamily,
|
|
315
|
+
allowed_consequence_families: allowedFamilies
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const boundKey = boundKeyMap[action] || null;
|
|
320
|
+
if (boundKey && Number.isFinite(Number(bounds[boundKey])) && numericUnits > Number(bounds[boundKey])) {
|
|
321
|
+
return {
|
|
322
|
+
ok: false,
|
|
323
|
+
reason: "authority_consequence_bound_exceeded",
|
|
324
|
+
binding_id: target.binding_id,
|
|
325
|
+
bound_key: boundKey,
|
|
326
|
+
max_units: Number(bounds[boundKey]),
|
|
327
|
+
attempted_units: numericUnits
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
ok: true,
|
|
333
|
+
binding_id: target.binding_id,
|
|
334
|
+
authority_scope: allowedScopes,
|
|
335
|
+
allowed_consequence_families: allowedFamilies,
|
|
336
|
+
consequence_bounds: {
|
|
337
|
+
max_commit_units: Number.isFinite(Number(bounds.max_commit_units)) ? Number(bounds.max_commit_units) : null,
|
|
338
|
+
max_reserve_units: Number.isFinite(Number(bounds.max_reserve_units)) ? Number(bounds.max_reserve_units) : null,
|
|
339
|
+
max_reversal_units: Number.isFinite(Number(bounds.max_reversal_units)) ? Number(bounds.max_reversal_units) : null
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
105
344
|
function revokeSpendCredential(state, credentialId, body) {
|
|
106
345
|
if (!credentialId || !state.accountSpendCredentials.has(credentialId)) return null;
|
|
107
346
|
const payload = ensureObject(body);
|
|
@@ -117,6 +356,94 @@ function revokeSpendCredential(state, credentialId, body) {
|
|
|
117
356
|
return buildCredentialPublicView(next, false);
|
|
118
357
|
}
|
|
119
358
|
|
|
359
|
+
function revokeAuthorityBinding(state, bindingId, body) {
|
|
360
|
+
if (!bindingId || !state.accountAuthorityBindings.has(bindingId)) return null;
|
|
361
|
+
const payload = ensureObject(body);
|
|
362
|
+
const current = state.accountAuthorityBindings.get(bindingId);
|
|
363
|
+
const next = {
|
|
364
|
+
...current,
|
|
365
|
+
status: "revoked",
|
|
366
|
+
revoked_at_iso: nowIso(),
|
|
367
|
+
revoke_reason: normalizeString(payload.reason, "operator_revoked"),
|
|
368
|
+
updated_at_iso: nowIso()
|
|
369
|
+
};
|
|
370
|
+
state.accountAuthorityBindings.set(bindingId, next);
|
|
371
|
+
if (current.credential_id) {
|
|
372
|
+
revokeSpendCredential(state, current.credential_id, payload);
|
|
373
|
+
}
|
|
374
|
+
return buildAuthorityBindingPublicView(next, current.credential_id ? state.accountSpendCredentials.get(current.credential_id) : null, false);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function renewAuthorityBinding(state, bindingId, body) {
|
|
378
|
+
if (!bindingId || !state.accountAuthorityBindings.has(bindingId)) return null;
|
|
379
|
+
const payload = ensureObject(body);
|
|
380
|
+
const current = state.accountAuthorityBindings.get(bindingId);
|
|
381
|
+
const nextExpiresAtIso = normalizeString(payload.expires_at_iso, current.expires_at_iso || null);
|
|
382
|
+
const updatedAtIso = nowIso();
|
|
383
|
+
const next = {
|
|
384
|
+
...current,
|
|
385
|
+
expires_at_iso: nextExpiresAtIso,
|
|
386
|
+
updated_at_iso: updatedAtIso
|
|
387
|
+
};
|
|
388
|
+
state.accountAuthorityBindings.set(bindingId, next);
|
|
389
|
+
if (current.credential_id && state.accountSpendCredentials.has(current.credential_id)) {
|
|
390
|
+
const credential = state.accountSpendCredentials.get(current.credential_id);
|
|
391
|
+
state.accountSpendCredentials.set(current.credential_id, {
|
|
392
|
+
...credential,
|
|
393
|
+
expires_at_iso: nextExpiresAtIso,
|
|
394
|
+
updated_at_iso: updatedAtIso
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return buildAuthorityBindingPublicView(next, current.credential_id ? state.accountSpendCredentials.get(current.credential_id) : null, false);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function rotateAuthorityBinding(state, bindingId, body) {
|
|
401
|
+
if (!bindingId || !state.accountAuthorityBindings.has(bindingId)) return null;
|
|
402
|
+
const payload = ensureObject(body);
|
|
403
|
+
const current = state.accountAuthorityBindings.get(bindingId);
|
|
404
|
+
const currentCredential = current.credential_id ? state.accountSpendCredentials.get(current.credential_id) : null;
|
|
405
|
+
const successorBindingId = normalizeString(payload.successor_binding_id, `${bindingId}_rotated`);
|
|
406
|
+
const successorCredentialId = normalizeString(payload.successor_credential_id, current.credential_id ? `${current.credential_id}_rotated` : `spend_for_${successorBindingId}`);
|
|
407
|
+
const successor = createAuthorityBinding(state, {
|
|
408
|
+
binding_id: successorBindingId,
|
|
409
|
+
credential_id: successorCredentialId,
|
|
410
|
+
bearer_token: normalizeString(payload.bearer_token, null),
|
|
411
|
+
account_id: current.account_id,
|
|
412
|
+
agent_id: normalizeString(payload.agent_id, current.agent_id),
|
|
413
|
+
budget_id: normalizeString(payload.budget_id, current.budget_id),
|
|
414
|
+
pack_id: normalizeString(payload.pack_id, current.pack_id),
|
|
415
|
+
entitlement_id: normalizeString(payload.entitlement_id, current.entitlement_id),
|
|
416
|
+
scope: normalizeString(payload.scope, current.scope),
|
|
417
|
+
authority_kind: normalizeString(payload.authority_kind, current.authority_kind),
|
|
418
|
+
authority_scope: ensureArray(payload.authority_scope).length > 0 ? ensureArray(payload.authority_scope) : ensureArray(current.authority_scope),
|
|
419
|
+
allowed_consequence_families: ensureArray(payload.allowed_consequence_families).length > 0 ? ensureArray(payload.allowed_consequence_families) : ensureArray(current.allowed_consequence_families),
|
|
420
|
+
max_commit_units: Number.isFinite(Number(payload.max_commit_units)) ? Number(payload.max_commit_units) : current.consequence_bounds.max_commit_units,
|
|
421
|
+
max_reserve_units: Number.isFinite(Number(payload.max_reserve_units)) ? Number(payload.max_reserve_units) : current.consequence_bounds.max_reserve_units,
|
|
422
|
+
max_reversal_units: Number.isFinite(Number(payload.max_reversal_units)) ? Number(payload.max_reversal_units) : current.consequence_bounds.max_reversal_units,
|
|
423
|
+
labels: ensureArray(payload.labels).length > 0 ? ensureArray(payload.labels) : ensureArray(current.labels),
|
|
424
|
+
issued_by: normalizeString(payload.issued_by, current.issued_by || "operator"),
|
|
425
|
+
expires_at_iso: normalizeString(payload.expires_at_iso, current.expires_at_iso || (currentCredential ? currentCredential.expires_at_iso : null)),
|
|
426
|
+
rotated_from_binding_id: current.binding_id
|
|
427
|
+
});
|
|
428
|
+
const revoked = revokeAuthorityBinding(state, bindingId, {
|
|
429
|
+
reason: normalizeString(payload.reason, "rotated_to_successor")
|
|
430
|
+
});
|
|
431
|
+
const successorRecord = state.accountAuthorityBindings.get(successor.binding_id);
|
|
432
|
+
state.accountAuthorityBindings.set(successor.binding_id, {
|
|
433
|
+
...successorRecord,
|
|
434
|
+
rotated_from_binding_id: current.binding_id
|
|
435
|
+
});
|
|
436
|
+
state.accountAuthorityBindings.set(bindingId, {
|
|
437
|
+
...state.accountAuthorityBindings.get(bindingId),
|
|
438
|
+
replaced_by_binding_id: successor.binding_id
|
|
439
|
+
});
|
|
440
|
+
return {
|
|
441
|
+
rotated_binding: buildAuthorityBindingPublicView(state.accountAuthorityBindings.get(bindingId), current.credential_id ? state.accountSpendCredentials.get(current.credential_id) : null, false),
|
|
442
|
+
successor_authority_binding: buildAuthorityBindingPublicView(state.accountAuthorityBindings.get(successor.binding_id), state.accountSpendCredentials.get(successor.credential_id), true),
|
|
443
|
+
previous_view: revoked
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
120
447
|
function resolveSpendCredential(state, bearerToken) {
|
|
121
448
|
const token = normalizeString(bearerToken, null);
|
|
122
449
|
if (!token) {
|
|
@@ -147,26 +474,45 @@ function resolveSpendCredential(state, bearerToken) {
|
|
|
147
474
|
credential: buildCredentialPublicView(credential, false)
|
|
148
475
|
};
|
|
149
476
|
}
|
|
477
|
+
const binding = Array.from(state.accountAuthorityBindings.values()).find((entry) => entry && entry.credential_id === credentialId && entry.status === "active") || null;
|
|
150
478
|
return {
|
|
151
479
|
ok: true,
|
|
152
480
|
credential,
|
|
481
|
+
binding,
|
|
153
482
|
authorization_context: {
|
|
154
483
|
verification_mode: "delegated_spend_credential",
|
|
155
484
|
wallet_id: credential.credential_id,
|
|
156
485
|
payment_payload: {
|
|
486
|
+
binding_id: binding ? binding.binding_id : null,
|
|
157
487
|
credential_id: credential.credential_id,
|
|
158
488
|
spend_posture: credential.spend_posture,
|
|
159
489
|
pack_id: credential.pack_id || null,
|
|
160
|
-
entitlement_id: credential.entitlement_id || null
|
|
490
|
+
entitlement_id: credential.entitlement_id || null,
|
|
491
|
+
authority_scope: binding ? ensureArray(binding.authority_scope) : [],
|
|
492
|
+
allowed_consequence_families: binding ? ensureArray(binding.allowed_consequence_families) : [],
|
|
493
|
+
consequence_bounds: binding && binding.consequence_bounds ? binding.consequence_bounds : {
|
|
494
|
+
max_commit_units: null,
|
|
495
|
+
max_reserve_units: null,
|
|
496
|
+
max_reversal_units: null
|
|
497
|
+
}
|
|
161
498
|
}
|
|
162
499
|
}
|
|
163
500
|
};
|
|
164
501
|
}
|
|
165
502
|
|
|
166
503
|
module.exports = {
|
|
504
|
+
createAuthorityBinding,
|
|
167
505
|
createSpendCredential,
|
|
506
|
+
evaluateAuthorityBindingAction,
|
|
507
|
+
getAuthorityBinding,
|
|
508
|
+
getAuthorityBindingContractSummary,
|
|
509
|
+
getAuthorityBindingRotationSummary,
|
|
168
510
|
getSpendCredential,
|
|
511
|
+
listAuthorityBindings,
|
|
169
512
|
listSpendCredentials,
|
|
513
|
+
renewAuthorityBinding,
|
|
170
514
|
resolveSpendCredential,
|
|
515
|
+
rotateAuthorityBinding,
|
|
516
|
+
revokeAuthorityBinding,
|
|
171
517
|
revokeSpendCredential
|
|
172
518
|
};
|