@agentunion/fastaun 0.5.18 → 0.5.20

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.
Files changed (122) hide show
  1. package/CHANGELOG.md +172 -79
  2. package/_packed_docs/CHANGELOG.md +172 -79
  3. package/_packed_docs/INDEX.md +244 -116
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
  5. package/_packed_docs/agent.md/SCHEMA.md +245 -205
  6. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
  7. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +42 -15
  8. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
  9. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
  10. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
  11. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
  12. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
  16. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
  17. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
  18. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  19. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
  20. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
  21. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  22. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
  23. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
  24. package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
  25. package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
  26. package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
  27. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
  28. package/_packed_docs/sdk/INDEX.md +105 -34
  29. package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
  30. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
  31. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
  32. package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
  33. package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +300 -0
  34. package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +382 -0
  35. package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
  36. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
  37. package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
  38. package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
  39. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
  40. package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
  41. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
  42. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
  43. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
  44. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
  45. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
  46. package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
  47. package/dist/agent-md-schema.d.ts +15 -1
  48. package/dist/agent-md-schema.js +87 -24
  49. package/dist/agent-md-schema.js.map +1 -1
  50. package/dist/agent-md.d.ts +54 -2
  51. package/dist/agent-md.js +637 -53
  52. package/dist/agent-md.js.map +1 -1
  53. package/dist/aid-store.d.ts +38 -59
  54. package/dist/aid-store.js +300 -265
  55. package/dist/aid-store.js.map +1 -1
  56. package/dist/aid.js +30 -11
  57. package/dist/aid.js.map +1 -1
  58. package/dist/auth.d.ts +6 -1
  59. package/dist/auth.js +500 -15
  60. package/dist/auth.js.map +1 -1
  61. package/dist/cert-utils.d.ts +44 -2
  62. package/dist/cert-utils.js +149 -49
  63. package/dist/cert-utils.js.map +1 -1
  64. package/dist/client/delivery.d.ts +11 -1
  65. package/dist/client/delivery.js +321 -62
  66. package/dist/client/delivery.js.map +1 -1
  67. package/dist/client/rpc-pipeline.d.ts +1 -0
  68. package/dist/client/rpc-pipeline.js +142 -82
  69. package/dist/client/rpc-pipeline.js.map +1 -1
  70. package/dist/client/v2-e2ee.d.ts +1 -0
  71. package/dist/client/v2-e2ee.js +166 -96
  72. package/dist/client/v2-e2ee.js.map +1 -1
  73. package/dist/client.d.ts +84 -1
  74. package/dist/client.js +780 -30
  75. package/dist/client.js.map +1 -1
  76. package/dist/errors.js +4 -3
  77. package/dist/errors.js.map +1 -1
  78. package/dist/events.d.ts +9 -3
  79. package/dist/events.js +69 -11
  80. package/dist/events.js.map +1 -1
  81. package/dist/facades.d.ts +83 -3
  82. package/dist/facades.js +100 -6
  83. package/dist/facades.js.map +1 -1
  84. package/dist/index.d.ts +9 -4
  85. package/dist/index.js +3 -2
  86. package/dist/index.js.map +1 -1
  87. package/dist/keystore/aid-db.d.ts +5 -0
  88. package/dist/keystore/aid-db.js +57 -0
  89. package/dist/keystore/aid-db.js.map +1 -1
  90. package/dist/keystore/index.d.ts +8 -0
  91. package/dist/keystore/local-identity-store.js +10 -3
  92. package/dist/keystore/local-identity-store.js.map +1 -1
  93. package/dist/keystore/local-token-store.d.ts +6 -1
  94. package/dist/keystore/local-token-store.js +24 -1
  95. package/dist/keystore/local-token-store.js.map +1 -1
  96. package/dist/org-protocol.d.ts +98 -0
  97. package/dist/org-protocol.js +1753 -0
  98. package/dist/org-protocol.js.map +1 -0
  99. package/dist/ownership-proof.d.ts +82 -5
  100. package/dist/ownership-proof.js +327 -31
  101. package/dist/ownership-proof.js.map +1 -1
  102. package/dist/ownership-v21.d.ts +196 -0
  103. package/dist/ownership-v21.js +712 -0
  104. package/dist/ownership-v21.js.map +1 -0
  105. package/dist/ownership-workflow.d.ts +87 -0
  106. package/dist/ownership-workflow.js +1182 -0
  107. package/dist/ownership-workflow.js.map +1 -0
  108. package/dist/seq-tracker.d.ts +1 -0
  109. package/dist/seq-tracker.js +10 -1
  110. package/dist/seq-tracker.js.map +1 -1
  111. package/dist/tools/cross-sdk-agent.d.ts +90 -1
  112. package/dist/tools/cross-sdk-agent.js +441 -7
  113. package/dist/tools/cross-sdk-agent.js.map +1 -1
  114. package/dist/transport.d.ts +8 -0
  115. package/dist/transport.js +118 -4
  116. package/dist/transport.js.map +1 -1
  117. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  118. package/dist/v2/e2ee/decrypt.js +9 -1
  119. package/dist/v2/e2ee/decrypt.js.map +1 -1
  120. package/dist/version.d.ts +1 -1
  121. package/dist/version.js +1 -1
  122. package/package.json +1 -1
