@agentunion/fastaun-browser 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 (149) 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 +17 -3
  48. package/dist/agent-md-schema.d.ts.map +1 -1
  49. package/dist/agent-md-schema.js +94 -26
  50. package/dist/agent-md-schema.js.map +1 -1
  51. package/dist/agent-md.d.ts +54 -3
  52. package/dist/agent-md.d.ts.map +1 -1
  53. package/dist/agent-md.js +711 -65
  54. package/dist/agent-md.js.map +1 -1
  55. package/dist/aid-store.d.ts +58 -46
  56. package/dist/aid-store.d.ts.map +1 -1
  57. package/dist/aid-store.js +254 -248
  58. package/dist/aid-store.js.map +1 -1
  59. package/dist/aid.d.ts +4 -1
  60. package/dist/aid.d.ts.map +1 -1
  61. package/dist/aid.js +27 -33
  62. package/dist/aid.js.map +1 -1
  63. package/dist/auth.d.ts +4 -1
  64. package/dist/auth.d.ts.map +1 -1
  65. package/dist/auth.js +426 -39
  66. package/dist/auth.js.map +1 -1
  67. package/dist/bundle.js +38179 -32054
  68. package/dist/cert-utils.d.ts +50 -2
  69. package/dist/cert-utils.d.ts.map +1 -1
  70. package/dist/cert-utils.js +105 -47
  71. package/dist/cert-utils.js.map +1 -1
  72. package/dist/client/delivery.d.ts +22 -1
  73. package/dist/client/delivery.d.ts.map +1 -1
  74. package/dist/client/delivery.js +389 -78
  75. package/dist/client/delivery.js.map +1 -1
  76. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  77. package/dist/client/rpc-pipeline.js +103 -77
  78. package/dist/client/rpc-pipeline.js.map +1 -1
  79. package/dist/client/v2-e2ee.d.ts +1 -0
  80. package/dist/client/v2-e2ee.d.ts.map +1 -1
  81. package/dist/client/v2-e2ee.js +182 -89
  82. package/dist/client/v2-e2ee.js.map +1 -1
  83. package/dist/client.d.ts +100 -1
  84. package/dist/client.d.ts.map +1 -1
  85. package/dist/client.js +1220 -25
  86. package/dist/client.js.map +1 -1
  87. package/dist/crypto.d.ts.map +1 -1
  88. package/dist/crypto.js +8 -6
  89. package/dist/crypto.js.map +1 -1
  90. package/dist/errors.d.ts.map +1 -1
  91. package/dist/errors.js +3 -2
  92. package/dist/errors.js.map +1 -1
  93. package/dist/events.d.ts +9 -2
  94. package/dist/events.d.ts.map +1 -1
  95. package/dist/events.js +72 -13
  96. package/dist/events.js.map +1 -1
  97. package/dist/facades.d.ts +82 -2
  98. package/dist/facades.d.ts.map +1 -1
  99. package/dist/facades.js +100 -6
  100. package/dist/facades.js.map +1 -1
  101. package/dist/index.d.ts +7 -3
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/index.js +3 -2
  104. package/dist/index.js.map +1 -1
  105. package/dist/keystore/index.d.ts +17 -0
  106. package/dist/keystore/index.d.ts.map +1 -1
  107. package/dist/keystore/indexeddb-shared.d.ts +10 -1
  108. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  109. package/dist/keystore/indexeddb-shared.js +111 -0
  110. package/dist/keystore/indexeddb-shared.js.map +1 -1
  111. package/dist/keystore/indexeddb-token-store.d.ts +7 -1
  112. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  113. package/dist/keystore/indexeddb-token-store.js +30 -1
  114. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  115. package/dist/org-protocol.d.ts +98 -0
  116. package/dist/org-protocol.d.ts.map +1 -0
  117. package/dist/org-protocol.js +1802 -0
  118. package/dist/org-protocol.js.map +1 -0
  119. package/dist/ownership-proof.d.ts +93 -5
  120. package/dist/ownership-proof.d.ts.map +1 -1
  121. package/dist/ownership-proof.js +239 -53
  122. package/dist/ownership-proof.js.map +1 -1
  123. package/dist/ownership-v21.d.ts +194 -0
  124. package/dist/ownership-v21.d.ts.map +1 -0
  125. package/dist/ownership-v21.js +678 -0
  126. package/dist/ownership-v21.js.map +1 -0
  127. package/dist/ownership-workflow.d.ts +151 -0
  128. package/dist/ownership-workflow.d.ts.map +1 -0
  129. package/dist/ownership-workflow.js +1229 -0
  130. package/dist/ownership-workflow.js.map +1 -0
  131. package/dist/seq-tracker.d.ts +1 -0
  132. package/dist/seq-tracker.d.ts.map +1 -1
  133. package/dist/seq-tracker.js +10 -2
  134. package/dist/seq-tracker.js.map +1 -1
  135. package/dist/tools/cross-sdk-agent.d.ts +83 -0
  136. package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
  137. package/dist/tools/cross-sdk-agent.js +415 -7
  138. package/dist/tools/cross-sdk-agent.js.map +1 -1
  139. package/dist/transport.d.ts +7 -0
  140. package/dist/transport.d.ts.map +1 -1
  141. package/dist/transport.js +90 -2
  142. package/dist/transport.js.map +1 -1
  143. package/dist/v2/e2ee/decrypt.d.ts +1 -0
  144. package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
  145. package/dist/v2/e2ee/decrypt.js +9 -1
  146. package/dist/v2/e2ee/decrypt.js.map +1 -1
  147. package/dist/version.d.ts +1 -1
  148. package/dist/version.js +1 -1
  149. package/package.json +1 -1
