@agentunion/fastaun-browser 0.5.17 → 0.5.19

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 (119) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/_packed_docs/CHANGELOG.md +83 -0
  3. package/_packed_docs/INDEX.md +163 -16
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +52 -20
  5. package/_packed_docs/agent.md/SCHEMA.md +266 -207
  6. 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 +64 -31
  7. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +8 -6
  8. 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 +39 -39
  9. 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 +1 -1
  10. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +28 -9
  11. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +20 -2
  12. package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
  13. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +5 -2
  14. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +246 -241
  15. package/_packed_docs/sdk/09-group-rpc-manual.md +46 -14
  16. package/_packed_docs/sdk/09-message-rpc-manual.md +40 -2
  17. package/_packed_docs/sdk/09-storage-rpc-manual.md +69 -38
  18. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +25 -5
  19. package/_packed_docs/sdk/INDEX.md +78 -18
  20. package/dist/agent-md-schema.d.ts +3 -0
  21. package/dist/agent-md-schema.d.ts.map +1 -1
  22. package/dist/agent-md-schema.js +46 -10
  23. package/dist/agent-md-schema.js.map +1 -1
  24. package/dist/agent-md.d.ts +44 -1
  25. package/dist/agent-md.d.ts.map +1 -1
  26. package/dist/agent-md.js +518 -26
  27. package/dist/agent-md.js.map +1 -1
  28. package/dist/aid-store.d.ts +12 -1
  29. package/dist/aid-store.d.ts.map +1 -1
  30. package/dist/aid-store.js +113 -18
  31. package/dist/aid-store.js.map +1 -1
  32. package/dist/aid.d.ts +17 -1
  33. package/dist/aid.d.ts.map +1 -1
  34. package/dist/aid.js +69 -9
  35. package/dist/aid.js.map +1 -1
  36. package/dist/auth.d.ts +4 -1
  37. package/dist/auth.d.ts.map +1 -1
  38. package/dist/auth.js +343 -23
  39. package/dist/auth.js.map +1 -1
  40. package/dist/bundle.js +37513 -31501
  41. package/dist/cert-utils.d.ts +1 -1
  42. package/dist/cert-utils.d.ts.map +1 -1
  43. package/dist/cert-utils.js +24 -10
  44. package/dist/cert-utils.js.map +1 -1
  45. package/dist/client/delivery.d.ts +16 -1
  46. package/dist/client/delivery.d.ts.map +1 -1
  47. package/dist/client/delivery.js +198 -58
  48. package/dist/client/delivery.js.map +1 -1
  49. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  50. package/dist/client/rpc-pipeline.js +28 -6
  51. package/dist/client/rpc-pipeline.js.map +1 -1
  52. package/dist/client/v2-e2ee.d.ts +1 -0
  53. package/dist/client/v2-e2ee.d.ts.map +1 -1
  54. package/dist/client/v2-e2ee.js +858 -484
  55. package/dist/client/v2-e2ee.js.map +1 -1
  56. package/dist/client.d.ts +100 -1
  57. package/dist/client.d.ts.map +1 -1
  58. package/dist/client.js +1216 -24
  59. package/dist/client.js.map +1 -1
  60. package/dist/errors.d.ts.map +1 -1
  61. package/dist/errors.js +3 -2
  62. package/dist/errors.js.map +1 -1
  63. package/dist/events.d.ts +4 -0
  64. package/dist/events.d.ts.map +1 -1
  65. package/dist/events.js +41 -1
  66. package/dist/events.js.map +1 -1
  67. package/dist/facades.d.ts +84 -2
  68. package/dist/facades.d.ts.map +1 -1
  69. package/dist/facades.js +104 -4
  70. package/dist/facades.js.map +1 -1
  71. package/dist/index.d.ts +8 -3
  72. package/dist/index.d.ts.map +1 -1
  73. package/dist/index.js +3 -1
  74. package/dist/index.js.map +1 -1
  75. package/dist/keystore/index.d.ts +17 -0
  76. package/dist/keystore/index.d.ts.map +1 -1
  77. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  78. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  79. package/dist/keystore/indexeddb-shared.js +111 -0
  80. package/dist/keystore/indexeddb-shared.js.map +1 -1
  81. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  82. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  83. package/dist/keystore/indexeddb-token-store.js +30 -1
  84. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  85. package/dist/org-protocol.d.ts +98 -0
  86. package/dist/org-protocol.d.ts.map +1 -0
  87. package/dist/org-protocol.js +1742 -0
  88. package/dist/org-protocol.js.map +1 -0
  89. package/dist/ownership-proof.d.ts +118 -0
  90. package/dist/ownership-proof.d.ts.map +1 -0
  91. package/dist/ownership-proof.js +283 -0
  92. package/dist/ownership-proof.js.map +1 -0
  93. package/dist/ownership-v21.d.ts +177 -0
  94. package/dist/ownership-v21.d.ts.map +1 -0
  95. package/dist/ownership-v21.js +609 -0
  96. package/dist/ownership-v21.js.map +1 -0
  97. package/dist/ownership-workflow.d.ts +146 -0
  98. package/dist/ownership-workflow.d.ts.map +1 -0
  99. package/dist/ownership-workflow.js +1066 -0
  100. package/dist/ownership-workflow.js.map +1 -0
  101. package/dist/seq-tracker.d.ts +1 -0
  102. package/dist/seq-tracker.d.ts.map +1 -1
  103. package/dist/seq-tracker.js +10 -2
  104. package/dist/seq-tracker.js.map +1 -1
  105. package/dist/tools/cross-sdk-agent.d.ts +82 -0
  106. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  107. package/dist/tools/cross-sdk-agent.js +332 -7
  108. package/dist/tools/cross-sdk-agent.js.map +1 -1
  109. package/dist/transport.d.ts +6 -0
  110. package/dist/transport.d.ts.map +1 -1
  111. package/dist/transport.js +88 -2
  112. package/dist/transport.js.map +1 -1
  113. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  114. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  115. package/dist/v2/e2ee/decrypt.js +9 -1
  116. package/dist/v2/e2ee/decrypt.js.map +1 -1
  117. package/dist/version.d.ts +1 -1
  118. package/dist/version.js +1 -1
  119. package/package.json +5 -5