@@ -0,0 +1,712 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { updateOwnershipAgentMd } from './agent-md.js';
3
+ import { parseFileFrontmatter, validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
4
+ import { certFingerprint, isStrictBase64, parseAgentMdTailSignature, publicKeyMatchesFingerprint, signDocument, verifyDocument } from './cert-utils.js';
5
+ import { StateError, ValidationError } from './errors.js';
6
+ const AID_RE = /^[a-z0-9_][a-z0-9_-]*(?:\.[a-z0-9_][a-z0-9_-]*)+$/;
7
+ export const OWNERSHIP_UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
8
+ const STATEMENT_KEYS = ['version', 'declaration_id', 'owner_aid', 'sub_aid', 'relation_version', 'declared_at'];
9
+ export function ownershipAid(value, field) {
10
+ if (typeof value !== 'string' || !AID_RE.test(value))
11
+ throw new ValidationError(`${field} must be a complete lowercase AID`);
12
+ return value;
13
+ }
14
+ export function ownershipInteger(value, field, min = 1) {
15
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < min)
16
+ throw new ValidationError(`${field} must be an integer in ${min}..9007199254740991`);
17
+ return value;
18
+ }
19
+ function mapping(value, field) {
20
+ if (!value || typeof value !== 'object' || Array.isArray(value))
21
+ throw new ValidationError(`${field} must be a mapping`);
22
+ return value;
23
+ }
24
+ function exactKeys(value, keys, field) {
25
+ if (Object.keys(value).some((key) => !keys.includes(key)) || keys.some((key) => !(key in value)))
26
+ throw new ValidationError(`${field} contains unknown or missing fields`);
27
+ }
28
+ export function validateOwnershipStatement(value) {
29
+ const statement = mapping(value, 'ownership.statement');
30
+ exactKeys(statement, STATEMENT_KEYS, 'ownership.statement');
31
+ if (statement.version !== 2)
32
+ throw new ValidationError('ownership.statement.version must be integer 2');
33
+ if (typeof statement.declaration_id !== 'string' || !OWNERSHIP_UUID_V4_RE.test(statement.declaration_id))
34
+ throw new ValidationError('declaration_id must be a lowercase UUIDv4');
35
+ ownershipAid(statement.owner_aid, 'owner_aid');
36
+ ownershipAid(statement.sub_aid, 'sub_aid');
37
+ ownershipInteger(statement.relation_version, 'relation_version');
38
+ ownershipInteger(statement.declared_at, 'declared_at');
39
+ return statement;
40
+ }
41
+ export function validateOwnershipAgentFrontmatter(meta, expectedAid) {
42
+ const aid = ownershipAid(meta.aid, 'aid');
43
+ if (expectedAid && expectedAid !== aid)
44
+ throw new ValidationError('agent.md AID mismatch');
45
+ if ('ownership_proof' in meta || 'ownership_release' in meta)
46
+ throw new ValidationError('legacy ownership fields cannot be used by Ownership 2.1');
47
+ if (meta.type === 'group' || meta.type === 'org') {
48
+ if ('ownership' in meta)
49
+ throw new ValidationError('group/org cannot declare Ownership 2.1', { stringCode: 'invalid' });
50
+ if ('owner' in meta)
51
+ ownershipAid(meta.owner, 'owner');
52
+ return undefined;
53
+ }
54
+ if (!('owner' in meta) && !('ownership' in meta))
55
+ return undefined;
56
+ if (meta.type === 'human')
57
+ throw new ValidationError('human cannot declare ownership');
58
+ const owner = ownershipAid(meta.owner, 'owner');
59
+ const ownership = mapping(meta.ownership, 'ownership');
60
+ exactKeys(ownership, ['statement'], 'agent.md ownership');
61
+ const statement = validateOwnershipStatement(ownership.statement);
62
+ if (statement.owner_aid !== owner || statement.sub_aid !== aid)
63
+ throw new ValidationError('agent.md ownership statement identity mismatch');
64
+ return statement;
65
+ }
66
+ function strictDerSignature(value) {
67
+ if (!isStrictBase64(value))
68
+ return false;
69
+ const bytes = Buffer.from(value, 'base64');
70
+ if (bytes.length < 8 || bytes.length > 72 || bytes[0] !== 0x30 || bytes[1] !== bytes.length - 2)
71
+ return false;
72
+ let offset = 2;
73
+ for (let i = 0; i < 2; i++) {
74
+ if (bytes[offset++] !== 0x02)
75
+ return false;
76
+ const length = bytes[offset++];
77
+ if (!length || length > 33 || offset + length > bytes.length || bytes[offset] & 0x80)
78
+ return false;
79
+ if (length > 1 && bytes[offset] === 0 && !(bytes[offset + 1] & 0x80))
80
+ return false;
81
+ if (bytes.subarray(offset, offset + length).every((byte) => byte === 0))
82
+ return false;
83
+ offset += length;
84
+ }
85
+ return offset === bytes.length;
86
+ }
87
+ function rawText(content) {
88
+ try {
89
+ const raw = typeof content === 'string' ? new TextEncoder().encode(content) : new Uint8Array(content);
90
+ const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw);
91
+ if (typeof content === 'string' && text !== content)
92
+ throw new Error('invalid Unicode');
93
+ return { raw, text };
94
+ }
95
+ catch {
96
+ throw new ValidationError('ownership file must be valid UTF-8');
97
+ }
98
+ }
99
+ export function parseOwnershipFile(content, role, context = {}) {
100
+ const { raw, text } = rawText(content);
101
+ const parsed = parseAgentMdTailSignature(text);
102
+ if (parsed.parseError)
103
+ throw new ValidationError(parsed.parseError);
104
+ if ((context.requireSignature ?? true) && !parsed.fields)
105
+ throw new ValidationError('ownership file signature is required');
106
+ if (new TextEncoder().encode(parsed.payload).length > 4096)
107
+ throw new ValidationError('ownership file payload exceeds 4096 bytes');
108
+ if (raw.length - new TextEncoder().encode(parsed.payload).length > 2048)
109
+ throw new ValidationError('ownership file signature exceeds 2048 bytes');
110
+ const fields = parsed.fields;
111
+ if (fields?.signer_aid !== undefined)
112
+ throw new ValidationError('ownership main signature contains unknown signer_aid');
113
+ for (const item of parsed.signatures) {
114
+ const fields = item.fields;
115
+ if (!/^sha256:[0-9a-f]{64}$/.test(fields.cert_fingerprint) || 'public_key_fingerprint' in fields && !/^sha256:[0-9a-f]{64}$/.test(fields.public_key_fingerprint))
116
+ throw new ValidationError('ownership file fingerprint is invalid');
117
+ if (!/^[1-9][0-9]*$/.test(fields.timestamp) || !Number.isSafeInteger(Number(fields.timestamp)))
118
+ throw new ValidationError('ownership file timestamp must be a positive integer');
119
+ if (!strictDerSignature(fields.signature))
120
+ throw new ValidationError('ownership file signature must be strict Base64 DER');
121
+ }
122
+ let frontmatter;
123
+ let statement;
124
+ let action;
125
+ let transfer;
126
+ if (role === 'agent') {
127
+ frontmatter = validateAgentMdDocument(parsed.payload, { expectedAid: context.expectedSubAid, validateOwnership: false, now: context.now }).frontmatter;
128
+ requireOwnershipApplicable(frontmatter.type);
129
+ statement = validateOwnershipAgentFrontmatter(frontmatter, context.expectedSubAid);
130
+ if (context.expectedOwnerAid && statement && statement.owner_aid !== context.expectedOwnerAid)
131
+ throw new ValidationError('agent.md owner mismatch');
132
+ }
133
+ else if (role === 'owner') {
134
+ frontmatter = parseFileFrontmatter(parsed.payload);
135
+ exactKeys(frontmatter, ['owner_aid', 'sub_aid', 'ownership'], 'owner.md');
136
+ const owner = ownershipAid(frontmatter.owner_aid, 'owner_aid'), sub = ownershipAid(frontmatter.sub_aid, 'sub_aid');
137
+ if (context.expectedOwnerAid && owner !== context.expectedOwnerAid || context.expectedSubAid && sub !== context.expectedSubAid)
138
+ throw new ValidationError('owner.md path identity mismatch');
139
+ const ownership = mapping(frontmatter.ownership, 'ownership');
140
+ action = ownership.action;
141
+ if (typeof action !== 'string' || !['declare', 'release', 'transfer_owner'].includes(action))
142
+ throw new ValidationError('owner.md action is invalid');
143
+ exactKeys(ownership, action === 'transfer_owner' ? ['statement', 'action', 'transfer'] : ['statement', 'action'], 'owner.md ownership');
144
+ statement = validateOwnershipStatement(ownership.statement);
145
+ if (statement.owner_aid !== owner || statement.sub_aid !== sub)
146
+ throw new ValidationError('owner.md ownership statement identity mismatch');
147
+ if (action === 'transfer_owner') {
148
+ const value = mapping(ownership.transfer, 'ownership.transfer');
149
+ exactKeys(value, ['new_owner_aid', 'target_relation_version', 'expires_at'], 'ownership.transfer');
150
+ if (ownershipAid(value.new_owner_aid, 'new_owner_aid') === owner)
151
+ throw new ValidationError('transfer target must differ from current owner');
152
+ if (ownershipInteger(value.target_relation_version, 'target_relation_version') !== statement.relation_version + 1)
153
+ throw new ValidationError('transfer target_relation_version mismatch');
154
+ ownershipInteger(value.expires_at, 'expires_at');
155
+ transfer = value;
156
+ }
157
+ }
158
+ else
159
+ throw new ValidationError('ownership file role is invalid');
160
+ return { raw, payload: parsed.payload, frontmatter, signature: fields, statement, action, transfer };
161
+ }
162
+ export function verifyOwnershipFile(content, role, certificate, context = {}) {
163
+ const parsed = parseOwnershipFile(content, role, { ...context, requireSignature: true });
164
+ const fields = parsed.signature;
165
+ const signer = role === 'agent' ? String(parsed.frontmatter.aid) : String(parsed.frontmatter.owner_aid);
166
+ const sub = role === 'agent' ? String(parsed.frontmatter.aid) : String(parsed.frontmatter.sub_aid);
167
+ const path = role === 'agent' ? 'agent.md' : `sub_aids/${sub}/owner.md`;
168
+ if (context.expectedTargetAid && context.expectedTargetAid !== sub || context.authenticatedAid && context.authenticatedAid !== signer || context.certificateAid && context.certificateAid !== signer)
169
+ throw new ValidationError('ownership target, authenticated AID or certificate AID mismatch');
170
+ if (context.resourcePath && context.resourcePath !== `${signer}:/${path}` || context.storagePath && context.storagePath !== path)
171
+ throw new ValidationError('ownership resource path identity mismatch');
172
+ const now = Math.trunc(context.now ?? Date.now() / 1000);
173
+ if (Number(fields.timestamp) > now + 300)
174
+ throw new ValidationError('ownership file timestamp is too far in the future');
175
+ if (certificate.chain_valid === false || certificate.revoked === true || typeof certificate.state === 'string' && certificate.state !== 'active_signing')
176
+ throw new ValidationError('ownership signing certificate is not currently verified and active_signing', { stringCode: 'invalid' });
177
+ if (certificate.chain_valid !== true || certificate.revoked !== false || certificate.revocation_checked !== true || certificate.state !== 'active_signing' || !Number.isSafeInteger(certificate.observed_at) || certificate.observed_at <= 0 || certificate.observed_at > now + 5 || certificate.observed_at < now - 30)
178
+ throw new StateError('ownership certificate current status is unavailable', { stringCode: 'unavailable', retryable: true });
179
+ if (typeof certificate.cert_pem !== 'string' || !certificate.cert_pem.trim())
180
+ throw ownershipFailure('unavailable', 'ownership signing certificate is missing', true);
181
+ try {
182
+ validateAgentMdCertificate(certificate.cert_pem, signer, Number(fields.timestamp), { now, requireCurrentValidity: true });
183
+ }
184
+ catch (error) {
185
+ if (error instanceof ValidationError)
186
+ throw error;
187
+ throw new ValidationError('ownership signing certificate PEM is invalid', { stringCode: 'invalid' });
188
+ }
189
+ if (certFingerprint(certificate.cert_pem) !== fields.cert_fingerprint)
190
+ throw new ValidationError('ownership certificate fingerprint mismatch');
191
+ if ('public_key_fingerprint' in fields && !publicKeyMatchesFingerprint(certificate.cert_pem, fields.public_key_fingerprint))
192
+ throw new ValidationError('ownership public key fingerprint mismatch');
193
+ const additional = new Map();
194
+ for (const item of parseAgentMdTailSignature(new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(parsed.raw)).signatures.slice(1)) {
195
+ const fields = item.fields, extra = context.certificateResolver?.(fields.signer_aid, fields.cert_fingerprint, Number(fields.timestamp));
196
+ if (extra && (extra.chain_valid === false || extra.revoked === true || typeof extra.state === 'string' && extra.state !== 'active_signing'))
197
+ throw new ValidationError('ownership additional signing certificate is not currently verified and active_signing', { stringCode: 'invalid' });
198
+ if (!extra || extra.chain_valid !== true || extra.revoked !== false || extra.revocation_checked !== true || extra.state !== 'active_signing' || !Number.isSafeInteger(extra.observed_at) || extra.observed_at <= 0 || extra.observed_at > now + 5 || extra.observed_at < now - 30 || !extra.cert_pem?.trim())
199
+ throw new StateError('ownership additional certificate current status is unavailable', { stringCode: 'unavailable', retryable: true });
200
+ additional.set(`${fields.signer_aid};${fields.cert_fingerprint}`, extra.cert_pem);
201
+ }
202
+ const verified = verifyDocument(new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(parsed.raw), { expectedAid: signer, primaryCertPem: certificate.cert_pem, requireCurrentValidity: true, now, certificateResolver: (aid, fingerprint) => additional.get(`${aid};${fingerprint}`) });
203
+ if (!verified.valid)
204
+ throw new ValidationError(verified.reason ?? 'ownership file signature verification failed');
205
+ return parsed;
206
+ }
207
+ export function signOwnershipFile(identity, content, role, context = {}) {
208
+ if (!identity.isPrivateKeyValid() || !identity.privateKeyPem || !identity.isCertValid())
209
+ throw new StateError('ownership requires a loaded signing identity');
210
+ const parsed = parseOwnershipFile(content, role, { ...context, requireSignature: false });
211
+ if (parsed.signature)
212
+ throw new ValidationError('ownership file is already signed');
213
+ const signer = role === 'agent' ? parsed.frontmatter.aid : parsed.frontmatter.owner_aid;
214
+ if (signer !== identity.aid)
215
+ throw new ValidationError('ownership files must be signed by their own AID');
216
+ if (!parsed.payload.endsWith('\n') && !parsed.payload.endsWith('\r'))
217
+ throw new ValidationError('ownership payload must end with a newline before signature');
218
+ const now = Math.trunc(context.now ?? Date.now() / 1000);
219
+ validateAgentMdCertificate(identity.certPem, identity.aid, now, { now });
220
+ return signDocument(identity, parsed.payload, { timestamp: now, tag: role === 'agent' ? 'AGENTMD-V2' : 'OWNERSHIP-V2' }).signed;
221
+ }
222
+ export function ownershipSha256(content) {
223
+ return createHash('sha256').update(content).digest('hex');
224
+ }
225
+ export function ownershipFailure(code, reason, retryable = false) {
226
+ return new StateError(`${code}: ${reason}`, { stringCode: code, retryable });
227
+ }
228
+ export function requireOwnershipApplicable(type) {
229
+ if (type === 'group' || type === 'org')
230
+ throw ownershipFailure('not_applicable', 'group/org use their dedicated lifecycle ownership');
231
+ }
232
+ export class OwnershipFiles {
233
+ deps;
234
+ constructor(deps) {
235
+ this.deps = deps;
236
+ }
237
+ async checkConsensus(subAid, target) {
238
+ ownershipAid(subAid, 'sub_aid');
239
+ const began = this.deps.now();
240
+ const result = { status: 'unavailable', sub_aid: subAid, owner_aid: null, relation_version: 0, lock: 'unlocked', agent_md_etag: '', agent_md_sha256: '', owner_md_sha256: '' };
241
+ try {
242
+ const before = await this.readAgent(subAid);
243
+ Object.assign(result, { relation_version: before.relation_version, lock: before.lock, agent_md_etag: before.etag, agent_md_sha256: before.exists ? ownershipSha256(before.content) : '' });
244
+ const agent = before.exists ? await this.verifyFile(before.content, 'agent', subAid) : undefined;
245
+ const statement = agent?.statement;
246
+ if (before.relation_version !== (statement?.relation_version ?? 0))
247
+ throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
248
+ if (before.lock === 'locked' && (!statement || statement.relation_version !== before.relation_version))
249
+ throw ownershipFailure('unavailable', 'NS locked relation does not match agent.md', true);
250
+ const currentOwner = before.lock === 'locked' ? statement.owner_aid : null;
251
+ if (before.owner_aid !== undefined && before.owner_aid !== currentOwner)
252
+ throw ownershipFailure('unavailable', 'NS owner metadata does not match agent.md', true);
253
+ result.owner_aid = currentOwner;
254
+ result.status = statement ? 'single_sided' : 'unbound';
255
+ const ownerAid = statement?.owner_aid ?? target?.owner_aid;
256
+ let ownerBefore;
257
+ if (ownerAid) {
258
+ const owner = await this.readOwner(ownerAid, subAid);
259
+ ownerBefore = owner;
260
+ result.owner_md_sha256 = owner.exists ? owner.sha256 ?? '' : '';
261
+ if (owner.exists) {
262
+ const declaration = await this.verifyFile(owner.content, 'owner', subAid, ownerAid);
263
+ if (statement) {
264
+ if (declaration.statement.relation_version !== before.relation_version)
265
+ throw new ValidationError('ownership relation_version mismatch');
266
+ result.status = declaration.action === 'release' ? 'released' : 'consensus';
267
+ }
268
+ else if (target && declaration.action === 'declare' && declaration.statement.relation_version === target.relation_version && target.relation_version === before.relation_version + 1) {
269
+ result.status = 'single_sided';
270
+ }
271
+ }
272
+ else if (statement)
273
+ result.status = 'released';
274
+ }
275
+ if (statement && before.lock !== (result.status === 'released' ? 'unlocked' : 'locked'))
276
+ throw ownershipFailure('unavailable', 'NS lock does not match current ownership materials', true);
277
+ result.owner_aid = result.status === 'consensus' ? statement.owner_aid : null;
278
+ const after = await this.readAgent(subAid);
279
+ if (ownerAid && ownerBefore) {
280
+ const ownerAfter = await this.readOwner(ownerAid, subAid);
281
+ if (ownerBefore.exists !== ownerAfter.exists || ownerBefore.content !== ownerAfter.content || ownerBefore.sha256 !== ownerAfter.sha256)
282
+ throw ownershipFailure('unavailable', 'owner.md changed during consensus check', true);
283
+ }
284
+ if (before.exists !== after.exists || before.etag !== after.etag || before.relation_version !== after.relation_version || before.lock !== after.lock || before.content !== after.content || before.owner_aid !== after.owner_aid || this.deps.now() - began > 30)
285
+ throw ownershipFailure('unavailable', 'NS relation changed or verification window elapsed during consensus check', true);
286
+ return result;
287
+ }
288
+ catch (error) {
289
+ result.status = error instanceof StateError && error.stringCode === 'not_applicable' ? 'not_applicable' : error instanceof ValidationError || error instanceof StateError && error.stringCode === 'invalid' ? 'invalid' : 'unavailable';
290
+ result.owner_aid = null;
291
+ result.code = error instanceof StateError && error.stringCode ? error.stringCode : result.status;
292
+ result.reason = error instanceof Error ? error.message : String(error);
293
+ result.retryable = result.status === 'unavailable';
294
+ return result;
295
+ }
296
+ }
297
+ async declareSubaid(task) {
298
+ const ownerAid = this.targetOwner(task);
299
+ this.requireLocal(ownerAid);
300
+ return this.publishOwner(task, 'owner', ownerAid, async () => {
301
+ const agent = await this.readAgent(task.sub_aid);
302
+ await this.assertBase(task, agent);
303
+ if (task.operation === 'transfer')
304
+ await this.requireTransfer(task);
305
+ return { statement: this.statement(task, ownerAid, task.target_relation_version), action: 'declare' };
306
+ });
307
+ }
308
+ async declareOwneraid(task) {
309
+ this.requireLocal(task.sub_aid);
310
+ const ownerAid = this.targetOwner(task);
311
+ return this.publishAgent(task, async (agent) => {
312
+ await this.assertBase(task, agent);
313
+ if (task.operation === 'transfer')
314
+ await this.requireTransfer(task);
315
+ const owner = await this.readOwner(ownerAid, task.sub_aid);
316
+ if (!owner.exists)
317
+ throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
318
+ const declaration = await this.verifyFile(owner.content, 'owner', task.sub_aid, ownerAid);
319
+ if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
320
+ throw ownershipFailure('conflict', 'target owner declaration does not match this task');
321
+ return { owner: ownerAid, ownership: { statement: this.statement(task, ownerAid, task.target_relation_version) } };
322
+ });
323
+ }
324
+ async revokeSubaid(task) {
325
+ this.requireLocal(task.owner_aid);
326
+ return this.publishOwner(task, 'release', task.owner_aid, async (owner) => {
327
+ await this.requireOriginal(task);
328
+ const original = await this.verifyFile(owner.content, 'owner', task.sub_aid, task.owner_aid);
329
+ return { statement: original.statement, action: 'release' };
330
+ });
331
+ }
332
+ async revokeOwneraid(task) {
333
+ this.requireLocal(task.sub_aid);
334
+ return this.publishAgent(task, async (agent) => {
335
+ if (agent.lock === 'locked')
336
+ throw ownershipFailure('owner_locked', 'NS has not confirmed release');
337
+ const parsed = parseOwnershipFile(agent.content, 'agent', { expectedSubAid: task.sub_aid });
338
+ if (!parsed.statement && agent.relation_version === 0)
339
+ return {};
340
+ if (agent.relation_version !== task.target_relation_version)
341
+ throw ownershipFailure('conflict', 'release relation_version changed');
342
+ if (parsed.statement && (parsed.statement.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
343
+ throw ownershipFailure('conflict', 'agent.md no longer contains the released relation');
344
+ if (task.expected_agent_statement && Object.keys(task.expected_agent_statement).some((field) => task.expected_agent_statement[field] !== parsed.statement?.[field]))
345
+ throw ownershipFailure('conflict', 'agent.md no longer contains the approved statement');
346
+ return {};
347
+ }, true);
348
+ }
349
+ async transferOwneraid(task) {
350
+ this.requireLocal(task.owner_aid);
351
+ ownershipAid(task.new_owner_aid, 'new_owner_aid');
352
+ if (task.new_owner_aid === task.owner_aid)
353
+ throw new ValidationError('transfer target must differ from current owner');
354
+ return this.publishOwner(task, 'transfer', task.owner_aid, async (owner) => {
355
+ await this.requireOriginal(task);
356
+ const original = await this.verifyFile(owner.content, 'owner', task.sub_aid, task.owner_aid);
357
+ return { statement: original.statement, action: 'transfer_owner', transfer: { new_owner_aid: task.new_owner_aid, target_relation_version: task.target_relation_version, expires_at: this.deps.now() + 3600 } };
358
+ });
359
+ }
360
+ async cleanupOwnerFile(task) {
361
+ this.requireLocal(task.owner_aid);
362
+ const source = this.savedFile(task, task.operation === 'release' ? 'release' : 'transfer');
363
+ if (!source)
364
+ throw ownershipFailure('conflict', 'original owner file is unavailable');
365
+ const consensus = await this.checkConsensus(task.sub_aid);
366
+ if (consensus.status === 'not_applicable')
367
+ throw ownershipFailure('not_applicable', consensus.reason ?? 'group/org use their dedicated lifecycle ownership');
368
+ if (consensus.status === 'invalid')
369
+ throw new ValidationError(consensus.reason ?? 'ownership consensus is invalid', { stringCode: 'invalid' });
370
+ if (consensus.status === 'unavailable')
371
+ throw ownershipFailure(consensus.code ?? 'unavailable', consensus.reason ?? 'ownership consensus is unavailable', true);
372
+ if (task.operation === 'release' && consensus.status === 'released')
373
+ return;
374
+ if (task.operation === 'release' ? consensus.status !== 'unbound' && consensus.status !== 'consensus' : consensus.relation_version !== task.target_relation_version || consensus.status !== 'consensus' || consensus.owner_aid !== task.new_owner_aid)
375
+ throw ownershipFailure('conflict', 'current materials do not permit this task cleanup');
376
+ const current = await this.readOwner(task.owner_aid, task.sub_aid);
377
+ if (current.exists && (current.content !== source.raw || current.sha256 !== source.hash))
378
+ throw ownershipFailure('conflict', 'owner file changed before cleanup');
379
+ if (!task.files.cleanup) {
380
+ task.files.cleanup = { raw: source.raw, hash: source.hash, condition: {} };
381
+ await task.persist();
382
+ }
383
+ else
384
+ this.savedFile(task, 'cleanup');
385
+ const beforeDelete = await this.readOwner(task.owner_aid, task.sub_aid);
386
+ if (beforeDelete.exists && (beforeDelete.content !== source.raw || beforeDelete.sha256 !== source.hash))
387
+ throw ownershipFailure('conflict', 'owner file changed before cleanup');
388
+ if (beforeDelete.exists)
389
+ await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid);
390
+ task.files.cleanup.deleted = true;
391
+ await task.persist();
392
+ }
393
+ async readAgent(subAid) {
394
+ if (this.deps.currentType && subAid === this.deps.currentAid().aid)
395
+ requireOwnershipApplicable(this.deps.currentType());
396
+ const value = await this.deps.readAgentMdFresh(subAid);
397
+ if (value.exists && typeof value.content === 'string')
398
+ requireOwnershipApplicable(parseFileFrontmatter(parseAgentMdTailSignature(value.content).payload).type);
399
+ const now = this.deps.now();
400
+ if (value.protocol !== '2.1')
401
+ throw ownershipFailure('unsupported_protocol', 'NS does not provide Ownership 2.1 metadata', true);
402
+ if (value.sub_aid !== subAid || typeof value.exists !== 'boolean' || !Number.isSafeInteger(value.relation_version) || value.relation_version < 0 || !['locked', 'unlocked'].includes(value.lock) || !Number.isSafeInteger(value.observed_at) || value.observed_at > now + 5 || value.observed_at < now - 30 || value.recovery_ready === false || typeof value.content !== 'string' || value.exists && !value.etag || !value.exists && (value.relation_version !== 0 || value.lock !== 'unlocked'))
403
+ throw ownershipFailure('unavailable', 'NS authoritative observation is incomplete', true);
404
+ return value;
405
+ }
406
+ async readOwner(ownerAid, subAid) {
407
+ const value = await this.deps.readOwnerMd(ownerAid, subAid);
408
+ const now = this.deps.now();
409
+ if (value.owner_aid !== ownerAid || value.sub_aid !== undefined && value.sub_aid !== subAid || value.bucket !== 'default' || value.path !== `sub_aids/${subAid}/owner.md` || typeof value.exists !== 'boolean' || !Number.isSafeInteger(value.observed_at) || value.observed_at > now + 5 || value.observed_at < now - 30 || value.authoritative !== true || value.recovery_ready !== undefined && value.recovery_ready !== true || value.service_aid !== `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}` || value.service_aid !== value.authenticated_service_aid || typeof value.content !== 'string' || value.exists && !/^[0-9a-f]{64}$/.test(value.sha256 ?? ''))
410
+ throw ownershipFailure('unavailable', 'Storage authoritative observation is incomplete or changed', true);
411
+ if (value.exists && value.sha256 !== ownershipSha256(value.content))
412
+ throw ownershipFailure('unavailable', 'Storage complete file digest does not match authoritative observation', true);
413
+ return value;
414
+ }
415
+ async verifyFile(raw, role, subAid, ownerAid) {
416
+ if (role === 'agent') {
417
+ const meta = parseFileFrontmatter(parseAgentMdTailSignature(raw).payload);
418
+ requireOwnershipApplicable(meta.type);
419
+ if ('ownership_proof' in meta || 'ownership_release' in meta || 'owner' in meta && !('ownership' in meta))
420
+ throw ownershipFailure('unsupported_protocol', 'legacy ownership requires explicit migration', true);
421
+ }
422
+ const context = { expectedSubAid: subAid, expectedOwnerAid: ownerAid, now: this.deps.now() };
423
+ const parsed = parseOwnershipFile(raw, role, context);
424
+ const signer = role === 'agent' ? subAid : String(parsed.frontmatter.owner_aid);
425
+ const certificate = await this.deps.resolveCertificate(signer, parsed.signature.cert_fingerprint, role === 'agent' && parsed.frontmatter.type === 'group' ? 'group_identity' : undefined);
426
+ const additional = new Map();
427
+ for (const item of parseAgentMdTailSignature(raw).signatures.slice(1))
428
+ additional.set(`${item.fields.signer_aid};${item.fields.cert_fingerprint}`, await this.deps.resolveCertificate(item.fields.signer_aid, item.fields.cert_fingerprint));
429
+ return verifyOwnershipFile(raw, role, certificate, { ...context, now: this.deps.now(), certificateResolver: (aid, fingerprint) => additional.get(`${aid};${fingerprint}`) });
430
+ }
431
+ async requireTransfer(task) {
432
+ const owner = await this.readOwner(task.owner_aid, task.sub_aid);
433
+ if (!owner.exists)
434
+ throw ownershipFailure('unavailable', 'original owner authorization is missing', true);
435
+ const authorization = await this.verifyFile(owner.content, 'owner', task.sub_aid, task.owner_aid);
436
+ const saved = task.files.transfer;
437
+ if (saved && (saved.raw !== owner.content || saved.hash !== owner.sha256))
438
+ throw ownershipFailure('conflict', 'fixed transfer authorization changed');
439
+ if (authorization.action !== 'transfer_owner' || authorization.statement.relation_version !== task.base_relation_version || authorization.transfer.new_owner_aid !== task.new_owner_aid || authorization.transfer.target_relation_version !== task.target_relation_version)
440
+ throw ownershipFailure('conflict', 'transfer authorization does not match this task');
441
+ const expiresAt = authorization.transfer.expires_at;
442
+ if (expiresAt <= this.deps.now())
443
+ throw ownershipFailure('conflict', 'transfer authorization expired');
444
+ if (task.authorization_sha256 !== undefined && task.authorization_sha256 !== owner.sha256 || task.expires_at !== undefined && task.expires_at !== expiresAt)
445
+ throw ownershipFailure('conflict', 'transfer authorization changed');
446
+ if (task.authorization_sha256 === undefined || task.expires_at === undefined) {
447
+ task.authorization_sha256 = owner.sha256;
448
+ task.expires_at = expiresAt;
449
+ await task.persist();
450
+ }
451
+ return authorization;
452
+ }
453
+ targetOwner(task) {
454
+ return ownershipAid(task.operation === 'transfer' ? task.new_owner_aid : task.owner_aid, 'target_owner_aid');
455
+ }
456
+ requireLocal(aid) {
457
+ if (this.deps.currentAid().aid !== aid)
458
+ throw ownershipFailure('forbidden', 'ownership files require their own authenticated AID');
459
+ }
460
+ statement(task, ownerAid, version) {
461
+ return { version: 2, declaration_id: randomUUID(), owner_aid: ownerAid, sub_aid: task.sub_aid, relation_version: version, declared_at: this.deps.now() };
462
+ }
463
+ async assertBase(task, agent) {
464
+ ownershipInteger(task.base_relation_version, 'base_relation_version', 0);
465
+ if (ownershipInteger(task.target_relation_version, 'target_relation_version') !== task.base_relation_version + (task.operation === 'release' ? 0 : 1) || agent.relation_version !== task.base_relation_version)
466
+ throw ownershipFailure('conflict', 'NS relation_version changed');
467
+ if (!agent.exists)
468
+ throw ownershipFailure('unavailable', 'initial agent.md is not published', true);
469
+ const parsed = await this.verifyFile(agent.content, 'agent', task.sub_aid);
470
+ const base = task.base_agent_md;
471
+ if (base && (agent.exists !== base.exists || (base.statement ? !parsed.statement || Object.keys(base.statement).some((field) => base.statement[field] !== parsed.statement[field]) : parsed.statement || agent.content !== base.raw || ownershipSha256(agent.content) !== base.sha256)))
472
+ throw ownershipFailure('conflict', 'the original ownership statement was replaced');
473
+ if (agent.relation_version !== (parsed.statement?.relation_version ?? 0))
474
+ throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
475
+ if (task.operation === 'bind' && parsed.frontmatter.type === 'human')
476
+ throw ownershipFailure('invalid', 'human cannot declare an owner');
477
+ if (task.operation === 'bind' && agent.lock !== 'unlocked')
478
+ throw ownershipFailure('owner_locked', 'an existing owner relation is locked');
479
+ if (task.operation === 'bind' && parsed.statement?.owner_aid === this.targetOwner(task))
480
+ throw ownershipFailure('conflict', 'clear the released agent.md statement before binding the same owner');
481
+ if (task.operation !== 'bind' && (agent.lock !== 'locked' || parsed.statement?.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
482
+ throw ownershipFailure('conflict', 'the original relation is no longer current');
483
+ }
484
+ async requireOriginal(task) {
485
+ await this.assertBase(task, await this.readAgent(task.sub_aid));
486
+ const consensus = await this.checkConsensus(task.sub_aid);
487
+ if (consensus.status !== 'consensus' || consensus.owner_aid !== task.owner_aid || consensus.relation_version !== task.base_relation_version)
488
+ throw ownershipFailure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'the original ownership consensus cannot be confirmed', consensus.status !== 'invalid');
489
+ }
490
+ savedFile(task, key) {
491
+ const saved = task.files[key];
492
+ if (saved && (typeof saved.raw !== 'string' || saved.hash !== ownershipSha256(saved.raw) || !this.validSavedCondition(key, saved.condition) || saved.published_etag !== undefined && (typeof saved.published_etag !== 'string' || !saved.published_etag)))
493
+ throw ownershipFailure('persistence_unavailable', 'saved ownership file is corrupt');
494
+ const original = saved?.original_owner;
495
+ if (saved && key !== 'agent' && key !== 'cleanup' && (!original || typeof original.exists !== 'boolean' || typeof original.raw !== 'string' || original.sha256 !== (original.exists ? ownershipSha256(original.raw) : '') || !original.exists && original.raw !== ''))
496
+ throw ownershipFailure('persistence_unavailable', 'original owner.md evidence is corrupt');
497
+ return saved;
498
+ }
499
+ validSavedCondition(key, condition) {
500
+ if (!condition || typeof condition !== 'object')
501
+ return false;
502
+ if (key !== 'agent')
503
+ return Object.keys(condition).length === 0;
504
+ if (Object.keys(condition).length !== 1)
505
+ return false;
506
+ if (key === 'agent')
507
+ return typeof condition.if_match === 'string' && condition.if_match.length > 0 || condition.if_none_match === '*';
508
+ return false;
509
+ }
510
+ async publishOwner(task, key, ownerAid, prepare) {
511
+ let saved = this.savedFile(task, key);
512
+ await this.readAgent(task.sub_aid);
513
+ const current = await this.readOwner(ownerAid, task.sub_aid);
514
+ if (!saved) {
515
+ const base = task.base_owner_md;
516
+ if (base && ownerAid === task.base_agent_md?.statement?.owner_aid && (current.exists !== base.exists || current.content !== base.raw || (current.exists ? current.sha256 : '') !== base.sha256))
517
+ throw ownershipFailure('conflict', 'original owner.md material changed before publication');
518
+ const ownership = await prepare(current);
519
+ const raw = signOwnershipFile(this.deps.currentAid(), `---\nowner_aid: ${ownerAid}\nsub_aid: ${task.sub_aid}\nownership: ${JSON.stringify(ownership)}\n---\n`, 'owner', { now: this.deps.now(), expectedOwnerAid: ownerAid, expectedSubAid: task.sub_aid });
520
+ saved = { raw, hash: ownershipSha256(raw), condition: {}, original_owner: { exists: current.exists, raw: current.content, sha256: current.exists ? ownershipSha256(current.content) : '' } };
521
+ if (key === 'transfer')
522
+ saved.expires_at = ownership.transfer.expires_at;
523
+ task.files[key] = saved;
524
+ await task.persist();
525
+ }
526
+ await this.verifyFile(saved.raw, 'owner', task.sub_aid, ownerAid);
527
+ if (current.exists && current.sha256 === saved.hash && current.content === saved.raw) {
528
+ await task.persist();
529
+ return current;
530
+ }
531
+ const original = saved.original_owner;
532
+ if (!original || typeof original.exists !== 'boolean' || typeof original.raw !== 'string' || original.sha256 !== (original.exists ? ownershipSha256(original.raw) : ''))
533
+ throw ownershipFailure('persistence_unavailable', 'owner file original material is missing or corrupt');
534
+ if (current.exists !== original.exists || current.content !== original.raw || (current.exists ? current.sha256 : '') !== original.sha256)
535
+ throw ownershipFailure('conflict', 'owner file original material changed');
536
+ await this.assertBase(task, await this.readAgent(task.sub_aid));
537
+ if (key === 'owner' && task.operation === 'transfer')
538
+ await this.requireTransfer(task);
539
+ if (key === 'transfer' && saved.expires_at <= this.deps.now())
540
+ throw ownershipFailure('conflict', 'transfer authorization expired');
541
+ const beforeWrite = await this.readOwner(ownerAid, task.sub_aid);
542
+ if (beforeWrite.exists !== original.exists || beforeWrite.content !== original.raw || (beforeWrite.exists ? beforeWrite.sha256 : '') !== original.sha256)
543
+ throw ownershipFailure('conflict', 'owner file original material changed before publication');
544
+ const published = await this.deps.writeOwnerMd(ownerAid, task.sub_aid, saved.raw);
545
+ if (!published.exists || published.sha256 !== saved.hash || published.content !== saved.raw)
546
+ throw ownershipFailure('unavailable', 'owner publication could not be verified', true);
547
+ await task.persist();
548
+ return published;
549
+ }
550
+ async publishAgent(task, prepare, cleanup = false) {
551
+ let saved = this.savedFile(task, 'agent');
552
+ const current = await this.readAgent(task.sub_aid);
553
+ if (!saved) {
554
+ const fields = await prepare(current);
555
+ if (cleanup && !parseOwnershipFile(current.content, 'agent', { now: this.deps.now() }).statement)
556
+ return current;
557
+ const payload = updateOwnershipAgentMd(current.content, fields);
558
+ const raw = signOwnershipFile(this.deps.currentAid(), payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent', { now: this.deps.now(), expectedSubAid: task.sub_aid });
559
+ saved = { raw, hash: ownershipSha256(raw), condition: current.exists ? { if_match: current.etag } : { if_none_match: '*' } };
560
+ task.files.agent = saved;
561
+ await task.persist();
562
+ }
563
+ await this.verifyFile(saved.raw, 'agent', task.sub_aid);
564
+ if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === (cleanup ? 0 : task.target_relation_version) && current.lock === (cleanup ? 'unlocked' : 'locked')) {
565
+ if (saved.published_etag !== undefined && current.etag !== saved.published_etag)
566
+ throw ownershipFailure('conflict', 'agent.md published ETag changed');
567
+ saved.published_etag = current.etag;
568
+ await task.persist();
569
+ return current;
570
+ }
571
+ if (current.etag !== saved.condition.if_match)
572
+ throw ownershipFailure('conflict', 'agent.md original ETag condition changed');
573
+ if (cleanup) {
574
+ if (current.lock !== 'unlocked' || current.relation_version !== task.target_relation_version)
575
+ throw ownershipFailure('conflict', 'release target is no longer current');
576
+ }
577
+ else {
578
+ await this.assertBase(task, current);
579
+ if (task.operation === 'transfer')
580
+ await this.requireTransfer(task);
581
+ const target = await this.readOwner(this.targetOwner(task), task.sub_aid);
582
+ if (!target.exists)
583
+ throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
584
+ const declaration = await this.verifyFile(target.content, 'owner', task.sub_aid, this.targetOwner(task));
585
+ const savedOwner = task.files.owner;
586
+ if (savedOwner && (savedOwner.raw !== target.content || savedOwner.hash !== target.sha256))
587
+ throw ownershipFailure('conflict', 'fixed target owner declaration changed');
588
+ if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
589
+ throw ownershipFailure('conflict', 'target owner declaration changed');
590
+ }
591
+ const published = await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition);
592
+ if (published.content !== saved.raw || published.relation_version !== (cleanup ? 0 : task.target_relation_version) || published.lock !== (cleanup ? 'unlocked' : 'locked'))
593
+ throw ownershipFailure('unavailable', 'agent.md publication could not be verified', true);
594
+ saved.published_etag = published.etag;
595
+ await task.persist();
596
+ return published;
597
+ }
598
+ }
599
+ export class OwnershipStorageAdapter {
600
+ deps;
601
+ constructor(deps) {
602
+ this.deps = deps;
603
+ }
604
+ now() { return this.deps.now?.() ?? Math.trunc(Date.now() / 1000); }
605
+ async call(method, params) {
606
+ try {
607
+ const result = await this.deps.call(method, params);
608
+ if (!result || typeof result !== 'object' || Array.isArray(result))
609
+ throw ownershipFailure('unavailable', 'Storage response is not an object', true);
610
+ return result;
611
+ }
612
+ catch (error) {
613
+ const reading = method === 'storage.head_object' || method === 'storage.get_object';
614
+ const code = error && typeof error === 'object' ? error.code : undefined;
615
+ if (error instanceof ValidationError && code !== -32601 || error instanceof StateError && error.stringCode === 'invalid')
616
+ throw new ValidationError(error.message, { stringCode: 'invalid' });
617
+ if (error instanceof StateError && ['unavailable', 'unsupported_protocol', 'conflict'].includes(error.stringCode))
618
+ throw ownershipFailure(error.stringCode, error.message, error.stringCode !== 'conflict');
619
+ if (reading && error instanceof StateError && error.stringCode === 'forbidden')
620
+ throw ownershipFailure('unavailable', error.message, true);
621
+ if (error instanceof StateError && error.stringCode)
622
+ throw error;
623
+ throw ownershipFailure(code === -32009 ? 'conflict' : code === -32004 ? reading ? 'unavailable' : 'forbidden' : code === -32601 ? 'unsupported_protocol' : 'unavailable', error instanceof Error ? error.message : String(error), code !== -32009 && (reading || code !== -32004));
624
+ }
625
+ }
626
+ metadata(value, ownerAid, path) {
627
+ const now = this.now();
628
+ const expectedService = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
629
+ if (value.consistency !== 'authoritative' || value.owner_aid !== ownerAid || value.bucket !== 'default' || value.path !== path || typeof value.exists !== 'boolean' || !Number.isSafeInteger(value.observed_at) || Number(value.observed_at) < now - 30 || Number(value.observed_at) > now + 5 || value.recovery_ready !== undefined && value.recovery_ready !== true || value.service_aid !== undefined && value.service_aid !== expectedService || value.authenticated_service_aid !== undefined && value.authenticated_service_aid !== expectedService || value.exists && (typeof value.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(value.sha256)))
630
+ throw ownershipFailure('unavailable', 'Storage authoritative namespace or freshness metadata is incomplete', true);
631
+ }
632
+ async readObject(ownerAid, path) {
633
+ ownershipAid(ownerAid, 'owner_aid');
634
+ const began = this.now();
635
+ const params = { owner_aid: ownerAid, bucket: 'default', object_key: path, consistency: 'authoritative' };
636
+ const before = await this.call('storage.head_object', params);
637
+ this.metadata(before, ownerAid, path);
638
+ let content = '';
639
+ let final = before;
640
+ if (before.exists) {
641
+ const object = await this.call('storage.get_object', params);
642
+ this.metadata(object, ownerAid, path);
643
+ if (object.exists !== true || object.sha256 !== before.sha256 || typeof object.content !== 'string')
644
+ throw ownershipFailure('unavailable', 'Storage object changed or full Base64 content is unavailable', true);
645
+ if (object.content !== '' && !isStrictBase64(object.content))
646
+ throw ownershipFailure('unavailable', 'Storage Base64 content is unavailable', true);
647
+ const bytes = Buffer.from(object.content, 'base64');
648
+ if (ownershipSha256(bytes) !== before.sha256)
649
+ throw ownershipFailure('unavailable', 'Storage complete file digest does not match authoritative head', true);
650
+ try {
651
+ content = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
652
+ }
653
+ catch {
654
+ throw new ValidationError('Storage ownership file is not valid UTF-8', { stringCode: 'invalid' });
655
+ }
656
+ final = await this.call('storage.head_object', params);
657
+ this.metadata(final, ownerAid, path);
658
+ if (final.exists !== true || final.sha256 !== before.sha256 || this.now() - began > 30)
659
+ throw ownershipFailure('unavailable', 'Storage material changed during authoritative read', true);
660
+ }
661
+ const service = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
662
+ return { owner_aid: ownerAid, bucket: 'default', path, exists: Boolean(final.exists), observed_at: Number(final.observed_at),
663
+ content, sha256: final.exists ? String(final.sha256) : undefined, authoritative: true, recovery_ready: final.recovery_ready, service_aid: service, authenticated_service_aid: service, version: final.exists && Number.isSafeInteger(final.version) && Number(final.version) > 0 ? final.version : undefined };
664
+ }
665
+ async readOwnerMd(ownerAid, subAid) {
666
+ ownershipAid(subAid, 'sub_aid');
667
+ return this.readObject(ownerAid, `sub_aids/${subAid}/owner.md`);
668
+ }
669
+ async writeOwnerMd(ownerAid, subAid, raw) {
670
+ this.requireLocal(ownerAid);
671
+ ownershipAid(subAid, 'sub_aid');
672
+ const path = `sub_aids/${subAid}/owner.md`;
673
+ const before = await this.readOwnerMd(ownerAid, subAid);
674
+ const bytes = new TextEncoder().encode(raw);
675
+ const expectedVersion = before.exists ? before.version : 0;
676
+ if (typeof expectedVersion !== 'number' || !Number.isSafeInteger(expectedVersion) || expectedVersion < (before.exists ? 1 : 0))
677
+ throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
678
+ const session = await this.call('storage.create_upload_session', { owner_aid: ownerAid, bucket: 'default', object_key: path, size_bytes: bytes.length, content_type: 'text/markdown; charset=utf-8', expected_version: expectedVersion, overwrite: true });
679
+ const uploadURL = String(session.upload_url ?? ''), sessionID = String(session.session_id ?? '');
680
+ if (!uploadURL || !sessionID)
681
+ throw ownershipFailure('unavailable', 'Storage owner.md upload session is incomplete', true);
682
+ const headers = {};
683
+ if (session.headers && typeof session.headers === 'object')
684
+ for (const [key, value] of Object.entries(session.headers))
685
+ if (value !== undefined && value !== null && key.toLowerCase() !== 'content-type')
686
+ headers[key] = String(value);
687
+ headers['Content-Type'] = 'text/markdown; charset=utf-8';
688
+ if (this.deps.httpPut)
689
+ await this.deps.httpPut(uploadURL, bytes, headers);
690
+ else {
691
+ const response = await fetch(uploadURL, { method: 'PUT', body: bytes, headers });
692
+ if (!response.ok)
693
+ throw ownershipFailure('unavailable', `Storage owner.md HTTP PUT failed: ${response.status}`, true);
694
+ }
695
+ const complete = { owner_aid: ownerAid, bucket: 'default', object_key: path, session_id: sessionID, size_bytes: bytes.length, sha256: ownershipSha256(bytes), content_type: 'text/markdown; charset=utf-8', is_private: false, overwrite: true };
696
+ complete.expected_version = expectedVersion;
697
+ await this.call('storage.complete_upload', complete);
698
+ return this.readOwnerMd(ownerAid, subAid);
699
+ }
700
+ async deleteOwnerMd(ownerAid, subAid) {
701
+ this.requireLocal(ownerAid);
702
+ ownershipAid(subAid, 'sub_aid');
703
+ await this.call('storage.delete_object', { owner_aid: ownerAid, bucket: 'default', object_key: `sub_aids/${subAid}/owner.md` });
704
+ if ((await this.readOwnerMd(ownerAid, subAid)).exists)
705
+ throw ownershipFailure('unavailable', 'Storage deletion could not be confirmed', true);
706
+ }
707
+ requireLocal(ownerAid) {
708
+ if (ownershipAid(ownerAid, 'owner_aid') !== this.deps.currentAid())
709
+ throw ownershipFailure('forbidden', 'owner.md must be published by its own authenticated AID');
710
+ }
711
+ }
712
+ //# sourceMappingURL=ownership-v21.js.map