@@ -0,0 +1,1229 @@
1
+ import { isScalar, parseDocument } from 'yaml';
2
+ import { StateError, ValidationError } from './errors.js';
3
+ import { OWNERSHIP_UUID_V4_RE, ownershipAid, ownershipInteger, ownershipSha256, parseOwnershipFile, requireOwnershipApplicable } from './ownership-v21.js';
4
+ export const OWNERSHIP_SDK_MESSAGE_TYPE = 'aun.ownership.sdk.v1';
5
+ export function parseOwnershipRegistrationIntent(raw, aid) {
6
+ try {
7
+ const intent = JSON.parse(raw);
8
+ if (!intent || typeof intent !== 'object' || Array.isArray(intent) || intent.schema_version !== 1 || intent.sub_aid !== aid || typeof intent.type !== 'string' || !intent.type || typeof intent.owner !== 'string' || typeof intent.exttype !== 'string' || typeof intent.pending !== 'boolean')
9
+ throw new Error('invalid registration intent');
10
+ const entity = intent.type === 'group' || intent.type === 'org';
11
+ const keys = ['schema_version', 'sub_aid', 'type', 'owner', 'exttype', 'pending', entity ? 'protocol' : 'operation_id'];
12
+ if (Object.keys(intent).some((key) => !keys.includes(key)) || keys.some((key) => !Object.hasOwn(intent, key)))
13
+ throw new Error('invalid registration fields');
14
+ if (entity ? intent.protocol !== 'entity' || intent.pending || !intent.owner : typeof intent.operation_id !== 'string' || !OWNERSHIP_UUID_V4_RE.test(intent.operation_id))
15
+ throw new Error('invalid registration protocol');
16
+ ownershipAid(intent.sub_aid, 'sub_aid');
17
+ if (intent.owner)
18
+ ownershipAid(intent.owner, 'owner_aid');
19
+ if (intent.type === 'human' && intent.owner || intent.type === 'agent' && !intent.owner || intent.pending && !intent.owner)
20
+ throw new Error('invalid registration owner');
21
+ return intent;
22
+ }
23
+ catch (error) {
24
+ throw failure('persistence_unavailable', `ownership registration intent is damaged or legacy: ${String(error)}`);
25
+ }
26
+ }
27
+ function failure(code, reason, retryable = false) {
28
+ return new StateError(reason, { stringCode: code, retryable });
29
+ }
30
+ function participants(task) {
31
+ return [...new Set([task.owner_aid, task.sub_aid, ...(task.new_owner_aid ? [task.new_owner_aid] : [])])];
32
+ }
33
+ function targetOwner(task) {
34
+ return task.operation === 'transfer' ? task.new_owner_aid : task.owner_aid;
35
+ }
36
+ export function isOwnershipSdkPayload(value) {
37
+ return Boolean(value && typeof value === 'object' && value.type === OWNERSHIP_SDK_MESSAGE_TYPE);
38
+ }
39
+ export function ownershipLocalRoles(value, localAid) {
40
+ const body = value;
41
+ const roles = [];
42
+ if (body?.sub_aid === localAid)
43
+ roles.push('sub');
44
+ if (body?.owner_aid === localAid)
45
+ roles.push('owner');
46
+ if (body?.new_owner_aid === localAid)
47
+ roles.push('new_owner');
48
+ return roles;
49
+ }
50
+ export function parseOwnershipMessage(content) {
51
+ let value = content;
52
+ if (typeof content === 'string') {
53
+ try {
54
+ value = JSON.parse(content);
55
+ }
56
+ catch {
57
+ throw new ValidationError('ownership message must be JSON');
58
+ }
59
+ const document = parseDocument(content, { uniqueKeys: true });
60
+ if (document.errors.length)
61
+ throw new ValidationError('ownership message has duplicate or invalid fields');
62
+ for (const field of ['base_relation_version', 'target_relation_version']) {
63
+ const node = document.get(field, true);
64
+ if (!isScalar(node) || !/^(?:0|[1-9][0-9]*)$/.test(node.source ?? ''))
65
+ throw new ValidationError(`${field} must use an integer`);
66
+ }
67
+ }
68
+ if (!value || typeof value !== 'object' || Array.isArray(value))
69
+ throw new ValidationError('ownership message must be an object');
70
+ const message = value;
71
+ const required = ['type', 'operation_id', 'operation', 'action', 'sub_aid', 'owner_aid', 'base_relation_version', 'target_relation_version'];
72
+ const optional = ['new_owner_aid', 'reason'];
73
+ if (required.some((key) => !Object.hasOwn(message, key)) || Object.keys(message).some((key) => !required.includes(key) && !optional.includes(key)))
74
+ throw new ValidationError('ownership message contains unknown or missing fields');
75
+ if (message.type !== OWNERSHIP_SDK_MESSAGE_TYPE)
76
+ throw failure('unsupported_protocol', 'legacy ownership control is unsupported');
77
+ if (typeof message.operation_id !== 'string' || !OWNERSHIP_UUID_V4_RE.test(message.operation_id))
78
+ throw new ValidationError('operation_id must be a UUIDv4');
79
+ if (!['bind', 'release', 'transfer'].includes(message.operation))
80
+ throw new ValidationError('ownership operation is invalid');
81
+ ownershipAid(message.sub_aid, 'sub_aid');
82
+ ownershipAid(message.owner_aid, 'owner_aid');
83
+ ownershipInteger(message.base_relation_version, 'base_relation_version', 0);
84
+ if (ownershipInteger(message.target_relation_version, 'target_relation_version') !== message.base_relation_version + (message.operation === 'release' ? 0 : 1))
85
+ throw new ValidationError('ownership target version does not match operation');
86
+ if (message.operation === 'transfer') {
87
+ if (ownershipAid(message.new_owner_aid, 'new_owner_aid') === message.owner_aid)
88
+ throw new ValidationError('transfer target must differ from current owner');
89
+ }
90
+ else if ('new_owner_aid' in message)
91
+ throw new ValidationError('new_owner_aid is only allowed for transfer');
92
+ const actions = {
93
+ bind: ['bind.request', 'bind.accept', 'bind.reject', 'owner.ready', 'relation.confirmed', 'operation.stopped'],
94
+ release: ['relation.confirmed', 'sub.cleaned', 'operation.stopped'],
95
+ transfer: ['transfer.request', 'transfer.accept', 'transfer.reject', 'transfer.ready', 'owner.ready', 'relation.confirmed', 'operation.stopped'],
96
+ };
97
+ if (!actions[message.operation].includes(message.action))
98
+ throw new ValidationError('ownership action does not match operation');
99
+ if ('reason' in message && (typeof message.reason !== 'string' || !['bind.reject', 'transfer.reject', 'operation.stopped'].includes(message.action)))
100
+ throw new ValidationError('ownership reason is only allowed for reject or stopped');
101
+ return message;
102
+ }
103
+ export function validateOwnershipMessage(message, context) {
104
+ const from = ownershipAid(context.authenticated_from_aid, 'authenticated_from_aid');
105
+ const local = ownershipAid(context.local_aid, 'local_aid');
106
+ if (from === local || !participants(message).includes(from) || !participants(message).includes(local))
107
+ throw failure('forbidden', 'ownership sender or receiver is not a remote participant');
108
+ const task = context.task;
109
+ if (task && ['operation_id', 'operation', 'sub_aid', 'owner_aid', 'new_owner_aid', 'base_relation_version', 'target_relation_version'].some((key) => task[key] !== message[key]))
110
+ throw failure('conflict', 'ownership fixed task context changed');
111
+ const initiator = task?.initiator_aid;
112
+ let direction = false;
113
+ switch (message.action) {
114
+ case 'bind.request':
115
+ direction = (!initiator || from === initiator) && [message.owner_aid, message.sub_aid].includes(from);
116
+ break;
117
+ case 'bind.accept':
118
+ case 'bind.reject':
119
+ direction = Boolean(initiator && local === initiator && from !== initiator);
120
+ break;
121
+ case 'transfer.request':
122
+ case 'transfer.ready':
123
+ direction = from === message.owner_aid && [message.sub_aid, message.new_owner_aid].includes(local);
124
+ break;
125
+ case 'transfer.accept':
126
+ case 'transfer.reject':
127
+ direction = Boolean(task && local === message.owner_aid && [message.sub_aid, message.new_owner_aid].includes(from));
128
+ break;
129
+ case 'owner.ready':
130
+ direction = Boolean(task && from === targetOwner(message) && local === message.sub_aid);
131
+ break;
132
+ case 'relation.confirmed':
133
+ direction = message.operation === 'release' ? from === message.owner_aid && local === message.sub_aid : Boolean(task && from === message.sub_aid && local !== message.sub_aid);
134
+ break;
135
+ case 'sub.cleaned':
136
+ direction = Boolean(task && from === message.sub_aid && local === message.owner_aid);
137
+ break;
138
+ case 'operation.stopped':
139
+ direction = Boolean(task);
140
+ break;
141
+ }
142
+ if (!direction)
143
+ throw failure('forbidden', 'ownership action has an invalid authenticated direction');
144
+ if (['owner.ready', 'transfer.ready'].includes(message.action) && task?.local_decision !== 'accept')
145
+ throw failure('forbidden', 'ownership local approval is required');
146
+ if (context.ns) {
147
+ const ns = context.ns;
148
+ if (ns.protocol !== '2.1')
149
+ throw failure('unsupported_protocol', 'NS ownership protocol is unavailable');
150
+ const base = ns.relation_version === message.base_relation_version && (message.operation === 'bind' ? ns.lock === 'unlocked' && !ns.owner_aid : ns.lock === 'locked' && ns.owner_aid === message.owner_aid);
151
+ const target = ns.relation_version === message.target_relation_version && (message.operation === 'release' ? ns.lock === 'unlocked' && !ns.owner_aid : ns.lock === 'locked' && ns.owner_aid === targetOwner(message));
152
+ const cleaned = message.action === 'sub.cleaned' && Boolean(task) && ns.relation_version === 0 && ns.lock === 'unlocked' && !ns.owner_aid;
153
+ if (message.action !== 'operation.stopped' && (['relation.confirmed', 'sub.cleaned'].includes(message.action) ? !target && !cleaned : !base && !target))
154
+ throw failure('conflict', 'ownership current NS relation does not match this task');
155
+ }
156
+ }
157
+ export function ownershipTaskResult(task) {
158
+ return {
159
+ operation_id: task.operation_id, operation: task.operation, sub_aid: task.sub_aid, owner_aid: task.owner_aid,
160
+ ...(task.new_owner_aid ? { new_owner_aid: task.new_owner_aid } : {}),
161
+ base_relation_version: task.base_relation_version, target_relation_version: task.target_relation_version,
162
+ status: task.status, step: task.step,
163
+ ...(task.code === undefined ? {} : { code: task.code }),
164
+ ...(task.reason === undefined ? {} : { reason: task.reason }),
165
+ ...(task.retryable === undefined ? {} : { retryable: task.retryable }),
166
+ };
167
+ }
168
+ export function ownershipErrorCode(error) {
169
+ const code = error?.stringCode;
170
+ if (code && ['owner_locked', 'conflict', 'invalid', 'unavailable', 'not_applicable', 'unsupported_protocol', 'persistence_unavailable', 'forbidden', 'operation_not_found'].includes(code))
171
+ return code;
172
+ if (code === 'ownership_conflict')
173
+ return 'conflict';
174
+ const numeric = error?.code;
175
+ if (numeric === -32009)
176
+ return 'conflict';
177
+ if (numeric === -32004)
178
+ return 'forbidden';
179
+ if (error instanceof ValidationError)
180
+ return 'invalid';
181
+ const reason = error instanceof Error ? error.message : String(error);
182
+ if (reason.includes('owner_locked'))
183
+ return 'owner_locked';
184
+ if (/condition conflict|revision conflict|relation version conflict/.test(reason))
185
+ return 'conflict';
186
+ return 'unavailable';
187
+ }
188
+ export class OwnershipWorkflow {
189
+ deps;
190
+ queues = new Map();
191
+ constructor(deps) {
192
+ this.deps = deps;
193
+ }
194
+ now() { return Math.trunc(this.deps.now?.() ?? Date.now() / 1000); }
195
+ requireApplicable(subAid) {
196
+ if (subAid === this.deps.currentAid())
197
+ requireOwnershipApplicable(this.deps.currentAidType?.());
198
+ }
199
+ storage() {
200
+ const store = this.deps.store;
201
+ for (const name of ['loadOwnershipTask', 'listOwnershipTasks', 'compareAndSwapOwnershipTask', 'acquireOwnershipTaskLease', 'renewOwnershipTaskLease', 'releaseOwnershipTaskLease']) {
202
+ if (typeof store[name] !== 'function')
203
+ throw failure('persistence_unavailable', 'ownership durable task storage is unavailable');
204
+ }
205
+ return store;
206
+ }
207
+ async load(operationId, aid = this.deps.currentAid()) {
208
+ try {
209
+ return await this.storage().loadOwnershipTask(aid, operationId);
210
+ }
211
+ catch (error) {
212
+ throw failure('persistence_unavailable', `ownership task read failed: ${String(error)}`);
213
+ }
214
+ }
215
+ async record(snapshot) {
216
+ const task = snapshot.record;
217
+ const localAid = this.deps.currentAid();
218
+ try {
219
+ if (!Number.isSafeInteger(snapshot.revision) || snapshot.revision < 1)
220
+ throw new Error('invalid persisted task revision');
221
+ if (task.schema_version !== 1 || !['pending', 'completed', 'rejected', 'stopped', 'error'].includes(task.status) || !['pending', 'accept', 'reject'].includes(task.local_decision) || typeof task.step !== 'string')
222
+ throw new Error('invalid task state');
223
+ parseOwnershipMessage({ ...this.message(task, task.operation === 'bind' ? 'bind.request' : task.operation === 'transfer' ? 'transfer.request' : 'relation.confirmed') });
224
+ if (!participants(task).includes(task.initiator_aid) || task.operation !== 'bind' && task.initiator_aid !== task.owner_aid || JSON.stringify(task.local_roles) !== JSON.stringify(ownershipLocalRoles(task, localAid)) || !task.local_roles.length)
225
+ throw new Error('invalid local task identity');
226
+ if (!task.approvals || typeof task.approvals !== 'object' || Array.isArray(task.approvals) || Object.entries(task.approvals).some(([aid, decision]) => !participants(task).includes(aid) || !['pending', 'accept', 'reject'].includes(decision)))
227
+ throw new Error('invalid task approvals');
228
+ if (JSON.stringify(Object.keys(task.approvals).sort()) !== JSON.stringify(participants(task).sort()) || task.approvals[task.initiator_aid] !== 'accept')
229
+ throw new Error('incomplete task approvals or initiator decision');
230
+ if (!task.files || typeof task.files !== 'object' || Array.isArray(task.files) || !Array.isArray(task.inbox) || !Array.isArray(task.received) || !Array.isArray(task.outbox) || !Array.isArray(task.sent) || !task.hints || typeof task.hints !== 'object' || typeof task.request_validated !== 'boolean')
231
+ throw new Error('invalid task journals');
232
+ const base = task.base_agent_md;
233
+ if (!base && (task.request_validated || Object.keys(task.files).length || task.local_decision !== 'pending'))
234
+ throw new Error('missing original agent.md context');
235
+ if (base) {
236
+ if (typeof base.exists !== 'boolean' || typeof base.raw !== 'string' || typeof base.etag !== 'string' || base.exists && !base.etag || base.sha256 !== await ownershipSha256(base.raw))
237
+ throw new Error('invalid original agent.md bytes or ETag');
238
+ const statement = base.exists ? parseOwnershipFile(base.raw, 'agent', { expectedSubAid: task.sub_aid }).statement : undefined;
239
+ if ((statement?.relation_version ?? 0) !== task.base_relation_version || statement && task.operation !== 'bind' && statement.owner_aid !== task.owner_aid || (statement ? !this.sameStatement(statement, base.statement ?? undefined) : base.statement !== null))
240
+ throw new Error('invalid original agent.md statement');
241
+ }
242
+ if (task.base_owner_md) {
243
+ const prior = task.base_owner_md;
244
+ if (typeof prior.exists !== 'boolean' || typeof prior.raw !== 'string' || prior.sha256 !== (prior.exists ? await ownershipSha256(prior.raw) : '') || !prior.exists && prior.raw !== '')
245
+ throw new Error('invalid original owner.md context');
246
+ if (base?.statement && !prior.exists && (task.operation === 'transfer' || task.operation === 'release' && task.local_roles.includes('owner')))
247
+ throw new Error('missing original owner.md relation');
248
+ if (prior.exists) {
249
+ const file = parseOwnershipFile(prior.raw, 'owner', { expectedSubAid: task.sub_aid });
250
+ if (!file.statement || file.statement.owner_aid !== base?.statement?.owner_aid || file.statement.relation_version !== base?.statement?.relation_version)
251
+ throw new Error('invalid original owner.md statement');
252
+ }
253
+ }
254
+ if (base?.statement && !task.base_owner_md && !(task.operation === 'release' && task.local_roles.includes('sub') && !task.local_roles.includes('owner') && task.cleanup_authorization_operation_id && !task.files.cleanup))
255
+ throw new Error('missing original owner.md context');
256
+ if (task.approvals[localAid] !== task.local_decision)
257
+ throw new Error('task decision and approval differ');
258
+ if (task.status === 'completed' && (task.verified_relation_version !== task.target_relation_version || task.operation !== 'release' && task.local_decision !== 'accept'))
259
+ throw new Error('invalid completion evidence or local approval');
260
+ const transferEvidenceRequired = task.operation === 'transfer' && task.local_roles.some((role) => role === 'sub' || role === 'new_owner') && (task.status === 'completed' || this.ongoing(task) && (Boolean(task.files.owner || task.files.agent || task.hints['transfer.ready']) || task.authorization_sha256 !== undefined));
261
+ const expiresAt = task.expires_at;
262
+ if (transferEvidenceRequired && (!/^[0-9a-f]{64}$/.test(task.authorization_sha256 ?? '') || typeof expiresAt !== 'number' || !Number.isSafeInteger(expiresAt) || expiresAt <= 0))
263
+ throw new Error('invalid transfer authorization evidence');
264
+ for (const [key, prepared] of Object.entries(task.files)) {
265
+ const agentFile = key === 'agent' || key === 'cleanup';
266
+ if (!['agent', 'owner', 'release', 'transfer', 'cleanup'].includes(key) || !prepared || typeof prepared.raw !== 'string' || !/^[0-9a-f]{64}$/.test(prepared.sha256) || await ownershipSha256(prepared.raw) !== prepared.sha256 || !prepared.condition || typeof prepared.condition !== 'object' || Array.isArray(prepared.condition))
267
+ throw new Error('invalid prepared file bytes');
268
+ const condition = prepared.condition;
269
+ if (agentFile ? Object.keys(condition).length !== 1 || !(typeof condition.if_match === 'string' && condition.if_match || condition.if_none_match === '*') : Object.keys(condition).length !== 0)
270
+ throw new Error('invalid prepared file condition');
271
+ const original = prepared.original_owner;
272
+ if (!agentFile && (!original || typeof original.exists !== 'boolean' || typeof original.raw !== 'string' || original.sha256 !== (original.exists ? await ownershipSha256(original.raw) : '') || !original.exists && original.raw !== ''))
273
+ throw new Error('invalid prepared owner original');
274
+ if (key === 'cleanup' && task.operation !== 'release' || key === 'release' && task.operation !== 'release' || key === 'transfer' && task.operation !== 'transfer' || ['agent', 'owner'].includes(key) && task.operation === 'release')
275
+ throw new Error('invalid prepared file operation');
276
+ const ownerAid = key === 'owner' || key === 'agent' ? targetOwner(task) : task.owner_aid;
277
+ const file = parseOwnershipFile(prepared.raw, agentFile ? 'agent' : 'owner', { expectedSubAid: task.sub_aid, expectedOwnerAid: key === 'cleanup' ? undefined : ownerAid });
278
+ if (!task.local_roles.includes(agentFile ? 'sub' : key === 'owner' && task.operation === 'transfer' ? 'new_owner' : 'owner'))
279
+ throw new Error('invalid prepared file local role');
280
+ if (key === 'cleanup' ? Boolean(file.statement) : file.statement?.relation_version !== (key === 'owner' || key === 'agent' ? task.target_relation_version : task.base_relation_version))
281
+ throw new Error('invalid prepared file generation');
282
+ const fileExpiry = prepared.expires_at;
283
+ if (!agentFile && file.action !== (key === 'owner' ? 'declare' : key === 'release' ? 'release' : 'transfer_owner') || key === 'transfer' && (typeof fileExpiry !== 'number' || !Number.isSafeInteger(fileExpiry) || fileExpiry <= 0 || fileExpiry !== task.expires_at || file.transfer?.new_owner_aid !== task.new_owner_aid || file.transfer?.target_relation_version !== task.target_relation_version || file.transfer?.expires_at !== fileExpiry) || key !== 'transfer' && fileExpiry !== undefined)
284
+ throw new Error('invalid prepared file authorization');
285
+ if (prepared.published_etag !== undefined && (!agentFile || typeof prepared.published_etag !== 'string' || !prepared.published_etag) || prepared.cleaned !== undefined && typeof prepared.cleaned !== 'boolean')
286
+ throw new Error('invalid prepared file result');
287
+ }
288
+ if (task.release_cleanup !== undefined) {
289
+ const cleanup = task.release_cleanup;
290
+ if (!['bind', 'transfer'].includes(task.operation) || task.status !== 'completed' || !task.local_roles.includes('sub') || task.local_decision !== 'accept' || typeof cleanup.cleaned !== 'boolean' || !cleanup.files || typeof cleanup.files !== 'object' || Array.isArray(cleanup.files))
291
+ throw new Error('invalid historical cleanup context');
292
+ for (const [key, prepared] of Object.entries(cleanup.files)) {
293
+ if (key !== 'cleanup' || !prepared || prepared.sha256 !== await ownershipSha256(prepared.raw) || typeof prepared.condition?.if_match !== 'string' || !prepared.condition.if_match || Object.keys(prepared.condition).length !== 1 || parseOwnershipFile(prepared.raw, 'agent', { expectedSubAid: task.sub_aid }).statement)
294
+ throw new Error('invalid historical cleanup original file');
295
+ }
296
+ }
297
+ if (task.status === 'completed') {
298
+ const consensus = task.verified_consensus;
299
+ const cleaned = task.operation === 'release' && consensus?.status === 'unbound' && consensus.relation_version === 0 && Boolean(await this.cleanupEvidence(task, consensus));
300
+ if (!consensus || consensus.protocol !== '2.1' || consensus.sub_aid !== task.sub_aid || !cleaned && consensus.relation_version !== task.target_relation_version || typeof consensus.agent_md_etag !== 'string' || !consensus.agent_md_etag || !/^[0-9a-f]{64}$/.test(consensus.agent_md_sha256))
301
+ throw new Error('invalid persisted consensus evidence');
302
+ if (task.operation === 'release') {
303
+ if (!cleaned && consensus.status !== 'released' || consensus.lock !== 'unlocked' || consensus.owner_aid !== null || task.local_roles.includes('owner') && !task.files.release)
304
+ throw new Error('invalid persisted release evidence');
305
+ }
306
+ else {
307
+ if (consensus.status !== 'consensus' || consensus.lock !== 'locked' || consensus.owner_aid !== targetOwner(task) || !/^[0-9a-f]{64}$/.test(consensus.owner_md_sha256))
308
+ throw new Error('invalid persisted binding evidence');
309
+ if (task.local_roles.includes('sub') && task.files.agent?.sha256 !== consensus.agent_md_sha256 || task.local_roles.includes(task.operation === 'transfer' ? 'new_owner' : 'owner') && task.files.owner?.sha256 !== consensus.owner_md_sha256 || task.operation === 'transfer' && task.local_roles.includes('owner') && !task.files.transfer)
310
+ throw new Error('persisted consensus differs from local original files');
311
+ }
312
+ }
313
+ for (const key of task.received) {
314
+ if (typeof key !== 'string' || key.split('|').length !== 2)
315
+ throw new Error('invalid received journal');
316
+ const [fromAid, action] = key.split('|');
317
+ const message = parseOwnershipMessage(this.message(task, action));
318
+ validateOwnershipMessage(message, { authenticated_from_aid: fromAid, local_aid: localAid, task });
319
+ }
320
+ for (const item of task.inbox) {
321
+ parseOwnershipMessage(item.payload);
322
+ validateOwnershipMessage(item.payload, { authenticated_from_aid: item.from_aid, local_aid: localAid, task });
323
+ if (!task.received.includes(item.from_aid + '|' + item.payload.action))
324
+ throw new Error('invalid inbox journal');
325
+ }
326
+ for (const item of task.outbox) {
327
+ parseOwnershipMessage(item.payload);
328
+ const action = item.payload.action;
329
+ if (['bind.accept', 'transfer.accept', 'owner.ready', 'transfer.ready'].includes(action) && task.local_decision !== 'accept' || ['bind.reject', 'transfer.reject'].includes(action) && task.local_decision !== 'reject')
330
+ throw new Error('invalid outbox local approval');
331
+ if (['bind.accept', 'transfer.accept', 'bind.reject', 'transfer.reject'].includes(action) && !task.request_validated)
332
+ throw new Error('invalid outbox received request');
333
+ validateOwnershipMessage(item.payload, { authenticated_from_aid: localAid, local_aid: item.to_aid, task });
334
+ if (item.key !== `${item.to_aid}|${item.payload.action}` || typeof item.message_id !== 'string' || !/^m-[0-9a-f]{32}$/.test(item.message_id))
335
+ throw new Error('invalid outbox target');
336
+ }
337
+ return task;
338
+ }
339
+ catch (error) {
340
+ throw failure('persistence_unavailable', `ownership task is damaged: ${String(error)}`);
341
+ }
342
+ }
343
+ message(task, action, reason) {
344
+ return { type: OWNERSHIP_SDK_MESSAGE_TYPE, operation_id: task.operation_id, operation: task.operation, action,
345
+ sub_aid: task.sub_aid, owner_aid: task.owner_aid, ...(task.new_owner_aid ? { new_owner_aid: task.new_owner_aid } : {}),
346
+ base_relation_version: task.base_relation_version, target_relation_version: task.target_relation_version,
347
+ ...(reason === undefined ? {} : { reason }) };
348
+ }
349
+ create(message, initiator, localDecision) {
350
+ const aid = this.deps.currentAid();
351
+ return { schema_version: 1, operation_id: message.operation_id, operation: message.operation,
352
+ sub_aid: message.sub_aid, owner_aid: message.owner_aid, ...(message.new_owner_aid ? { new_owner_aid: message.new_owner_aid } : {}),
353
+ base_relation_version: message.base_relation_version, target_relation_version: message.target_relation_version,
354
+ status: 'pending', step: 'created', retryable: true, initiator_aid: initiator, local_roles: ownershipLocalRoles(message, aid),
355
+ local_decision: localDecision, approvals: { ...Object.fromEntries(participants(message).map((participant) => [participant, 'pending'])), [initiator]: 'accept', ...(localDecision === 'pending' ? {} : { [aid]: localDecision }) },
356
+ files: {}, inbox: [], received: [], outbox: [], sent: [], hints: {}, created_at: this.now(), request_validated: initiator === aid };
357
+ }
358
+ enqueue(task, toAid, action, reason) {
359
+ if (toAid === this.deps.currentAid())
360
+ return;
361
+ const key = `${toAid}|${action}`;
362
+ if (task.sent.includes(key) || task.outbox.some((item) => item.key === key))
363
+ return;
364
+ task.outbox.push({ key, to_aid: toAid, message_id: `m-${crypto.randomUUID().replace(/-/g, '')}`, payload: this.message(task, action, reason) });
365
+ }
366
+ event(context, event, extra = {}) {
367
+ context.events.push({ event, payload: { ...ownershipTaskResult(context.task), initiator_aid: context.task.initiator_aid, local_roles: context.task.local_roles,
368
+ ...(event === 'ownership.request' ? { requester_aid: context.task.initiator_aid } : {}),
369
+ ...(context.task.verified_relation_version === undefined ? {} : { relation_version: context.task.verified_relation_version }), ...extra } });
370
+ }
371
+ async save(context) {
372
+ context.guard?.();
373
+ if (context.aid !== this.deps.currentAid())
374
+ throw failure('forbidden', 'ownership current identity changed');
375
+ const store = this.storage();
376
+ let renewed;
377
+ try {
378
+ renewed = await store.renewOwnershipTaskLease(context.aid, context.lease, this.now() + 60);
379
+ }
380
+ catch (error) {
381
+ throw failure('persistence_unavailable', `ownership lease check failed: ${String(error)}`);
382
+ }
383
+ if (!renewed)
384
+ throw failure('persistence_unavailable', 'ownership sub lease or fencing was lost');
385
+ context.lease = renewed;
386
+ let saved;
387
+ try {
388
+ saved = await store.compareAndSwapOwnershipTask(context.aid, context.task.operation_id, context.revision, context.task, renewed);
389
+ }
390
+ catch (error) {
391
+ throw failure('persistence_unavailable', `ownership task save failed: ${String(error)}`);
392
+ }
393
+ if (!saved)
394
+ throw failure('persistence_unavailable', 'ownership task revision conflict');
395
+ context.revision = saved.revision;
396
+ if (context.aid !== this.deps.currentAid())
397
+ throw failure('forbidden', 'ownership current identity changed');
398
+ context.guard?.();
399
+ }
400
+ async queued(subAid, work, guard) {
401
+ const aid = this.deps.currentAid(), events = [];
402
+ const previous = this.queues.get(subAid) ?? Promise.resolve();
403
+ const queued = previous.catch(() => { }).then(() => work(events));
404
+ this.queues.set(subAid, queued);
405
+ const cleanup = () => { if (this.queues.get(subAid) === queued)
406
+ this.queues.delete(subAid); };
407
+ queued.then(cleanup, cleanup);
408
+ const result = await queued;
409
+ guard?.();
410
+ if (aid !== this.deps.currentAid())
411
+ throw failure('forbidden', 'ownership current identity changed');
412
+ for (const event of events)
413
+ await this.deps.emit(event.event, event.payload);
414
+ return result;
415
+ }
416
+ async execute(subAid, operationId, initial, mutate, guard) {
417
+ return await this.queued(subAid, (events) => this.advance(subAid, operationId, initial, events, mutate, guard), guard);
418
+ }
419
+ async advance(subAid, operationId, initial, events, mutate, guard) {
420
+ const store = this.storage(), aid = this.deps.currentAid();
421
+ const output = await (async () => {
422
+ guard?.();
423
+ if (aid !== this.deps.currentAid())
424
+ throw failure('forbidden', 'ownership current identity changed');
425
+ let lease;
426
+ try {
427
+ lease = await store.acquireOwnershipTaskLease(aid, subAid, operationId, crypto.randomUUID(), this.now() + 60);
428
+ }
429
+ catch (error) {
430
+ throw failure('persistence_unavailable', `ownership sub lease failed: ${String(error)}`);
431
+ }
432
+ if (!lease)
433
+ throw failure('persistence_unavailable', 'ownership sub is being advanced by another instance');
434
+ let context;
435
+ try {
436
+ const saved = await this.load(operationId, aid);
437
+ if (!saved && !initial)
438
+ throw failure('operation_not_found', 'ownership operation was not found');
439
+ if (!saved && initial) {
440
+ const existing = (await this.records(aid, subAid)).find((task) => task.sub_aid === subAid && this.ongoing(task));
441
+ if (existing) {
442
+ if (!this.sameIntent(existing, initial) || existing.base_relation_version !== initial.base_relation_version || existing.target_relation_version !== initial.target_relation_version)
443
+ throw failure('conflict', 'another local operation for this sub is pending');
444
+ try {
445
+ await store.releaseOwnershipTaskLease(aid, lease);
446
+ }
447
+ catch (error) {
448
+ throw failure('persistence_unavailable', `ownership sub lease release failed: ${String(error)}`);
449
+ }
450
+ return { result: await this.advance(subAid, existing.operation_id, undefined, events, undefined, guard), events: [] };
451
+ }
452
+ }
453
+ context = { aid, task: saved ? await this.record(saved) : initial, revision: saved?.revision ?? null, lease, events: [], guard };
454
+ if (context.task.sub_aid !== subAid)
455
+ throw failure('conflict', 'ownership task sub changed');
456
+ if (!saved)
457
+ await this.save(context);
458
+ await mutate?.(context);
459
+ await this.drive(context);
460
+ return { result: ownershipTaskResult(context.task), events: context.events };
461
+ }
462
+ finally {
463
+ try {
464
+ await store.releaseOwnershipTaskLease(aid, context?.lease ?? lease);
465
+ }
466
+ catch (error) {
467
+ throw failure('persistence_unavailable', `ownership sub lease release failed: ${String(error)}`);
468
+ }
469
+ }
470
+ })();
471
+ guard?.();
472
+ if (aid !== this.deps.currentAid())
473
+ throw failure('forbidden', 'ownership current identity changed');
474
+ events.push(...output.events);
475
+ return output.result;
476
+ }
477
+ ongoing(task) { return task.status === 'pending' || task.status === 'error' && task.retryable === true; }
478
+ sameIntent(task, intent) {
479
+ return task.initiator_aid === intent.initiator_aid && task.operation === intent.operation && task.sub_aid === intent.sub_aid && task.owner_aid === intent.owner_aid && task.new_owner_aid === intent.new_owner_aid;
480
+ }
481
+ async records(aid, subAid) {
482
+ let snapshots;
483
+ try {
484
+ snapshots = await this.storage().listOwnershipTasks(aid);
485
+ }
486
+ catch (error) {
487
+ throw failure('persistence_unavailable', `ownership task list failed: ${String(error)}`);
488
+ }
489
+ return await Promise.all(snapshots.filter((snapshot) => !subAid || typeof snapshot.record.sub_aid !== 'string' || snapshot.record.sub_aid === subAid).map((snapshot) => this.record(snapshot)));
490
+ }
491
+ async observe(subAid) {
492
+ this.requireApplicable(subAid);
493
+ const snapshot = await this.deps.files.readAgent(subAid);
494
+ const file = snapshot.exists ? await this.deps.files.verify(snapshot.content, 'agent', subAid) : null;
495
+ if (file)
496
+ requireOwnershipApplicable(file.frontmatter.type);
497
+ if ((file?.statement?.relation_version ?? 0) !== snapshot.relation_version)
498
+ throw failure('unavailable', 'NS relation metadata does not match agent.md', true);
499
+ if (snapshot.lock === 'locked' && (!file?.statement || file.statement.relation_version !== snapshot.relation_version || snapshot.owner_aid !== undefined && snapshot.owner_aid !== file.statement.owner_aid))
500
+ throw failure('unavailable', 'NS lock and current signed agent.md differ', true);
501
+ return { ...snapshot, file, owner_aid: snapshot.lock === 'locked' ? file.statement.owner_aid : null };
502
+ }
503
+ isBase(task, ns) {
504
+ const base = task.base_agent_md;
505
+ return ns.relation_version === task.base_relation_version && (task.operation === 'bind' ? ns.lock === 'unlocked' : ns.lock === 'locked' && ns.owner_aid === task.owner_aid) && (!base || base.exists === ns.exists && (base.statement ? this.sameStatement(base.statement, ns.file?.statement) : !ns.file?.statement && base.raw === ns.content && base.etag === ns.etag));
506
+ }
507
+ async captureBase(task, ns) {
508
+ if (!task.base_agent_md)
509
+ task.base_agent_md = { exists: ns.exists, raw: ns.content, sha256: await ownershipSha256(ns.content), etag: ns.etag, statement: ns.file?.statement ?? null };
510
+ if (!task.base_owner_md) {
511
+ const prior = ns.file?.statement ? await this.deps.files.readOwner(ns.file.statement.owner_aid, task.sub_aid) : null;
512
+ task.base_owner_md = prior ? { exists: prior.exists, raw: prior.content, sha256: prior.sha256 } : { exists: false, raw: '', sha256: '' };
513
+ }
514
+ }
515
+ isTarget(task, ns) {
516
+ return ns.relation_version === task.target_relation_version && (task.operation === 'release' ? ns.lock === 'unlocked' : ns.lock === 'locked' && ns.owner_aid === targetOwner(task));
517
+ }
518
+ fileTask(context, expectedStatement) {
519
+ const task = { ...context.task, files: context.task.files, expected_agent_statement: expectedStatement,
520
+ persist: async () => { context.task.expires_at = task.expires_at; context.task.authorization_sha256 = task.authorization_sha256; context.task.base_agent_md = task.base_agent_md; context.task.base_owner_md = task.base_owner_md; await this.save(context); },
521
+ guard: async () => { context.task.expires_at = task.expires_at; context.task.authorization_sha256 = task.authorization_sha256; context.task.base_agent_md = task.base_agent_md; context.task.base_owner_md = task.base_owner_md; await this.save(context); } };
522
+ return task;
523
+ }
524
+ async start(operation, subAid, ownerAid, newOwnerAid, requestedId, guard) {
525
+ this.requireApplicable(subAid);
526
+ const aid = this.deps.currentAid();
527
+ return await this.queued(subAid, async (events) => {
528
+ if (aid !== this.deps.currentAid())
529
+ throw failure('forbidden', 'ownership current identity changed');
530
+ return await this.startCurrent(operation, subAid, ownerAid, events, newOwnerAid, requestedId, guard);
531
+ }, guard);
532
+ }
533
+ async startCurrent(operation, subAid, ownerAid, events, newOwnerAid, requestedId, guard) {
534
+ guard?.();
535
+ this.storage();
536
+ const aid = ownershipAid(this.deps.currentAid(), 'local_aid');
537
+ ownershipAid(subAid, 'sub_aid');
538
+ ownershipAid(ownerAid, 'owner_aid');
539
+ if (newOwnerAid)
540
+ ownershipAid(newOwnerAid, 'new_owner_aid');
541
+ if (operation === 'transfer' && (!newOwnerAid || newOwnerAid === ownerAid))
542
+ throw failure('invalid', 'ownership transfer target must differ from current owner');
543
+ if (requestedId) {
544
+ if (!OWNERSHIP_UUID_V4_RE.test(requestedId))
545
+ throw failure('invalid', 'ownership registration operation_id must be a UUIDv4');
546
+ const saved = await this.load(requestedId);
547
+ if (saved) {
548
+ const task = await this.record(saved);
549
+ if (task.operation !== operation || task.sub_aid !== subAid || task.owner_aid !== ownerAid || task.new_owner_aid !== newOwnerAid || task.initiator_aid !== aid)
550
+ throw failure('conflict', 'ownership registration intent changed');
551
+ return await this.advance(subAid, requestedId, undefined, events, undefined, guard);
552
+ }
553
+ }
554
+ const intent = { initiator_aid: aid, operation, sub_aid: subAid, owner_aid: ownerAid, new_owner_aid: newOwnerAid };
555
+ for (const pending of (await this.records(aid, subAid)).filter((task) => task.sub_aid === subAid && this.ongoing(task))) {
556
+ if (this.sameIntent(pending, intent)) {
557
+ const result = await this.advance(subAid, pending.operation_id, undefined, events, undefined, guard);
558
+ if (result.status !== 'stopped' || result.code !== 'conflict' || !/relation version or target was replaced/.test(result.reason ?? ''))
559
+ return result;
560
+ }
561
+ else {
562
+ const current = await this.observe(subAid);
563
+ if (this.isBase(pending, current))
564
+ throw failure('conflict', 'another local operation for this sub is pending');
565
+ const result = await this.advance(subAid, pending.operation_id, undefined, events, undefined, guard);
566
+ if (result.status === 'pending' || result.status === 'error')
567
+ throw failure('conflict', 'another local operation for this sub is pending');
568
+ }
569
+ }
570
+ const ns = await this.observe(subAid);
571
+ if (operation === 'bind') {
572
+ if (!ns.exists)
573
+ throw failure('unavailable', 'initial agent.md is not published', true);
574
+ if (ns.lock === 'locked')
575
+ throw failure('owner_locked', 'the sub AID has a current owner lock');
576
+ if (ns.file?.frontmatter.type === 'human')
577
+ throw failure('invalid', 'human cannot declare an owner');
578
+ if (ns.file?.statement?.owner_aid === ownerAid)
579
+ throw failure('conflict', 'current owner release requires sub cleanup before binding again');
580
+ }
581
+ else {
582
+ if (aid !== ownerAid || ns.lock !== 'locked' || ns.owner_aid !== aid)
583
+ throw failure('forbidden', 'only the current original owner may release or transfer');
584
+ const consensus = await this.deps.files.checkConsensus(subAid);
585
+ if (consensus.status !== 'consensus' || consensus.owner_aid !== aid || consensus.relation_version !== ns.relation_version)
586
+ throw failure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'the current owner relation could not be verified', consensus.status !== 'invalid');
587
+ }
588
+ const targetVersion = ownershipInteger(ns.relation_version + (operation === 'release' ? 0 : 1), 'target_relation_version');
589
+ const operationId = requestedId ?? crypto.randomUUID();
590
+ const payload = { type: OWNERSHIP_SDK_MESSAGE_TYPE, operation_id: operationId, operation, action: operation === 'bind' ? 'bind.request' : operation === 'transfer' ? 'transfer.request' : 'relation.confirmed', sub_aid: subAid, owner_aid: ownerAid, ...(newOwnerAid ? { new_owner_aid: newOwnerAid } : {}), base_relation_version: ns.relation_version, target_relation_version: targetVersion };
591
+ const task = this.create(payload, aid, 'accept');
592
+ await this.captureBase(task, ns);
593
+ if (!task.local_roles.length)
594
+ throw failure('forbidden', 'the local AID is not an ownership participant');
595
+ if (aid !== this.deps.currentAid())
596
+ throw failure('forbidden', 'ownership current identity changed');
597
+ return await this.advance(subAid, operationId, task, events, async (context) => {
598
+ if (context.revision === 1) {
599
+ if (operation === 'bind')
600
+ for (const peer of participants(task))
601
+ this.enqueue(context.task, peer, 'bind.request');
602
+ if (operation === 'transfer')
603
+ for (const peer of [...new Set([subAid, newOwnerAid])])
604
+ this.enqueue(context.task, peer, 'transfer.request');
605
+ await this.save(context);
606
+ }
607
+ }, guard);
608
+ }
609
+ async bindOwneraid(ownerAid) { return await this.start('bind', this.deps.currentAid(), ownerAid); }
610
+ async bindSubaid(subAid) { return await this.start('bind', subAid, this.deps.currentAid()); }
611
+ async releaseSubaid(subAid) { return await this.start('release', subAid, this.deps.currentAid()); }
612
+ async transferSubaid(subAid, newOwnerAid) { return await this.start('transfer', subAid, this.deps.currentAid(), newOwnerAid); }
613
+ async startRegistrationIntent(ownerAid, operationId, guard) { return await this.start('bind', this.deps.currentAid(), ownerAid, undefined, operationId, guard); }
614
+ async checkOwnershipConsensus(subAid = this.deps.currentAid()) { return await this.deps.files.checkConsensus(subAid); }
615
+ async decide(operationId, decision, reason) {
616
+ const saved = await this.load(operationId);
617
+ if (!saved)
618
+ throw failure('operation_not_found', 'ownership operation was not found');
619
+ const original = await this.record(saved);
620
+ return await this.execute(original.sub_aid, operationId, undefined, async (context) => {
621
+ const task = context.task;
622
+ if (task.operation === 'release' || task.initiator_aid === context.aid || !task.request_validated)
623
+ throw failure(task.request_validated ? 'forbidden' : 'unavailable', 'ownership approval requires a verified local received request');
624
+ if (task.local_decision !== 'pending') {
625
+ if (task.local_decision !== decision)
626
+ throw failure('conflict', 'ownership approval decision is immutable');
627
+ return;
628
+ }
629
+ if (['completed', 'rejected', 'stopped'].includes(task.status) || task.status === 'error' && !task.retryable)
630
+ throw failure('conflict', 'ownership operation no longer awaits approval');
631
+ const ns = await this.observe(task.sub_aid);
632
+ if (!this.isBase(task, ns))
633
+ throw failure(ns.lock === 'locked' && task.operation === 'bind' ? 'owner_locked' : 'conflict', 'ownership approval current relation changed');
634
+ task.local_decision = decision;
635
+ task.approvals[context.aid] = decision;
636
+ task.step = decision === 'accept' ? 'approved' : 'rejected';
637
+ if (decision === 'reject') {
638
+ task.status = 'rejected';
639
+ task.reason = reason;
640
+ task.retryable = false;
641
+ }
642
+ this.enqueue(task, task.initiator_aid, task.operation === 'bind' ? decision === 'accept' ? 'bind.accept' : 'bind.reject' : decision === 'accept' ? 'transfer.accept' : 'transfer.reject', reason);
643
+ await this.save(context);
644
+ this.event(context, decision === 'reject' ? 'ownership.rejected' : 'ownership.progress');
645
+ });
646
+ }
647
+ async acceptOwnershipRequest(operationId) { return await this.decide(operationId, 'accept'); }
648
+ async rejectOwnershipRequest(operationId, reason) { return await this.decide(operationId, 'reject', reason); }
649
+ async receive(fromAid, content) {
650
+ let body = content;
651
+ if (typeof content === 'string') {
652
+ try {
653
+ body = JSON.parse(content);
654
+ }
655
+ catch {
656
+ return false;
657
+ }
658
+ }
659
+ if (!isOwnershipSdkPayload(body)) {
660
+ const type = body && typeof body === 'object' ? body.type : undefined;
661
+ return typeof type === 'string' && (type === 'aun.ownership.v1' || type.startsWith('aun.ownership.'));
662
+ }
663
+ let message;
664
+ try {
665
+ message = parseOwnershipMessage(content);
666
+ }
667
+ catch {
668
+ return true;
669
+ }
670
+ try {
671
+ this.requireApplicable(message.sub_aid);
672
+ }
673
+ catch (error) {
674
+ if (ownershipErrorCode(error) === 'not_applicable')
675
+ return true;
676
+ throw error;
677
+ }
678
+ const snapshot = await this.load(message.operation_id);
679
+ const prior = snapshot ? await this.record(snapshot) : undefined;
680
+ if (!prior && !['bind.request', 'transfer.request'].includes(message.action) && !(message.operation === 'release' && message.action === 'relation.confirmed'))
681
+ return true;
682
+ const initial = prior ?? this.create(message, fromAid, 'pending');
683
+ try {
684
+ validateOwnershipMessage(message, { authenticated_from_aid: fromAid, local_aid: this.deps.currentAid(), task: initial });
685
+ }
686
+ catch {
687
+ return true;
688
+ }
689
+ let observationError;
690
+ try {
691
+ await this.deps.files.readAgent(message.sub_aid);
692
+ }
693
+ catch (error) {
694
+ if (ownershipErrorCode(error) === 'not_applicable')
695
+ return true;
696
+ observationError = error;
697
+ }
698
+ await this.execute(message.sub_aid, message.operation_id, initial, async (context) => {
699
+ context.observationError = observationError;
700
+ try {
701
+ validateOwnershipMessage(message, { authenticated_from_aid: fromAid, local_aid: context.aid, task: context.task });
702
+ }
703
+ catch {
704
+ return;
705
+ }
706
+ if (message.action === 'sub.cleaned' && !await this.confirmSubCleaned(context.task))
707
+ return;
708
+ const key = `${fromAid}|${message.action}`;
709
+ if (!context.task.received.includes(key)) {
710
+ context.task.received.push(key);
711
+ context.task.inbox.push({ from_aid: fromAid, payload: message });
712
+ await this.save(context);
713
+ }
714
+ });
715
+ return true;
716
+ }
717
+ async confirmSubCleaned(task) {
718
+ const began = this.now(), before = await this.deps.files.readAgent(task.sub_aid);
719
+ if (!before.exists || before.relation_version !== 0 || before.lock !== 'unlocked' || before.owner_aid || !before.etag)
720
+ return false;
721
+ const file = parseOwnershipFile(before.content, 'agent', { expectedSubAid: task.sub_aid });
722
+ if (Object.hasOwn(file.frontmatter, 'owner') || Object.hasOwn(file.frontmatter, 'ownership'))
723
+ return false;
724
+ if (!task.base_agent_md?.statement || task.base_agent_md.statement.owner_aid !== task.owner_aid || task.base_agent_md.statement.sub_aid !== task.sub_aid || task.base_agent_md.statement.relation_version !== task.base_relation_version)
725
+ return false;
726
+ let ownerHash = '';
727
+ if (task.local_roles.includes('owner') && task.files.release) {
728
+ const original = task.files.release, object = await this.deps.files.readOwner(task.owner_aid, task.sub_aid);
729
+ if (object.exists && object.content === original.raw && object.sha256 === original.sha256) {
730
+ const file = await this.deps.files.verify(object.content, 'owner', task.sub_aid, task.owner_aid);
731
+ if (file.action === 'release' && file.statement && ['version', 'owner_aid', 'sub_aid', 'relation_version'].every((key) => file.statement[key] === task.base_agent_md.statement[key]))
732
+ ownerHash = object.sha256;
733
+ }
734
+ }
735
+ const confirmed = Boolean(await this.cleanupEvidence(task, { agent_md_etag: before.etag, agent_md_sha256: await ownershipSha256(before.content), owner_md_sha256: ownerHash }, before.content));
736
+ const after = await this.deps.files.readAgent(task.sub_aid);
737
+ if (this.now() - began > 30 || before.exists !== after.exists || before.etag !== after.etag || before.relation_version !== after.relation_version || before.lock !== after.lock || before.owner_aid !== after.owner_aid || before.content !== after.content)
738
+ throw failure('unavailable', 'NS changed during sub cleanup verification', true);
739
+ return confirmed;
740
+ }
741
+ async resume(guard) {
742
+ guard?.();
743
+ let snapshots;
744
+ try {
745
+ snapshots = await this.storage().listOwnershipTasks(this.deps.currentAid());
746
+ }
747
+ catch (error) {
748
+ throw failure('persistence_unavailable', `ownership task list failed: ${String(error)}`);
749
+ }
750
+ for (const snapshot of snapshots) {
751
+ guard?.();
752
+ try {
753
+ const task = await this.record(snapshot);
754
+ if (['rejected', 'stopped'].includes(task.status) && !task.outbox.length && !task.inbox.length || task.status === 'completed' && !task.cleanup_pending && !task.outbox.length && !task.inbox.length && !(['bind', 'transfer'].includes(task.operation) && task.local_roles.includes('sub') && task.local_decision === 'accept' && !task.release_cleanup?.cleaned))
755
+ continue;
756
+ await this.execute(task.sub_aid, task.operation_id, undefined, undefined, guard);
757
+ }
758
+ catch (error) {
759
+ guard?.();
760
+ if (ownershipErrorCode(error) !== 'conflict')
761
+ await this.deps.emit('ownership.error', { operation_id: snapshot.record.operation_id, sub_aid: snapshot.record.sub_aid, code: ownershipErrorCode(error), reason: String(error), retryable: false });
762
+ }
763
+ }
764
+ }
765
+ async inbox(context) {
766
+ const task = context.task;
767
+ while (task.inbox.length) {
768
+ if (task.status === 'error' && !task.retryable) {
769
+ task.inbox.shift();
770
+ await this.save(context);
771
+ continue;
772
+ }
773
+ const entry = task.inbox[0];
774
+ const ns = await this.observe(task.sub_aid);
775
+ if (!task.base_agent_md && !task.request_validated && task.local_decision === 'pending' && !Object.keys(task.files).length) {
776
+ if (this.isBase(task, ns) || task.operation === 'release' && ns.relation_version === task.base_relation_version && ns.file?.statement?.owner_aid === task.owner_aid)
777
+ await this.captureBase(task, ns);
778
+ else if (task.operation === 'release' && ns.relation_version === 0 && !ns.file?.statement && task.local_roles.includes('sub')) {
779
+ for (const candidate of await this.records(context.aid, task.sub_aid)) {
780
+ const original = candidate.files.agent, cleanup = candidate.release_cleanup?.files.cleanup;
781
+ if (!['bind', 'transfer'].includes(candidate.operation) || candidate.local_decision !== 'accept' || !candidate.local_roles.includes('sub') || targetOwner(candidate) !== task.owner_aid || candidate.target_relation_version !== task.base_relation_version || !original || !cleanup || cleanup.raw !== ns.content || cleanup.sha256 !== await ownershipSha256(ns.content) || cleanup.published_etag !== undefined && cleanup.published_etag !== ns.etag)
782
+ continue;
783
+ const statement = parseOwnershipFile(original.raw, 'agent', { expectedSubAid: task.sub_aid }).statement;
784
+ if (!statement || !cleanup.condition?.if_match)
785
+ continue;
786
+ task.base_agent_md = { exists: true, raw: original.raw, sha256: original.sha256, etag: cleanup.condition.if_match, statement };
787
+ task.cleanup_authorization_operation_id = candidate.operation_id;
788
+ break;
789
+ }
790
+ }
791
+ if (task.base_agent_md)
792
+ await this.save(context);
793
+ }
794
+ try {
795
+ validateOwnershipMessage(entry.payload, { authenticated_from_aid: entry.from_aid, local_aid: context.aid, task, ns });
796
+ }
797
+ catch {
798
+ task.inbox.shift();
799
+ task.received.push(entry.from_aid + '|' + entry.payload.action);
800
+ await this.save(context);
801
+ continue;
802
+ }
803
+ const action = entry.payload.action;
804
+ if (!['rejected', 'stopped', 'completed'].includes(task.status) && !(task.status === 'error' && !task.retryable)) {
805
+ if (action === 'bind.request' || action === 'transfer.request') {
806
+ task.request_validated = true;
807
+ }
808
+ else if (['bind.accept', 'transfer.accept', 'bind.reject', 'transfer.reject'].includes(action)) {
809
+ const decision = action.endsWith('.accept') ? 'accept' : 'reject';
810
+ if (decision === 'reject' && await this.complete(context, ns)) {
811
+ task.inbox.shift();
812
+ await this.save(context);
813
+ continue;
814
+ }
815
+ if (!task.approvals[entry.from_aid] || task.approvals[entry.from_aid] === 'pending') {
816
+ task.approvals[entry.from_aid] = decision;
817
+ if (decision === 'reject') {
818
+ task.status = 'rejected';
819
+ task.step = 'rejected';
820
+ task.reason = entry.payload.reason;
821
+ task.retryable = false;
822
+ if (task.operation === 'transfer')
823
+ for (const peer of participants(task))
824
+ if (peer !== entry.from_aid)
825
+ this.enqueue(task, peer, 'operation.stopped', entry.payload.reason);
826
+ this.event(context, 'ownership.rejected');
827
+ }
828
+ }
829
+ }
830
+ else if (action === 'operation.stopped') {
831
+ if (!await this.complete(context, ns)) {
832
+ task.status = 'stopped';
833
+ task.step = 'stopped';
834
+ task.reason = entry.payload.reason;
835
+ task.retryable = false;
836
+ this.event(context, 'ownership.stopped');
837
+ }
838
+ }
839
+ else {
840
+ if (action === 'transfer.ready' && this.isBase(task, ns)) {
841
+ const authorization = await this.deps.files.verifyTransfer(this.fileTask(context));
842
+ task.expires_at = authorization.transfer.expires_at;
843
+ }
844
+ task.hints[action] = true;
845
+ }
846
+ }
847
+ else if (action === 'sub.cleaned') {
848
+ const file = ns.file;
849
+ if (ns.relation_version === 0 && ns.lock === 'unlocked' && !ns.owner_aid && file && !file.statement && !Object.hasOwn(file.frontmatter, 'owner') && !Object.hasOwn(file.frontmatter, 'ownership'))
850
+ task.hints[action] = true;
851
+ }
852
+ task.inbox.shift();
853
+ await this.save(context);
854
+ }
855
+ }
856
+ async flush(context, ns) {
857
+ const task = context.task;
858
+ for (const item of [...task.outbox]) {
859
+ const action = item.payload.action;
860
+ const notification = ['relation.confirmed', 'sub.cleaned'].includes(action);
861
+ const terminal = ['bind.reject', 'transfer.reject', 'operation.stopped'].includes(action);
862
+ const cleaned = action === 'sub.cleaned' && task.status === 'completed' && await this.confirmSubCleaned(task);
863
+ if (!terminal && (notification ? !this.isTarget(task, ns) && !cleaned : !this.isBase(task, ns) || task.status === 'completed' || task.status === 'rejected' || task.status === 'stopped')) {
864
+ task.outbox = task.outbox.filter((row) => row.key !== item.key);
865
+ await this.save(context);
866
+ continue;
867
+ }
868
+ if (action === 'owner.ready' || action === 'transfer.ready') {
869
+ const prepared = task.files[action === 'owner.ready' ? 'owner' : 'transfer'];
870
+ const object = await this.deps.files.readOwner(action === 'owner.ready' ? targetOwner(task) : task.owner_aid, task.sub_aid);
871
+ if (!prepared || !object.exists || object.sha256 !== prepared.sha256 || object.content !== prepared.raw)
872
+ throw failure('conflict', 'ownership ready file has been replaced');
873
+ const fileExpiry = prepared.expires_at;
874
+ if (action === 'transfer.ready' && (typeof fileExpiry !== 'number' || !Number.isSafeInteger(fileExpiry) || fileExpiry <= 0 || fileExpiry !== task.expires_at || fileExpiry <= this.now()))
875
+ throw failure('conflict', 'ownership transfer authorization expired');
876
+ }
877
+ await this.save(context);
878
+ await this.deps.send(item.to_aid, item.payload, item.message_id);
879
+ task.outbox = task.outbox.filter((row) => row.key !== item.key);
880
+ task.sent.push(item.key);
881
+ await this.save(context);
882
+ }
883
+ }
884
+ async complete(context, ns) {
885
+ const task = context.task;
886
+ const began = Date.now(), cleaned = task.operation === 'release' && ns.exists && ns.relation_version === 0 && ns.lock === 'unlocked' && !ns.file?.statement;
887
+ if (!this.isTarget(task, ns) && !cleaned)
888
+ return false;
889
+ let consensus;
890
+ if (task.operation === 'release') {
891
+ consensus = await this.deps.files.checkConsensus(task.sub_aid);
892
+ if ((cleaned ? consensus.status !== 'unbound' || consensus.relation_version !== 0 : consensus.status !== 'released' || consensus.relation_version !== task.target_relation_version) || consensus.lock !== ns.lock || consensus.owner_aid !== ns.owner_aid || consensus.agent_md_etag !== ns.etag || consensus.agent_md_sha256 !== await ownershipSha256(ns.content))
893
+ throw failure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'ownership release could not be freshly confirmed', consensus.status !== 'invalid');
894
+ if (!cleaned && task.files.release && consensus.owner_md_sha256) {
895
+ const source = task.files.release;
896
+ if (source.sha256 !== consensus.owner_md_sha256)
897
+ throw failure('conflict', 'current release material does not belong to this task');
898
+ }
899
+ if (cleaned) {
900
+ const source = task.files.release;
901
+ if (task.local_roles.includes('owner') && source) {
902
+ const object = await this.deps.files.readOwner(task.owner_aid, task.sub_aid);
903
+ if (object.exists && object.content === source.raw && object.sha256 === source.sha256) {
904
+ await this.deps.files.verify(object.content, 'owner', task.sub_aid, task.owner_aid);
905
+ consensus.owner_md_sha256 = object.sha256;
906
+ }
907
+ }
908
+ const authorization = await this.cleanupEvidence(task, consensus, ns.content);
909
+ if (!authorization)
910
+ throw failure('conflict', 'no local cleanup original can confirm this release task');
911
+ const after = await this.observe(task.sub_aid);
912
+ if (Date.now() - began > 30_000 || after.etag !== ns.etag || after.content !== ns.content || !after.exists || after.relation_version !== 0 || after.lock !== 'unlocked' || after.owner_aid)
913
+ throw failure('unavailable', 'NS changed during cleanup confirmation', true);
914
+ if (authorization !== task.operation_id)
915
+ task.cleanup_authorization_operation_id = authorization;
916
+ }
917
+ const prior = ns.file?.statement;
918
+ if (prior && (prior.owner_aid !== task.owner_aid || prior.relation_version !== task.base_relation_version || !this.sameStatement(prior, task.base_agent_md?.statement ?? undefined)))
919
+ throw failure('conflict', 'ownership released document does not match this generation');
920
+ if (task.local_roles.includes('owner') && !task.files.release)
921
+ throw failure('conflict', 'ownership completion lacks this task original owner file');
922
+ if (prior && task.local_roles.includes('sub') && !task.local_roles.includes('owner') && !await this.authorizeCleanup(context, prior)) {
923
+ task.status = 'pending';
924
+ task.step = 'cleanup_action_required';
925
+ task.cleanup_pending = true;
926
+ task.retryable = true;
927
+ await this.save(context);
928
+ this.event(context, 'ownership.action_required');
929
+ return true;
930
+ }
931
+ }
932
+ else {
933
+ if (task.local_decision !== 'accept')
934
+ return false;
935
+ if (task.operation === 'transfer' && task.local_roles.includes('owner') && !task.files.transfer)
936
+ throw failure('conflict', 'ownership completion lacks this task original transfer authorization');
937
+ consensus = await this.deps.files.checkConsensus(task.sub_aid, { owner_aid: targetOwner(task), relation_version: task.target_relation_version });
938
+ if (consensus.status !== 'consensus' || consensus.owner_aid !== targetOwner(task) || consensus.relation_version !== task.target_relation_version)
939
+ throw failure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'ownership target materials could not be verified', consensus.status !== 'invalid');
940
+ if (task.local_roles.includes('sub') && (!task.files.agent || task.files.agent.sha256 !== consensus.agent_md_sha256))
941
+ throw failure('conflict', 'NS accepted agent.md does not match this task original bytes');
942
+ if (task.local_roles.includes(task.operation === 'transfer' ? 'new_owner' : 'owner') && task.files.owner?.sha256 !== consensus.owner_md_sha256)
943
+ throw failure('conflict', 'Storage owner.md does not match this task original bytes');
944
+ if (task.local_roles.includes('sub') && task.files.agent.published_etag !== undefined && task.files.agent.published_etag !== consensus.agent_md_etag && task.verified_relation_version === undefined)
945
+ throw failure('conflict', 'NS accepted agent.md revision changed');
946
+ }
947
+ task.status = 'completed';
948
+ task.step = task.operation === 'release' ? 'released' : 'confirmed';
949
+ task.verified_relation_version = task.target_relation_version;
950
+ task.verified_consensus = { ...consensus, protocol: '2.1' };
951
+ delete task.code;
952
+ delete task.reason;
953
+ task.retryable = false;
954
+ task.cleanup_pending = task.operation === 'release' || task.operation === 'transfer' && task.local_roles.includes('owner');
955
+ if (task.operation === 'release' && task.local_roles.includes('owner'))
956
+ this.enqueue(task, task.sub_aid, 'relation.confirmed');
957
+ if (task.operation !== 'release' && task.local_roles.includes('sub'))
958
+ for (const peer of participants(task))
959
+ this.enqueue(task, peer, 'relation.confirmed');
960
+ if (!task.completed_event) {
961
+ task.completed_event = true;
962
+ await this.save(context);
963
+ this.event(context, 'ownership.completed', task.operation !== 'release' ? { consensus } : {});
964
+ }
965
+ else
966
+ await this.save(context);
967
+ return true;
968
+ }
969
+ async cleanupEvidence(task, evidence, raw) {
970
+ const release = task.files.release;
971
+ if (task.local_roles.includes('owner') && release && release.sha256 === evidence.owner_md_sha256 && release.sha256 === await ownershipSha256(release.raw))
972
+ return task.operation_id;
973
+ if (!task.local_roles.includes('sub'))
974
+ return undefined;
975
+ const matches = async (prepared) => Boolean(prepared && prepared.sha256 === evidence.agent_md_sha256 && prepared.sha256 === await ownershipSha256(prepared.raw) && (raw === undefined || prepared.raw === raw) && (prepared.published_etag === undefined || prepared.published_etag === evidence.agent_md_etag));
976
+ if (await matches(task.files.cleanup))
977
+ return task.operation_id;
978
+ let history;
979
+ try {
980
+ history = await this.storage().listOwnershipTasks(this.deps.currentAid());
981
+ }
982
+ catch (error) {
983
+ throw failure('persistence_unavailable', `ownership cleanup history read failed: ${String(error)}`);
984
+ }
985
+ for (const snapshot of history) {
986
+ if (!['bind', 'transfer'].includes(String(snapshot.record.operation)))
987
+ continue;
988
+ if (snapshot.record.sub_aid !== task.sub_aid)
989
+ continue;
990
+ const candidate = await this.record(snapshot);
991
+ if (task.cleanup_authorization_operation_id && candidate.operation_id !== task.cleanup_authorization_operation_id || candidate.local_decision !== 'accept' || !candidate.local_roles.includes('sub') || candidate.sub_aid !== task.sub_aid || targetOwner(candidate) !== task.owner_aid || candidate.target_relation_version !== task.base_relation_version || !await matches(candidate.release_cleanup?.files.cleanup))
992
+ continue;
993
+ const statement = candidate.files.agent && parseOwnershipFile(candidate.files.agent.raw, 'agent', { expectedSubAid: task.sub_aid }).statement;
994
+ if (statement?.owner_aid === task.owner_aid && statement.sub_aid === task.sub_aid && statement.relation_version === task.base_relation_version && this.sameStatement(statement, task.base_agent_md?.statement ?? undefined))
995
+ return candidate.operation_id;
996
+ }
997
+ return undefined;
998
+ }
999
+ sameStatement(left, right) {
1000
+ return Boolean(left && right && ['version', 'declaration_id', 'owner_aid', 'sub_aid', 'relation_version', 'declared_at'].every((key) => left[key] === right[key]));
1001
+ }
1002
+ async authorizeCleanup(context, statement) {
1003
+ const task = context.task;
1004
+ const matches = (record) => ['bind', 'transfer'].includes(record.operation) && record.sub_aid === task.sub_aid && record.target_relation_version === task.base_relation_version && record.local_decision === 'accept' && record.local_roles.includes('sub') && targetOwner(record) === task.owner_aid && Boolean(record.files.agent && this.sameStatement(parseOwnershipFile(record.files.agent.raw, 'agent', { expectedSubAid: task.sub_aid }).statement, statement));
1005
+ if (task.cleanup_authorization_operation_id) {
1006
+ const saved = await this.load(task.cleanup_authorization_operation_id);
1007
+ if (!saved || !matches(await this.record(saved)))
1008
+ throw failure('persistence_unavailable', 'ownership cleanup historical authorization is damaged');
1009
+ return true;
1010
+ }
1011
+ let history;
1012
+ try {
1013
+ history = await this.storage().listOwnershipTasks(context.aid);
1014
+ }
1015
+ catch (error) {
1016
+ throw failure('persistence_unavailable', `ownership cleanup history read failed: ${String(error)}`);
1017
+ }
1018
+ const authorized = (await Promise.all(history.filter((row) => row.record.sub_aid === task.sub_aid).map((row) => this.record(row)))).find(matches);
1019
+ if (!authorized)
1020
+ return false;
1021
+ task.cleanup_authorization_operation_id = authorized.operation_id;
1022
+ await this.save(context);
1023
+ return true;
1024
+ }
1025
+ async resumeSubCleanup(context, ns) {
1026
+ const task = context.task;
1027
+ if (!['bind', 'transfer'].includes(task.operation) || !task.local_roles.includes('sub') || task.local_decision !== 'accept' || ns.lock !== 'unlocked' || task.release_cleanup?.cleaned)
1028
+ return;
1029
+ if (ns.relation_version === 0 && !ns.file?.statement) {
1030
+ const prepared = task.release_cleanup?.files.cleanup;
1031
+ if (!prepared || !prepared.condition?.if_match || prepared.raw !== ns.content || prepared.sha256 !== await ownershipSha256(ns.content) || prepared.published_etag !== undefined && prepared.published_etag !== ns.etag)
1032
+ throw failure('conflict', 'historical cleanup original file was not confirmed');
1033
+ task.release_cleanup.cleaned = true;
1034
+ await this.save(context);
1035
+ return;
1036
+ }
1037
+ if (ns.relation_version !== task.target_relation_version)
1038
+ return;
1039
+ const consensus = await this.deps.files.checkConsensus(task.sub_aid);
1040
+ if (consensus.status !== 'released' || consensus.relation_version !== ns.relation_version || consensus.agent_md_etag !== ns.etag || consensus.agent_md_sha256 !== await ownershipSha256(ns.content))
1041
+ throw failure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'NS changed during historical cleanup observation', consensus.status !== 'invalid');
1042
+ const statement = ns.file?.statement;
1043
+ if (!statement || !task.files.agent || !this.sameStatement(statement, parseOwnershipFile(task.files.agent.raw, 'agent', { expectedSubAid: task.sub_aid }).statement))
1044
+ throw failure('conflict', 'historical cleanup fields belong to another relation');
1045
+ if (!task.release_cleanup) {
1046
+ task.release_cleanup = { files: {}, cleaned: false };
1047
+ await this.save(context);
1048
+ }
1049
+ if (statement || task.release_cleanup.files.cleanup)
1050
+ await this.deps.files.revokeOwneraid({ operation: 'release', sub_aid: task.sub_aid, owner_aid: targetOwner(task),
1051
+ base_relation_version: task.target_relation_version, target_relation_version: ns.relation_version, files: task.release_cleanup.files, expected_agent_statement: statement,
1052
+ persist: () => this.save(context), guard: () => this.save(context) });
1053
+ task.release_cleanup.cleaned = true;
1054
+ await this.save(context);
1055
+ this.event(context, 'ownership.progress');
1056
+ }
1057
+ async cleanup(context, ns) {
1058
+ const task = context.task;
1059
+ if (!task.cleanup_pending || task.cleanup_done)
1060
+ return;
1061
+ const releaseOwner = task.operation === 'release' && task.local_roles.includes('owner');
1062
+ const cleaned = task.operation === 'release' && ns.relation_version === 0 && ns.lock === 'unlocked' && !ns.file?.statement;
1063
+ if (!this.isTarget(task, ns) && !cleaned && !releaseOwner) {
1064
+ task.cleanup_pending = false;
1065
+ task.step = 'cleanup_superseded';
1066
+ await this.save(context);
1067
+ return;
1068
+ }
1069
+ if (task.operation === 'release' && task.local_roles.includes('sub')) {
1070
+ const prior = ns.file?.statement;
1071
+ if (prior && !this.sameStatement(prior, task.base_agent_md?.statement ?? undefined))
1072
+ throw failure('conflict', 'ownership cleanup original relation was replaced');
1073
+ if (prior && !await this.authorizeCleanup(context, prior)) {
1074
+ task.step = 'cleanup_action_required';
1075
+ await this.save(context);
1076
+ this.event(context, 'ownership.action_required');
1077
+ return;
1078
+ }
1079
+ if (prior && (prior.owner_aid !== task.owner_aid || prior.relation_version !== task.base_relation_version))
1080
+ throw failure('conflict', 'ownership cleanup current fields differ');
1081
+ if (prior || task.files.cleanup)
1082
+ await this.deps.files.revokeOwneraid(this.fileTask(context, prior));
1083
+ this.enqueue(task, task.owner_aid, 'sub.cleaned');
1084
+ }
1085
+ if (task.local_roles.includes('owner') && (task.operation === 'release' || task.operation === 'transfer')) {
1086
+ if (releaseOwner && this.isTarget(task, ns) && ns.file?.statement)
1087
+ return;
1088
+ const prepared = task.files[task.operation === 'release' ? 'release' : 'transfer'];
1089
+ if (prepared) {
1090
+ const object = await this.deps.files.readOwner(task.owner_aid, task.sub_aid);
1091
+ if (object.exists && (object.sha256 !== prepared.sha256 || object.content !== prepared.raw))
1092
+ throw failure('conflict', 'ownership cleanup original owner file has been replaced');
1093
+ }
1094
+ await this.deps.files.cleanupOwnerFile(this.fileTask(context));
1095
+ }
1096
+ task.cleanup_pending = false;
1097
+ task.cleanup_done = true;
1098
+ task.step = 'cleanup_completed';
1099
+ await this.save(context);
1100
+ this.event(context, 'ownership.progress');
1101
+ }
1102
+ async drive(context) {
1103
+ const task = context.task;
1104
+ try {
1105
+ if (context.observationError !== undefined && this.ongoing(task))
1106
+ throw context.observationError;
1107
+ await this.inbox(context);
1108
+ if (task.status === 'error' && !task.retryable)
1109
+ return;
1110
+ let ns = await this.observe(task.sub_aid);
1111
+ if (task.status === 'completed') {
1112
+ await this.resumeSubCleanup(context, ns);
1113
+ ns = await this.observe(task.sub_aid);
1114
+ await this.cleanup(context, ns);
1115
+ ns = await this.observe(task.sub_aid);
1116
+ await this.flush(context, ns);
1117
+ return;
1118
+ }
1119
+ if (['rejected', 'stopped'].includes(task.status) || task.status === 'error' && !task.retryable) {
1120
+ await this.flush(context, ns);
1121
+ return;
1122
+ }
1123
+ task.status = 'pending';
1124
+ delete task.code;
1125
+ delete task.reason;
1126
+ task.retryable = true;
1127
+ if (await this.complete(context, ns)) {
1128
+ if (task.status === 'completed' && task.operation === 'release') {
1129
+ await this.cleanup(context, ns);
1130
+ ns = await this.observe(task.sub_aid);
1131
+ }
1132
+ await this.flush(context, ns);
1133
+ return;
1134
+ }
1135
+ if (!this.isBase(task, ns))
1136
+ throw failure(ns.lock === 'locked' && task.operation === 'bind' ? 'owner_locked' : 'conflict', 'ownership relation version or target was replaced');
1137
+ await this.flush(context, ns);
1138
+ if (task.local_decision === 'pending' && task.operation !== 'release') {
1139
+ task.step = 'awaiting_local_approval';
1140
+ await this.save(context);
1141
+ if (task.request_validated)
1142
+ this.event(context, 'ownership.request');
1143
+ return;
1144
+ }
1145
+ if (task.expires_at && task.expires_at <= this.now())
1146
+ throw failure('conflict', 'ownership transfer authorization expired');
1147
+ const accepted = participants(task).every((aid) => task.approvals[aid] === 'accept');
1148
+ if (task.operation === 'bind') {
1149
+ if (task.local_roles.includes('owner') && accepted) {
1150
+ await this.deps.files.declareSubaid(this.fileTask(context));
1151
+ this.enqueue(task, task.sub_aid, 'owner.ready');
1152
+ if (task.local_roles.includes('sub'))
1153
+ task.hints['owner.ready'] = true;
1154
+ await this.save(context);
1155
+ }
1156
+ if (task.local_roles.includes('sub') && (task.hints['owner.ready'] || task.approvals[task.owner_aid] === 'accept' || task.files.agent)) {
1157
+ await this.deps.files.declareOwneraid(this.fileTask(context));
1158
+ }
1159
+ task.step = accepted ? 'awaiting_confirmation' : 'awaiting_peer_approval';
1160
+ }
1161
+ else if (task.operation === 'transfer') {
1162
+ if (task.local_roles.includes('owner') && accepted) {
1163
+ await this.deps.files.transferOwneraid(this.fileTask(context));
1164
+ for (const peer of [...new Set([task.sub_aid, task.new_owner_aid])])
1165
+ this.enqueue(task, peer, 'transfer.ready');
1166
+ if (task.local_roles.includes('sub'))
1167
+ task.hints['transfer.ready'] = true;
1168
+ await this.save(context);
1169
+ }
1170
+ if (task.local_roles.includes('new_owner') && (task.hints['transfer.ready'] || task.files.owner)) {
1171
+ await this.deps.files.declareSubaid(this.fileTask(context));
1172
+ this.enqueue(task, task.sub_aid, 'owner.ready');
1173
+ if (task.local_roles.includes('sub'))
1174
+ task.hints['owner.ready'] = true;
1175
+ await this.save(context);
1176
+ }
1177
+ if (task.local_roles.includes('sub') && (task.hints['owner.ready'] || task.files.agent) && (task.hints['transfer.ready'] || task.local_roles.includes('owner')))
1178
+ await this.deps.files.declareOwneraid(this.fileTask(context));
1179
+ task.step = accepted || task.hints['transfer.ready'] ? 'awaiting_confirmation' : 'awaiting_peer_approval';
1180
+ }
1181
+ else {
1182
+ if (task.local_roles.includes('owner'))
1183
+ await this.deps.files.revokeSubaid(this.fileTask(context));
1184
+ task.step = 'awaiting_release_confirmation';
1185
+ }
1186
+ await this.save(context);
1187
+ ns = await this.observe(task.sub_aid);
1188
+ await this.complete(context, ns);
1189
+ await this.flush(context, ns);
1190
+ this.event(context, 'ownership.progress');
1191
+ }
1192
+ catch (error) {
1193
+ let code = ownershipErrorCode(error);
1194
+ if (code === 'conflict' && /\blease\b|fencing|task revision/.test(String(error)))
1195
+ throw failure('persistence_unavailable', error instanceof Error ? error.message : String(error));
1196
+ if (code === 'persistence_unavailable')
1197
+ throw error;
1198
+ if (task.status !== 'completed' && !['rejected', 'stopped'].includes(task.status) && (code === 'conflict' || code === 'owner_locked')) {
1199
+ try {
1200
+ const current = await this.observe(task.sub_aid);
1201
+ if (await this.complete(context, current)) {
1202
+ await this.flush(context, current);
1203
+ return;
1204
+ }
1205
+ }
1206
+ catch (confirmationError) {
1207
+ code = ownershipErrorCode(confirmationError);
1208
+ error = confirmationError;
1209
+ if (code === 'persistence_unavailable')
1210
+ throw confirmationError;
1211
+ }
1212
+ }
1213
+ task.code = code;
1214
+ task.reason = error instanceof Error ? error.message : String(error);
1215
+ task.retryable = !['completed', 'rejected', 'stopped'].includes(task.status) && (code === 'unavailable' || code === 'unsupported_protocol');
1216
+ if (task.status !== 'completed' && !['rejected', 'stopped'].includes(task.status)) {
1217
+ task.status = task.retryable ? 'pending' : code === 'conflict' || code === 'owner_locked' ? 'stopped' : 'error';
1218
+ if (!task.retryable) {
1219
+ task.step = 'stopped';
1220
+ task.inbox = [];
1221
+ task.outbox = task.outbox.filter((item) => ['bind.reject', 'transfer.reject', 'operation.stopped'].includes(item.payload.action));
1222
+ }
1223
+ }
1224
+ await this.save(context);
1225
+ this.event(context, task.status === 'stopped' ? 'ownership.stopped' : 'ownership.error');
1226
+ }
1227
+ }
1228
+ }
1229
+ //# sourceMappingURL=ownership-workflow.js.map