@@ -0,0 +1,609 @@
1
+ import { parseFileFrontmatter, validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
2
+ import { buildAgentMdSignatureBlock, certFingerprint, isStrictBase64, parseAgentMdTailSignature, publicKeyFingerprint, publicKeyMatchesFingerprint, signBytes, verifySignatureWithCert } from './cert-utils.js';
3
+ import { base64ToUint8 } from './crypto.js';
4
+ import { StateError, ValidationError } from './errors.js';
5
+ import { buildDefaultAgentMd, buildOwnershipAgentMdV21 } from './agent-md.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 = base64ToUint8(value);
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) {
112
+ if (!/^sha256:[0-9a-f]{64}$/.test(fields.cert_fingerprint) || fields.public_key_fingerprint !== undefined && !/^sha256:[0-9a-f]{64}$/.test(fields.public_key_fingerprint))
113
+ throw new ValidationError('ownership file fingerprint is invalid');
114
+ if (!/^[1-9][0-9]*$/.test(fields.timestamp) || !Number.isSafeInteger(Number(fields.timestamp)))
115
+ throw new ValidationError('ownership file timestamp must be a positive integer');
116
+ if (!strictDerSignature(fields.signature))
117
+ throw new ValidationError('ownership file signature must be strict Base64 DER');
118
+ }
119
+ let frontmatter;
120
+ let statement;
121
+ let action;
122
+ let transfer;
123
+ if (role === 'agent') {
124
+ frontmatter = validateAgentMdDocument(text, { expectedAid: context.expectedSubAid, validateOwnership: false, now: context.now, checkTimestamp: false }).frontmatter;
125
+ requireOwnershipApplicable(frontmatter.type);
126
+ statement = validateOwnershipAgentFrontmatter(frontmatter, context.expectedSubAid);
127
+ if (context.expectedOwnerAid && statement?.owner_aid !== context.expectedOwnerAid)
128
+ throw new ValidationError('agent.md owner mismatch');
129
+ }
130
+ else if (role === 'owner') {
131
+ frontmatter = parseFileFrontmatter(parsed.payload);
132
+ exactKeys(frontmatter, ['owner_aid', 'sub_aid', 'ownership'], 'owner.md');
133
+ const owner = ownershipAid(frontmatter.owner_aid, 'owner_aid'), sub = ownershipAid(frontmatter.sub_aid, 'sub_aid');
134
+ if (context.expectedOwnerAid && owner !== context.expectedOwnerAid || context.expectedSubAid && sub !== context.expectedSubAid)
135
+ throw new ValidationError('owner.md path identity mismatch');
136
+ const ownership = mapping(frontmatter.ownership, 'ownership');
137
+ action = ownership.action;
138
+ if (typeof action !== 'string' || !['declare', 'release', 'transfer_owner'].includes(action))
139
+ throw new ValidationError('owner.md action is invalid');
140
+ exactKeys(ownership, action === 'transfer_owner' ? ['statement', 'action', 'transfer'] : ['statement', 'action'], 'owner.md ownership');
141
+ statement = validateOwnershipStatement(ownership.statement);
142
+ if (statement.owner_aid !== owner || statement.sub_aid !== sub)
143
+ throw new ValidationError('owner.md ownership statement identity mismatch');
144
+ if (action === 'transfer_owner') {
145
+ const value = mapping(ownership.transfer, 'ownership.transfer');
146
+ exactKeys(value, ['new_owner_aid', 'target_relation_version', 'expires_at'], 'ownership.transfer');
147
+ if (ownershipAid(value.new_owner_aid, 'new_owner_aid') === owner)
148
+ throw new ValidationError('transfer target must differ from current owner');
149
+ if (ownershipInteger(value.target_relation_version, 'target_relation_version') !== statement.relation_version + 1)
150
+ throw new ValidationError('transfer target_relation_version mismatch');
151
+ ownershipInteger(value.expires_at, 'expires_at');
152
+ transfer = value;
153
+ }
154
+ }
155
+ else
156
+ throw new ValidationError('ownership file role is invalid');
157
+ return { raw, payload: parsed.payload, frontmatter, signature: fields, statement, action, transfer };
158
+ }
159
+ export async function verifyOwnershipFile(content, role, certificate, context = {}) {
160
+ const parsed = parseOwnershipFile(content, role, { ...context, requireSignature: true });
161
+ const fields = parsed.signature;
162
+ const signer = role === 'agent' ? String(parsed.frontmatter.aid) : String(parsed.frontmatter.owner_aid);
163
+ const sub = role === 'agent' ? signer : String(parsed.frontmatter.sub_aid);
164
+ const storagePath = role === 'agent' ? 'agent.md' : `sub_aids/${sub}/owner.md`;
165
+ if (context.expectedTargetAid && context.expectedTargetAid !== sub || context.authenticatedAid && context.authenticatedAid !== signer || context.certificateAid && context.certificateAid !== signer || context.resourcePath && context.resourcePath !== `${signer}:/${storagePath}` || context.storagePath && context.storagePath !== storagePath)
166
+ throw new ValidationError('ownership file resource or authenticated identity mismatch');
167
+ const now = Math.trunc(context.now ?? Date.now() / 1000);
168
+ if (Number(fields.timestamp) > now + 300)
169
+ throw new ValidationError('ownership file timestamp is too far in the future');
170
+ if (certificate.chain_valid === false || certificate.revoked === true || typeof certificate.state === 'string' && certificate.state !== 'active_signing')
171
+ throw new ValidationError('ownership signing certificate is not currently verified and active_signing', { stringCode: 'invalid' });
172
+ if (certificate.chain_valid !== true || certificate.state !== 'active_signing' || certificate.revoked !== false || certificate.revocation_checked !== true || !Number.isSafeInteger(certificate.observed_at) || certificate.observed_at <= 0 || certificate.observed_at > now + 5 || certificate.observed_at < now - 30)
173
+ throw new StateError('ownership certificate current status is unavailable', { stringCode: 'unavailable', retryable: true });
174
+ validateAgentMdCertificate(certificate.cert_pem, signer, Number(fields.timestamp), now);
175
+ if (await certFingerprint(certificate.cert_pem) !== fields.cert_fingerprint)
176
+ throw new ValidationError('ownership certificate fingerprint mismatch');
177
+ if (fields.public_key_fingerprint && !await publicKeyMatchesFingerprint(certificate.cert_pem, fields.public_key_fingerprint))
178
+ throw new ValidationError('ownership public key fingerprint mismatch');
179
+ if (!await verifySignatureWithCert(certificate.cert_pem, fields.signature, new TextEncoder().encode(parsed.payload)))
180
+ throw new ValidationError('ownership file signature verification failed');
181
+ return parsed;
182
+ }
183
+ export async function signOwnershipFile(identity, content, role, context = {}) {
184
+ if (!identity.isPrivateKeyValid() || !identity.privateKeyPem || !identity.isCertValid())
185
+ throw new StateError('ownership requires a loaded signing identity');
186
+ const parsed = parseOwnershipFile(content, role, { ...context, requireSignature: false });
187
+ if (parsed.signature)
188
+ throw new ValidationError('ownership file is already signed');
189
+ const signer = role === 'agent' ? parsed.frontmatter.aid : parsed.frontmatter.owner_aid;
190
+ if (signer !== identity.aid)
191
+ throw new ValidationError('ownership files must be signed by their own AID');
192
+ if (!parsed.payload.endsWith('\n') && !parsed.payload.endsWith('\r'))
193
+ throw new ValidationError('ownership payload must end with a newline before signature');
194
+ const now = Math.trunc(context.now ?? Date.now() / 1000);
195
+ validateAgentMdCertificate(identity.certPem, identity.aid, now, now);
196
+ const signature = await signBytes(identity.privateKeyPem, new TextEncoder().encode(parsed.payload));
197
+ return parsed.payload + buildAgentMdSignatureBlock(identity.certFingerprint, now, signature, await publicKeyFingerprint(identity.certPem), role === 'agent' ? 'AUN-SIGNATURE-AGENTMD-V2' : 'AUN-SIGNATURE-OWNERSHIP-V2');
198
+ }
199
+ export async function ownershipSha256(content) {
200
+ const bytes = typeof content === 'string' ? new TextEncoder().encode(content) : content;
201
+ const digest = await crypto.subtle.digest('SHA-256', bytes.slice().buffer);
202
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, '0')).join('');
203
+ }
204
+ export function ownershipStatementsMatch(first, second) {
205
+ return first.version === second.version && first.owner_aid === second.owner_aid && first.sub_aid === second.sub_aid && first.relation_version === second.relation_version;
206
+ }
207
+ export class OwnershipStorageAdapter {
208
+ deps;
209
+ constructor(deps) {
210
+ this.deps = deps;
211
+ }
212
+ error(code, reason) { return new StateError(reason, { stringCode: code, retryable: code === 'unavailable' }); }
213
+ async request(method, params) {
214
+ try {
215
+ const result = await this.deps.call(method, params);
216
+ if (!result || typeof result !== 'object' || Array.isArray(result))
217
+ throw this.error('unavailable', 'Storage authoritative result unavailable');
218
+ return result;
219
+ }
220
+ catch (error) {
221
+ if (error instanceof StateError && error.stringCode)
222
+ throw error;
223
+ const code = error?.code;
224
+ throw this.error(code === -32009 ? 'conflict' : code === -32004 ? 'forbidden' : code === -32601 || code === -32602 ? 'unsupported_protocol' : 'unavailable', `Storage ownership request failed: ${String(error)}`);
225
+ }
226
+ }
227
+ metadata(value, ownerAid, path) {
228
+ const now = Math.trunc(this.deps.now?.() ?? Date.now() / 1000);
229
+ const observed = value.observed_at;
230
+ const service = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
231
+ if (Object.hasOwn(value, 'service_aid') && value.service_aid !== service || Object.hasOwn(value, 'authenticated_service_aid') && value.authenticated_service_aid !== service)
232
+ throw this.error('unavailable', 'Storage authoritative service identity unavailable');
233
+ if (value.consistency !== 'authoritative' || Object.hasOwn(value, 'recovery_ready') && value.recovery_ready !== true || value.owner_aid !== ownerAid || value.bucket !== 'default' || value.path !== path || typeof value.exists !== 'boolean' || typeof value.revision !== 'string' || !value.revision || !Number.isSafeInteger(observed) || observed < now - 30 || observed > now + 5 || observed <= 0)
234
+ throw this.error('unavailable', 'Storage authoritative namespace, revision or freshness unavailable');
235
+ if (value.exists && (typeof value.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(value.sha256) || typeof value.object_id !== 'string' || !value.object_id || !Number.isSafeInteger(value.version) || Number(value.version) <= 0))
236
+ throw this.error('unavailable', 'Storage authoritative object metadata unavailable');
237
+ return { owner_aid: ownerAid, bucket: 'default', path, exists: value.exists, revision: value.revision,
238
+ observed_at: observed, sha256: value.exists ? String(value.sha256) : '', content: '', authoritative: true,
239
+ service_aid: service, authenticated_service_aid: service };
240
+ }
241
+ async readFile(ownerAid, path) {
242
+ ownershipAid(ownerAid, 'owner_aid');
243
+ const began = Math.trunc(this.deps.now?.() ?? Date.now() / 1000);
244
+ const params = { owner_aid: ownerAid, bucket: 'default', object_key: path, consistency: 'authoritative' };
245
+ const beforeRaw = await this.request('storage.head_object', params);
246
+ const before = this.metadata(beforeRaw, ownerAid, path);
247
+ if (!before.exists)
248
+ return before;
249
+ const body = await this.request('storage.get_object', { ...params, expected_revision: before.revision });
250
+ const current = this.metadata(body, ownerAid, path);
251
+ if (!current.exists || current.revision !== before.revision || current.sha256 !== before.sha256 || body.object_id !== beforeRaw.object_id || body.version !== beforeRaw.version)
252
+ throw this.error('unavailable', 'Storage ownership body revision changed');
253
+ if (typeof body.content !== 'string')
254
+ throw this.error('unavailable', 'Storage ownership body is unavailable');
255
+ if (body.content !== '' && !isStrictBase64(body.content))
256
+ throw this.error('unavailable', 'Storage ownership body is not strict Base64');
257
+ const bytes = base64ToUint8(body.content);
258
+ if (await ownershipSha256(bytes) !== before.sha256)
259
+ throw this.error('unavailable', 'Storage ownership body digest mismatch');
260
+ let content;
261
+ try {
262
+ content = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
263
+ }
264
+ catch {
265
+ throw this.error('invalid', 'Storage ownership file is not UTF-8');
266
+ }
267
+ const afterRaw = await this.request('storage.head_object', params);
268
+ const after = this.metadata(afterRaw, ownerAid, path);
269
+ if (!after.exists || after.revision !== before.revision || after.sha256 !== before.sha256 || afterRaw.object_id !== beforeRaw.object_id || afterRaw.version !== beforeRaw.version || Math.trunc(this.deps.now?.() ?? Date.now() / 1000) - began > 30)
270
+ throw this.error('unavailable', 'Storage ownership changed during authoritative read');
271
+ return { ...after, content };
272
+ }
273
+ async readOwnerMd(ownerAid, subAid) {
274
+ return await this.readFile(ownerAid, `sub_aids/${ownershipAid(subAid, 'sub_aid')}/owner.md`);
275
+ }
276
+ async writeOwnerMd(ownerAid, subAid, raw, expectedRevision) {
277
+ if (ownerAid !== this.deps.currentAid())
278
+ throw this.error('forbidden', 'only the authenticated owner may publish owner.md');
279
+ if (typeof expectedRevision !== 'string' || !expectedRevision)
280
+ throw this.error('invalid', 'ownership expected_revision is required');
281
+ const bytes = new TextEncoder().encode(raw);
282
+ let binary = '';
283
+ for (const byte of bytes)
284
+ binary += String.fromCharCode(byte);
285
+ await this.request('storage.put_object', { owner_aid: ownershipAid(ownerAid, 'owner_aid'), bucket: 'default', object_key: `sub_aids/${ownershipAid(subAid, 'sub_aid')}/owner.md`, content: btoa(binary), content_type: 'text/markdown', is_private: false, overwrite: true, expected_revision: expectedRevision });
286
+ return await this.readOwnerMd(ownerAid, subAid);
287
+ }
288
+ async deleteOwnerMd(ownerAid, subAid, expectedRevision) {
289
+ if (ownerAid !== this.deps.currentAid())
290
+ throw this.error('forbidden', 'only the authenticated owner may delete owner.md');
291
+ if (typeof expectedRevision !== 'string' || !expectedRevision)
292
+ throw this.error('invalid', 'ownership expected_revision is required');
293
+ const path = `sub_aids/${ownershipAid(subAid, 'sub_aid')}/owner.md`;
294
+ await this.request('storage.delete_object', { owner_aid: ownershipAid(ownerAid, 'owner_aid'), bucket: 'default', object_key: path, expected_revision: expectedRevision });
295
+ const after = this.metadata(await this.request('storage.head_object', { owner_aid: ownerAid, bucket: 'default', object_key: path, consistency: 'authoritative' }), ownerAid, path);
296
+ if (after.exists || after.revision === expectedRevision)
297
+ throw this.error('conflict', 'Storage ownership deletion could not be confirmed');
298
+ }
299
+ }
300
+ export function requireOwnershipApplicable(type) {
301
+ if (type === 'group' || type === 'org')
302
+ throw new StateError('group/org use their dedicated lifecycle ownership', { stringCode: 'not_applicable', retryable: false });
303
+ }
304
+ export class OwnershipFiles {
305
+ deps;
306
+ constructor(deps) {
307
+ this.deps = deps;
308
+ }
309
+ now() { return Math.trunc(this.deps.now?.() ?? Date.now() / 1000); }
310
+ fresh(observedAt) {
311
+ const now = this.now();
312
+ if (!Number.isSafeInteger(observedAt) || observedAt <= 0 || observedAt < now - 30 || observedAt > now + 5)
313
+ throw new StateError('ownership authoritative material is unavailable or stale');
314
+ }
315
+ validateAgentSnapshot(snapshot, subAid) {
316
+ if (snapshot.protocol !== '2.1')
317
+ throw new StateError('NS ownership 2.1 protocol capability is unavailable', { stringCode: 'unsupported_protocol', retryable: true });
318
+ if (snapshot.sub_aid !== subAid || Object.hasOwn(snapshot, 'recovery_ready') && snapshot.recovery_ready !== true || !['locked', 'unlocked'].includes(snapshot.lock) || typeof snapshot.exists !== 'boolean' || !Number.isSafeInteger(snapshot.relation_version) || snapshot.relation_version < 0)
319
+ throw new StateError('NS ownership 2.1 authoritative metadata is unavailable', { stringCode: 'unavailable', retryable: true });
320
+ if (snapshot.exists && !snapshot.etag || !snapshot.exists && (snapshot.lock !== 'unlocked' || snapshot.relation_version !== 0))
321
+ throw new StateError('NS ownership document or ledger is unavailable');
322
+ this.fresh(snapshot.observed_at);
323
+ }
324
+ async validateOwnerSnapshot(snapshot, ownerAid, subAid) {
325
+ const expectedService = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
326
+ if (snapshot.error || snapshot.authoritative !== true || Object.hasOwn(snapshot, 'recovery_ready') && snapshot.recovery_ready !== true || snapshot.owner_aid !== ownerAid || snapshot.bucket !== 'default' || snapshot.path !== `sub_aids/${subAid}/owner.md` || snapshot.service_aid !== expectedService || snapshot.authenticated_service_aid !== expectedService || typeof snapshot.revision !== 'string' || !snapshot.revision || typeof snapshot.exists !== 'boolean')
327
+ throw new StateError('Storage authoritative ownership object is unavailable');
328
+ this.fresh(snapshot.observed_at);
329
+ if (snapshot.exists && (!/^[0-9a-f]{64}$/.test(snapshot.sha256) || await ownershipSha256(snapshot.content) !== snapshot.sha256))
330
+ throw new StateError('Storage ownership body and metadata differ', { stringCode: 'unavailable', retryable: true });
331
+ }
332
+ async readAgent(subAid) {
333
+ if (this.deps.currentAidType && subAid === this.deps.currentAid().aid)
334
+ requireOwnershipApplicable(this.deps.currentAidType());
335
+ const snapshot = await this.deps.readAgentMdFresh(ownershipAid(subAid, 'sub_aid'));
336
+ if (snapshot.exists && typeof snapshot.content === 'string')
337
+ requireOwnershipApplicable(parseFileFrontmatter(parseAgentMdTailSignature(snapshot.content).payload).type);
338
+ this.validateAgentSnapshot(snapshot, subAid);
339
+ return snapshot;
340
+ }
341
+ async readOwner(ownerAid, subAid) {
342
+ const snapshot = await this.deps.readOwnerMd(ownershipAid(ownerAid, 'owner_aid'), ownershipAid(subAid, 'sub_aid'));
343
+ await this.validateOwnerSnapshot(snapshot, ownerAid, subAid);
344
+ return snapshot;
345
+ }
346
+ async verify(content, role, subAid, ownerAid) {
347
+ if (role === 'agent') {
348
+ const meta = parseFileFrontmatter(parseAgentMdTailSignature(content).payload);
349
+ requireOwnershipApplicable(meta.type);
350
+ if ('ownership_proof' in meta || 'ownership_release' in meta || 'owner' in meta && !('ownership' in meta))
351
+ throw new StateError('legacy ownership requires explicit migration', { stringCode: 'unsupported_protocol', retryable: true });
352
+ }
353
+ const file = parseOwnershipFile(content, role, { expectedSubAid: subAid, expectedOwnerAid: role === 'owner' ? ownerAid : undefined, now: this.now() });
354
+ const signer = role === 'agent' ? subAid : ownerAid;
355
+ const certificate = await this.deps.resolveCertificate(signer, file.signature.cert_fingerprint, role === 'agent' && file.frontmatter.type === 'group' ? 'group_identity' : undefined);
356
+ return await verifyOwnershipFile(content, role, certificate, { expectedSubAid: subAid, expectedOwnerAid: role === 'owner' ? ownerAid : undefined, certificateAid: signer, now: this.now() });
357
+ }
358
+ async checkConsensus(subAid = this.deps.currentAid().aid, pendingTarget) {
359
+ const began = this.now();
360
+ const result = { status: 'unavailable', sub_aid: subAid, owner_aid: null, relation_version: 0, lock: 'unlocked', agent_md_etag: '', agent_md_sha256: '', owner_md_revision: '', owner_md_sha256: '' };
361
+ try {
362
+ const first = await this.readAgent(subAid);
363
+ Object.assign(result, { relation_version: first.relation_version, lock: first.lock, agent_md_etag: first.etag, agent_md_sha256: first.exists ? await ownershipSha256(first.content) : '' });
364
+ const released = first.lock === 'unlocked' && first.relation_version > 0;
365
+ const agent = !released && first.exists ? await this.verify(first.content, 'agent', subAid) : null;
366
+ if (first.lock === 'unlocked' && first.relation_version === 0 && agent?.statement)
367
+ throw new StateError('NS relation metadata does not match agent.md', { stringCode: 'unavailable', retryable: true });
368
+ const ownerAid = released ? undefined : agent?.statement?.owner_aid ?? pendingTarget?.owner_aid;
369
+ let owner = null;
370
+ let object = null;
371
+ if (ownerAid) {
372
+ object = await this.readOwner(ownerAid, subAid);
373
+ result.owner_md_revision = object.revision;
374
+ result.owner_md_sha256 = object.exists ? object.sha256 : '';
375
+ if (object.exists)
376
+ owner = await this.verify(object.content, 'owner', subAid, ownerAid);
377
+ }
378
+ const last = await this.readAgent(subAid);
379
+ if (last.relation_version !== first.relation_version || last.etag !== first.etag || last.lock !== first.lock || last.exists !== first.exists || last.owner_aid !== first.owner_aid || first.exists && await ownershipSha256(last.content) !== result.agent_md_sha256)
380
+ throw new StateError('NS ownership changed during verification');
381
+ this.fresh(began);
382
+ this.fresh(first.observed_at);
383
+ if (object)
384
+ this.fresh(object.observed_at);
385
+ if (released) {
386
+ result.status = 'released';
387
+ }
388
+ else if (first.lock === 'locked') {
389
+ if (!agent?.statement || agent.statement.relation_version !== first.relation_version || first.owner_aid !== undefined && first.owner_aid !== agent.statement.owner_aid)
390
+ throw new StateError('ownership current files do not match the NS lock', { stringCode: 'unavailable', retryable: true });
391
+ if (!owner)
392
+ throw new StateError('NS has not confirmed owner object absence');
393
+ if (!owner.statement || !ownershipStatementsMatch(agent.statement, owner.statement))
394
+ throw new ValidationError('ownership current files do not match the NS lock');
395
+ if (owner.action === 'release')
396
+ throw new StateError('NS has not confirmed the current release');
397
+ result.status = 'consensus';
398
+ result.owner_aid = agent.statement.owner_aid;
399
+ }
400
+ else if (pendingTarget && owner?.action === 'declare' && owner.statement?.relation_version === pendingTarget.relation_version && pendingTarget.relation_version === first.relation_version + 1) {
401
+ result.status = 'single_sided';
402
+ }
403
+ else
404
+ result.status = 'unbound';
405
+ }
406
+ catch (error) {
407
+ result.status = error instanceof StateError && error.stringCode === 'not_applicable' ? 'not_applicable' : error instanceof ValidationError || error?.stringCode === 'invalid' ? 'invalid' : 'unavailable';
408
+ result.owner_aid = null;
409
+ result.code = error instanceof StateError && error.stringCode ? error.stringCode : result.status;
410
+ result.reason = error instanceof Error ? error.message : String(error);
411
+ result.retryable = result.status === 'unavailable';
412
+ }
413
+ return result;
414
+ }
415
+ local(aid) {
416
+ const identity = this.deps.currentAid();
417
+ if (identity.aid !== aid)
418
+ throw new ValidationError('ownership file requires its own authenticated identity');
419
+ return identity;
420
+ }
421
+ statement(ownerAid, subAid, version) {
422
+ return { version: 2, declaration_id: crypto.randomUUID(), owner_aid: ownerAid, sub_aid: subAid, relation_version: ownershipInteger(version, 'relation_version'), declared_at: this.now() };
423
+ }
424
+ async requireBase(task) {
425
+ const snapshot = await this.readAgent(task.sub_aid);
426
+ if (snapshot.relation_version !== task.base_relation_version || snapshot.lock !== (task.operation === 'bind' ? 'unlocked' : 'locked'))
427
+ throw new StateError('ownership relation version conflict', { stringCode: 'ownership_conflict', retryable: false });
428
+ if (!snapshot.exists)
429
+ throw new StateError('initial agent.md is not published', { stringCode: 'unavailable', retryable: true });
430
+ if (snapshot.exists) {
431
+ const file = task.operation === 'bind' && snapshot.relation_version > 0 ? parseOwnershipFile(snapshot.content, 'agent', { expectedSubAid: task.sub_aid }) : await this.verify(snapshot.content, 'agent', task.sub_aid);
432
+ if (snapshot.lock === 'unlocked' && snapshot.relation_version === 0 && file.statement)
433
+ throw new StateError('NS relation metadata does not match agent.md', { stringCode: 'unavailable', retryable: true });
434
+ if (task.operation !== 'bind' && file.statement?.owner_aid !== task.owner_aid || task.operation === 'bind' && file.statement && file.statement.relation_version >= snapshot.relation_version)
435
+ throw new StateError('ownership current owner mismatch', { stringCode: 'conflict', retryable: false });
436
+ }
437
+ return snapshot;
438
+ }
439
+ async verifyTransfer(task) {
440
+ await this.requireBase(task);
441
+ const object = await this.readOwner(task.owner_aid, task.sub_aid);
442
+ if (!object.exists)
443
+ throw new StateError('ownership transfer authorization is unavailable');
444
+ const file = await this.verify(object.content, 'owner', task.sub_aid, task.owner_aid);
445
+ if (file.action !== 'transfer_owner' || file.statement?.relation_version !== task.base_relation_version || !file.transfer || file.transfer.new_owner_aid !== task.new_owner_aid || file.transfer.target_relation_version !== task.target_relation_version)
446
+ throw new StateError('ownership transfer authorization target changed', { stringCode: 'conflict', retryable: false });
447
+ if (file.transfer.expires_at <= this.now())
448
+ throw new StateError('ownership transfer authorization expired', { stringCode: 'conflict', retryable: false });
449
+ if (task.authorization_sha256 !== undefined && task.authorization_sha256 !== object.sha256 || task.expires_at !== undefined && task.expires_at !== file.transfer.expires_at)
450
+ throw new StateError('ownership transfer authorization changed', { stringCode: 'conflict', retryable: false });
451
+ if (task.authorization_sha256 === undefined || task.expires_at === undefined) {
452
+ task.authorization_sha256 = object.sha256;
453
+ task.expires_at = file.transfer.expires_at;
454
+ await task.persist();
455
+ }
456
+ return file;
457
+ }
458
+ async publishOwner(task, key, action, ownerAid, version) {
459
+ const identity = this.local(ownerAid);
460
+ await this.readAgent(task.sub_aid);
461
+ const current = await this.readOwner(ownerAid, task.sub_aid);
462
+ let prepared = task.files[key];
463
+ if (!prepared) {
464
+ await this.requireBase(task);
465
+ if (action === 'transfer_owner')
466
+ task.expires_at = this.now() + 3600;
467
+ const ownership = { statement: this.statement(ownerAid, task.sub_aid, version), action,
468
+ ...(action === 'transfer_owner' ? { transfer: { new_owner_aid: task.new_owner_aid, target_relation_version: task.target_relation_version, expires_at: task.expires_at } } : {}) };
469
+ const payload = `---\nowner_aid: ${ownerAid}\nsub_aid: ${task.sub_aid}\nownership: ${JSON.stringify(ownership)}\n---\n`;
470
+ const raw = await signOwnershipFile(identity, payload, 'owner', { now: this.now() });
471
+ await this.verify(raw, 'owner', task.sub_aid, ownerAid);
472
+ prepared = { raw, sha256: await ownershipSha256(raw), condition: { expected_revision: current.revision } };
473
+ if (action === 'transfer_owner')
474
+ prepared.expires_at = task.expires_at;
475
+ task.files[key] = prepared;
476
+ await task.persist();
477
+ }
478
+ if (!prepared.condition || typeof prepared.condition.expected_revision !== 'string' || !prepared.condition.expected_revision)
479
+ throw new StateError('ownership original Storage revision is unavailable', { stringCode: 'ownership_conflict' });
480
+ if (current.exists && current.sha256 === prepared.sha256 && current.content === prepared.raw) {
481
+ if (prepared.published_revision !== undefined && prepared.published_revision !== current.revision)
482
+ throw new StateError('ownership published file revision changed', { stringCode: 'ownership_conflict' });
483
+ prepared.published_revision = current.revision;
484
+ await task.persist();
485
+ return prepared;
486
+ }
487
+ await this.requireBase(task);
488
+ if (current.revision !== prepared.condition.expected_revision)
489
+ throw new StateError('ownership Storage revision conflict', { stringCode: 'ownership_conflict' });
490
+ if (action === 'transfer_owner' && (!task.expires_at || task.expires_at <= this.now()))
491
+ throw new StateError('ownership transfer authorization expired', { stringCode: 'conflict', retryable: false });
492
+ await this.verify(prepared.raw, 'owner', task.sub_aid, ownerAid);
493
+ await task.guard?.();
494
+ await this.deps.writeOwnerMd(ownerAid, task.sub_aid, prepared.raw, prepared.condition.expected_revision);
495
+ const saved = await this.readOwner(ownerAid, task.sub_aid);
496
+ if (!saved.exists || saved.sha256 !== prepared.sha256 || saved.content !== prepared.raw)
497
+ throw new StateError('ownership published file could not be confirmed');
498
+ prepared.published_revision = saved.revision;
499
+ await task.persist();
500
+ return prepared;
501
+ }
502
+ async declareSubaid(task) {
503
+ const owner = task.operation === 'transfer' ? task.new_owner_aid : task.owner_aid;
504
+ if (task.operation === 'transfer')
505
+ await this.verifyTransfer(task);
506
+ return await this.publishOwner(task, 'owner', 'declare', owner, task.target_relation_version);
507
+ }
508
+ async revokeSubaid(task) {
509
+ return await this.publishOwner(task, 'release', 'release', task.owner_aid, task.base_relation_version);
510
+ }
511
+ async transferOwneraid(task) {
512
+ if (!task.new_owner_aid || task.new_owner_aid === task.owner_aid)
513
+ throw new ValidationError('ownership transfer target must differ from current owner');
514
+ return await this.publishOwner(task, 'transfer', 'transfer_owner', task.owner_aid, task.base_relation_version);
515
+ }
516
+ async publishAgent(task, cleanup) {
517
+ const identity = this.local(task.sub_aid), key = cleanup ? 'cleanup' : 'agent';
518
+ const current = await this.readAgent(task.sub_aid);
519
+ let prepared = task.files[key];
520
+ if (!prepared) {
521
+ if (cleanup) {
522
+ if (current.lock !== 'unlocked' || current.relation_version !== task.target_relation_version)
523
+ throw new StateError('owner_locked or ownership cleanup version conflict');
524
+ const prior = current.exists ? parseOwnershipFile(current.content, 'agent', { expectedSubAid: task.sub_aid, now: this.now() }) : null;
525
+ if (prior?.statement && (prior.statement.owner_aid !== task.owner_aid || prior.statement.relation_version !== task.base_relation_version))
526
+ throw new StateError('ownership cleanup historical authorization mismatch', { stringCode: 'conflict', retryable: false });
527
+ }
528
+ else {
529
+ await this.requireBase(task);
530
+ if (task.operation === 'transfer')
531
+ await this.verifyTransfer(task);
532
+ const targetOwner = task.operation === 'transfer' ? task.new_owner_aid : task.owner_aid;
533
+ const target = await this.readOwner(targetOwner, task.sub_aid);
534
+ if (!target.exists)
535
+ throw new StateError('ownership target declaration is unavailable');
536
+ const owner = await this.verify(target.content, 'owner', task.sub_aid, targetOwner);
537
+ if (owner.action === 'release' || owner.statement?.relation_version !== task.target_relation_version)
538
+ throw new StateError('ownership target declaration mismatch', { stringCode: 'conflict', retryable: false });
539
+ }
540
+ const ownerAid = task.operation === 'transfer' ? task.new_owner_aid : task.owner_aid;
541
+ const payload = buildOwnershipAgentMdV21(current.exists ? current.content : buildDefaultAgentMd(task.sub_aid), cleanup ? undefined : this.statement(ownerAid, task.sub_aid, task.target_relation_version));
542
+ const raw = await signOwnershipFile(identity, payload, 'agent', { now: this.now() });
543
+ await this.verify(raw, 'agent', task.sub_aid);
544
+ prepared = { raw, sha256: await ownershipSha256(raw), condition: current.exists ? { if_match: current.etag } : { if_none_match: '*' } };
545
+ task.files[key] = prepared;
546
+ await task.persist();
547
+ }
548
+ if (!prepared.condition || !(typeof prepared.condition.if_match === 'string' && prepared.condition.if_match || prepared.condition.if_none_match === '*'))
549
+ throw new StateError('ownership original agent.md condition is unavailable', { stringCode: 'ownership_conflict' });
550
+ if (current.exists && await ownershipSha256(current.content) === prepared.sha256 && current.content === prepared.raw && current.relation_version === task.target_relation_version) {
551
+ if (prepared.published_etag !== undefined && prepared.published_etag !== current.etag)
552
+ throw new StateError('ownership published agent ETag changed', { stringCode: 'ownership_conflict' });
553
+ prepared.published_etag = current.etag;
554
+ await task.persist();
555
+ return prepared;
556
+ }
557
+ if (current.exists ? current.etag !== prepared.condition.if_match : prepared.condition.if_none_match !== '*')
558
+ throw new StateError('ownership agent.md condition conflict', { stringCode: 'ownership_conflict' });
559
+ if (cleanup) {
560
+ if (current.lock !== 'unlocked' || current.relation_version !== task.target_relation_version)
561
+ throw new StateError('owner_locked or ownership cleanup version conflict');
562
+ }
563
+ else {
564
+ await this.requireBase(task);
565
+ if (task.operation === 'transfer')
566
+ await this.verifyTransfer(task);
567
+ }
568
+ await this.verify(prepared.raw, 'agent', task.sub_aid);
569
+ await task.guard?.();
570
+ const saved = await this.deps.submitAgentMdRaw(task.sub_aid, prepared.raw, prepared.condition);
571
+ this.validateAgentSnapshot(saved, task.sub_aid);
572
+ if (!saved.exists || await ownershipSha256(saved.content) !== prepared.sha256 || saved.relation_version !== task.target_relation_version)
573
+ throw new StateError('NS ownership submitted file could not be confirmed');
574
+ prepared.published_etag = saved.etag;
575
+ await task.persist();
576
+ return prepared;
577
+ }
578
+ async declareOwneraid(task) { return await this.publishAgent(task, false); }
579
+ async revokeOwneraid(task) { return await this.publishAgent(task, true); }
580
+ async cleanupOwnerFile(task) {
581
+ this.local(task.owner_aid);
582
+ await this.readAgent(task.sub_aid);
583
+ const prepared = task.files[task.operation === 'release' ? 'release' : 'transfer'];
584
+ if (!prepared?.published_revision || prepared.cleaned)
585
+ return;
586
+ const consensus = await this.checkConsensus(task.sub_aid);
587
+ if (consensus.status === 'not_applicable')
588
+ throw new StateError(consensus.reason || 'group/org use their dedicated lifecycle ownership', { stringCode: 'not_applicable', retryable: false });
589
+ if (consensus.status === 'invalid' || consensus.status === 'unavailable')
590
+ throw new StateError(consensus.reason || 'ownership cleanup verification failed', { stringCode: consensus.status, retryable: consensus.status === 'unavailable' });
591
+ if (consensus.relation_version !== task.target_relation_version || (task.operation === 'release' ? consensus.status !== 'released' : consensus.status !== 'consensus' || consensus.owner_aid !== task.new_owner_aid))
592
+ throw new StateError('ownership cleanup relation is not confirmed', { stringCode: 'conflict', retryable: false });
593
+ const object = await this.readOwner(task.owner_aid, task.sub_aid);
594
+ if (!object.exists) {
595
+ prepared.cleaned = true;
596
+ await task.persist();
597
+ return;
598
+ }
599
+ if (object.revision !== prepared.published_revision || object.sha256 !== prepared.sha256)
600
+ throw new StateError('ownership cleanup revision conflict');
601
+ if (!this.deps.deleteOwnerMd)
602
+ throw new StateError('ownership conditional delete unsupported');
603
+ await task.guard?.();
604
+ await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid, prepared.published_revision);
605
+ prepared.cleaned = true;
606
+ await task.persist();
607
+ }
608
+ }
609
+ //# sourceMappingURL=ownership-v21.js.map