@agentunion/fastaun 0.5.21 → 0.5.22

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 (71) hide show
  1. package/CHANGELOG.md +58 -1
  2. package/_packed_docs/CHANGELOG.md +58 -1
  3. package/_packed_docs/INDEX.md +465 -453
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +168 -164
  5. 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 +307 -67
  6. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
  7. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +5 -5
  8. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
  9. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +669 -666
  10. package/_packed_docs/sdk/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
  11. package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -0
  12. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +16 -0
  13. package/_packed_docs/sdk/09-group-rpc-manual.md +10 -0
  14. package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
  15. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +3 -1
  16. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +2 -0
  17. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +138 -134
  18. package/_packed_docs/sdk/INDEX.md +255 -239
  19. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +939 -930
  20. 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 +312 -363
  21. 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 +322 -553
  22. 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 +4 -4
  23. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +484 -480
  24. 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 +190 -97
  25. 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 +158 -127
  26. package/dist/agent-md.d.ts +1 -1
  27. package/dist/agent-md.js +11 -4
  28. package/dist/agent-md.js.map +1 -1
  29. package/dist/auth.d.ts +1 -0
  30. package/dist/auth.js +18 -2
  31. package/dist/auth.js.map +1 -1
  32. package/dist/client/delivery.d.ts +28 -0
  33. package/dist/client/delivery.js +399 -44
  34. package/dist/client/delivery.js.map +1 -1
  35. package/dist/client/group-state.js +3 -0
  36. package/dist/client/group-state.js.map +1 -1
  37. package/dist/client/rpc-pipeline.js +28 -2
  38. package/dist/client/rpc-pipeline.js.map +1 -1
  39. package/dist/client/v2-e2ee.js +109 -39
  40. package/dist/client/v2-e2ee.js.map +1 -1
  41. package/dist/client.js +38 -10
  42. package/dist/client.js.map +1 -1
  43. package/dist/group-fs.d.ts +1 -0
  44. package/dist/group-fs.js +37 -19
  45. package/dist/group-fs.js.map +1 -1
  46. package/dist/org-protocol.d.ts +1 -0
  47. package/dist/org-protocol.js +54 -7
  48. package/dist/org-protocol.js.map +1 -1
  49. package/dist/ownership-v21.d.ts +21 -3
  50. package/dist/ownership-v21.js +135 -37
  51. package/dist/ownership-v21.js.map +1 -1
  52. package/dist/ownership-workflow.d.ts +7 -2
  53. package/dist/ownership-workflow.js +173 -17
  54. package/dist/ownership-workflow.js.map +1 -1
  55. package/dist/storage/lowlevel.d.ts +6 -0
  56. package/dist/storage/lowlevel.js +24 -7
  57. package/dist/storage/lowlevel.js.map +1 -1
  58. package/dist/storage/read-cache.d.ts +70 -0
  59. package/dist/storage/read-cache.js +625 -0
  60. package/dist/storage/read-cache.js.map +1 -0
  61. package/dist/storage/vfs.d.ts +2 -0
  62. package/dist/storage/vfs.js +50 -15
  63. package/dist/storage/vfs.js.map +1 -1
  64. package/dist/tools/cross-sdk-agent.d.ts +21 -1
  65. package/dist/tools/cross-sdk-agent.js +301 -19
  66. package/dist/tools/cross-sdk-agent.js.map +1 -1
  67. package/dist/transport.js +6 -2
  68. package/dist/transport.js.map +1 -1
  69. package/dist/version.d.ts +1 -1
  70. package/dist/version.js +1 -1
  71. package/package.json +1 -1
@@ -59,6 +59,7 @@ export interface OwnershipConsensusResult {
59
59
  owner_aid: string | null;
60
60
  relation_version: number;
61
61
  lock: 'locked' | 'unlocked';
62
+ ownership_status?: string;
62
63
  agent_md_etag: string;
63
64
  agent_md_sha256: string;
64
65
  owner_md_sha256: string;
@@ -84,6 +85,9 @@ export interface OwnershipAgentObservation {
84
85
  protocol: string;
85
86
  relation_version: number;
86
87
  lock: 'locked' | 'unlocked';
88
+ ownership_status?: string;
89
+ pending?: boolean;
90
+ stage_supported?: boolean;
87
91
  owner_aid?: string | null;
88
92
  observed_at: number;
89
93
  recovery_ready?: boolean;
@@ -123,6 +127,7 @@ export interface OwnershipSavedFile {
123
127
  }
124
128
  export interface OwnershipFileTask {
125
129
  operation_id?: string;
130
+ stage?: boolean;
126
131
  operation: 'bind' | 'release' | 'transfer';
127
132
  sub_aid: string;
128
133
  owner_aid: string;
@@ -137,6 +142,16 @@ export interface OwnershipFileTask {
137
142
  sha256: string;
138
143
  etag: string;
139
144
  statement: OwnershipStatementV21 | null;
145
+ pending?: boolean;
146
+ };
147
+ pending_agent_md?: {
148
+ raw: string;
149
+ sha256: string;
150
+ etag: string;
151
+ };
152
+ owner_proposal?: {
153
+ raw: string;
154
+ sha256: string;
140
155
  };
141
156
  base_owner_md?: {
142
157
  exists: boolean;
@@ -153,10 +168,10 @@ export interface OwnershipFileDependencies {
153
168
  currentType?(): string;
154
169
  now(): number;
155
170
  readAgentMdFresh(subAid: string): Promise<OwnershipAgentObservation>;
156
- submitAgentMdRaw(subAid: string, raw: string, condition: OwnershipSavedFile['condition'], assertActive?: () => void): Promise<OwnershipAgentObservation>;
171
+ submitAgentMdRaw(subAid: string, raw: string, condition: OwnershipSavedFile['condition'], assertActive?: () => void, stage?: boolean): Promise<OwnershipAgentObservation>;
157
172
  readOwnerMd(ownerAid: string, subAid: string): Promise<OwnershipOwnerObservation>;
158
173
  writeOwnerMd(ownerAid: string, subAid: string, raw: string, original?: Pick<OwnershipOwnerObservation, 'exists' | 'content' | 'sha256' | 'version'>, assertActive?: () => void): Promise<OwnershipOwnerObservation>;
159
- deleteOwnerMd(ownerAid: string, subAid: string): Promise<unknown>;
174
+ deleteOwnerMd(ownerAid: string, subAid: string, expectedVersion: number): Promise<unknown>;
160
175
  resolveCertificate(aid: string, fingerprint: string, purpose?: 'signing' | 'group_identity'): Promise<OwnershipCertificate>;
161
176
  }
162
177
  export declare function ownershipFailure(code: string, reason: string, retryable?: boolean): StateError;
@@ -172,8 +187,10 @@ export declare class OwnershipFiles {
172
187
  declareOwneraid(task: OwnershipFileTask): Promise<OwnershipAgentObservation>;
173
188
  revokeSubaid(task: OwnershipFileTask): Promise<OwnershipOwnerObservation>;
174
189
  revokeOwneraid(task: OwnershipFileTask): Promise<OwnershipAgentObservation>;
190
+ cancelPendingBind(task: OwnershipFileTask, candidate: OwnershipSavedFile): Promise<OwnershipAgentObservation>;
175
191
  transferOwneraid(task: OwnershipFileTask): Promise<OwnershipOwnerObservation>;
176
192
  cleanupOwnerFile(task: OwnershipFileTask): Promise<void>;
193
+ cancelOwnerProposal(task: OwnershipFileTask): Promise<void>;
177
194
  readAgent(subAid: string): Promise<OwnershipAgentObservation>;
178
195
  readOwner(ownerAid: string, subAid: string): Promise<OwnershipOwnerObservation>;
179
196
  verifyFile(raw: string, role: OwnershipFileRole, subAid: string, ownerAid?: string): Promise<OwnershipFile>;
@@ -183,6 +200,7 @@ export declare class OwnershipFiles {
183
200
  private statement;
184
201
  private assertBase;
185
202
  private requireOriginal;
203
+ private currentConsensus;
186
204
  private savedFile;
187
205
  private validSavedCondition;
188
206
  private publishOwner;
@@ -203,7 +221,7 @@ export declare class OwnershipStorageAdapter {
203
221
  readObject(ownerAid: string, path: string): Promise<OwnershipOwnerObservation>;
204
222
  readOwnerMd(ownerAid: string, subAid: string): Promise<OwnershipOwnerObservation>;
205
223
  writeOwnerMd(ownerAid: string, subAid: string, raw: string, original?: Pick<OwnershipOwnerObservation, 'exists' | 'content' | 'sha256' | 'version'>, assertActive?: () => void): Promise<OwnershipOwnerObservation>;
206
- deleteOwnerMd(ownerAid: string, subAid: string): Promise<void>;
224
+ deleteOwnerMd(ownerAid: string, subAid: string, expectedVersion: number): Promise<void>;
207
225
  private requireLocal;
208
226
  }
209
227
  export {};
@@ -239,18 +239,36 @@ export class OwnershipFiles {
239
239
  const began = this.deps.now();
240
240
  const result = { status: 'unavailable', sub_aid: subAid, owner_aid: null, relation_version: 0, lock: 'unlocked', agent_md_etag: '', agent_md_sha256: '', owner_md_sha256: '' };
241
241
  try {
242
- const before = await this.readAgent(subAid);
242
+ let before = await this.readAgent(subAid);
243
+ if (before.ownership_status)
244
+ result.ownership_status = before.ownership_status;
243
245
  Object.assign(result, { relation_version: before.relation_version, lock: before.lock, agent_md_etag: before.etag, agent_md_sha256: before.exists ? ownershipSha256(before.content) : '' });
244
- const agent = before.exists ? await this.verifyFile(before.content, 'agent', subAid) : undefined;
245
- const statement = agent?.statement;
246
- if (before.relation_version !== (statement?.relation_version ?? 0))
247
- throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
248
- if (before.lock === 'locked' && (!statement || statement.relation_version !== before.relation_version))
249
- throw ownershipFailure('unavailable', 'NS locked relation does not match agent.md', true);
250
- const currentOwner = before.lock === 'locked' ? statement.owner_aid : null;
251
- if (before.owner_aid !== undefined && before.owner_aid !== currentOwner)
252
- throw ownershipFailure('unavailable', 'NS owner metadata does not match agent.md', true);
253
- result.owner_aid = currentOwner;
246
+ let agent = before.exists ? await this.verifyFile(before.content, 'agent', subAid) : undefined;
247
+ let statement = agent?.statement;
248
+ let projectionRetried = false;
249
+ const refreshProjection = async () => {
250
+ if (projectionRetried)
251
+ throw ownershipFailure('unavailable', 'NS relationship projection does not match current signed materials', true);
252
+ projectionRetried = true;
253
+ const current = await this.readAgent(subAid);
254
+ if (current.exists !== before.exists || current.content !== before.content)
255
+ throw ownershipFailure('unavailable', 'agent.md changed while rechecking NS projection', true);
256
+ const currentAgent = current.exists ? await this.verifyFile(current.content, 'agent', subAid) : undefined;
257
+ const currentStatement = currentAgent?.statement;
258
+ const currentOwner = current.lock === 'locked' ? currentStatement?.owner_aid : null;
259
+ if (current.relation_version !== (currentStatement?.relation_version ?? 0) || current.lock === 'locked' && !currentStatement || current.owner_aid !== undefined && current.owner_aid !== currentOwner)
260
+ throw ownershipFailure('unavailable', 'NS relationship projection still does not match agent.md', true);
261
+ before = current;
262
+ agent = currentAgent;
263
+ statement = currentStatement;
264
+ Object.assign(result, { relation_version: before.relation_version, lock: before.lock, agent_md_etag: before.etag, agent_md_sha256: before.exists ? ownershipSha256(before.content) : '' });
265
+ };
266
+ const agentProjectionMatches = () => before.relation_version === (statement?.relation_version ?? 0) && !(before.lock === 'locked' && !statement) && (before.owner_aid === undefined || before.owner_aid === (before.lock === 'locked' ? statement?.owner_aid ?? null : null));
267
+ if (!agentProjectionMatches())
268
+ await refreshProjection();
269
+ if (!agentProjectionMatches())
270
+ throw ownershipFailure('unavailable', 'NS relationship projection does not match agent.md', true);
271
+ result.owner_aid = before.lock === 'locked' ? statement.owner_aid : null;
254
272
  result.status = statement ? 'single_sided' : 'unbound';
255
273
  const ownerAid = statement?.owner_aid ?? target?.owner_aid;
256
274
  let ownerBefore;
@@ -261,7 +279,10 @@ export class OwnershipFiles {
261
279
  result.owner_md = { owner_aid: ownerAid, bucket: 'default', path: `sub_aids/${subAid}/owner.md`, exists: owner.exists, content: owner.content, sha256: owner.sha256 ?? '', observed_at: owner.observed_at };
262
280
  if (owner.exists) {
263
281
  const declaration = await this.verifyFile(owner.content, 'owner', subAid, ownerAid);
264
- if (statement) {
282
+ if (statement && (before.pending || before.ownership_status === 'pending')) {
283
+ result.status = 'single_sided';
284
+ }
285
+ else if (statement) {
265
286
  if (declaration.statement.relation_version !== before.relation_version)
266
287
  throw new ValidationError('ownership relation_version mismatch');
267
288
  result.status = declaration.action === 'release' ? 'released' : 'consensus';
@@ -271,10 +292,14 @@ export class OwnershipFiles {
271
292
  }
272
293
  }
273
294
  else if (statement)
274
- result.status = 'released';
295
+ result.status = before.pending || before.ownership_status === 'pending' ? 'single_sided' : 'released';
275
296
  }
276
- if (statement && before.lock !== (result.status === 'released' ? 'unlocked' : 'locked'))
277
- throw ownershipFailure('unavailable', 'NS lock does not match current ownership materials', true);
297
+ if (before.lock !== (result.status === 'consensus' ? 'locked' : 'unlocked')) {
298
+ await refreshProjection();
299
+ if (before.lock !== (result.status === 'consensus' ? 'locked' : 'unlocked'))
300
+ throw ownershipFailure('unavailable', 'NS lock projection still conflicts with signed ownership files', true);
301
+ }
302
+ result.lock = before.lock;
278
303
  result.owner_aid = result.status === 'consensus' ? statement.owner_aid : null;
279
304
  const after = await this.readAgent(subAid);
280
305
  if (ownerAid && ownerBefore) {
@@ -313,13 +338,22 @@ export class OwnershipFiles {
313
338
  await this.assertBase(task, agent);
314
339
  if (task.operation === 'transfer')
315
340
  await this.requireTransfer(task);
341
+ if (task.operation === 'bind' && task.stage)
342
+ return { owner: ownerAid, ownership: { statement: this.statement(task, ownerAid, task.target_relation_version) } };
316
343
  const owner = await this.readOwner(ownerAid, task.sub_aid);
317
344
  if (!owner.exists)
318
345
  throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
319
346
  const declaration = await this.verifyFile(owner.content, 'owner', task.sub_aid, ownerAid);
347
+ if (task.owner_proposal && (owner.content !== task.owner_proposal.raw || owner.sha256 !== task.owner_proposal.sha256))
348
+ throw ownershipFailure('conflict', 'owner binding proposal changed');
349
+ const savedOwner = task.files.owner;
350
+ if (savedOwner && (savedOwner.raw !== owner.content || savedOwner.hash !== owner.sha256))
351
+ throw ownershipFailure('conflict', 'fixed target owner declaration changed');
352
+ if (declaration.action === 'declare' && declaration.statement.relation_version < task.target_relation_version)
353
+ throw ownershipFailure('unavailable', 'target owner declaration is not ready', true);
320
354
  if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
321
355
  throw ownershipFailure('conflict', 'target owner declaration does not match this task');
322
- return { owner: ownerAid, ownership: { statement: this.statement(task, ownerAid, task.target_relation_version) } };
356
+ return { owner: ownerAid, ownership: { statement: task.owner_proposal ? declaration.statement : this.statement(task, ownerAid, task.target_relation_version) } };
323
357
  });
324
358
  }
325
359
  async revokeSubaid(task) {
@@ -333,8 +367,9 @@ export class OwnershipFiles {
333
367
  async revokeOwneraid(task) {
334
368
  this.requireLocal(task.sub_aid);
335
369
  return this.publishAgent(task, async (agent) => {
336
- if (agent.lock === 'locked')
337
- throw ownershipFailure('owner_locked', 'NS has not confirmed release');
370
+ const consensus = await this.currentConsensus(agent);
371
+ if (consensus.lock === 'locked')
372
+ throw ownershipFailure('owner_locked', 'current ownership materials have not confirmed release');
338
373
  const parsed = parseOwnershipFile(agent.content, 'agent', { expectedSubAid: task.sub_aid });
339
374
  if (!parsed.statement && agent.relation_version === 0)
340
375
  return {};
@@ -347,6 +382,19 @@ export class OwnershipFiles {
347
382
  return {};
348
383
  }, true);
349
384
  }
385
+ async cancelPendingBind(task, candidate) {
386
+ this.requireLocal(task.sub_aid);
387
+ const current = await this.readAgent(task.sub_aid), saved = task.files.agent;
388
+ if (!(saved && current.content === saved.raw && ownershipSha256(current.content) === saved.hash)) {
389
+ if (current.lock !== 'unlocked' || !(current.pending || current.ownership_status === 'pending') || current.content !== candidate.raw || ownershipSha256(current.content) !== candidate.hash || candidate.published_etag !== undefined && current.etag !== candidate.published_etag)
390
+ throw ownershipFailure('conflict', 'pending ownership candidate changed before rejection cleanup');
391
+ }
392
+ return this.publishAgent({ ...task, operation: 'release', base_relation_version: task.target_relation_version }, async (agent) => {
393
+ if (agent.lock !== 'unlocked' || !(agent.pending || agent.ownership_status === 'pending') || agent.content !== candidate.raw || ownershipSha256(agent.content) !== candidate.hash || candidate.published_etag !== undefined && agent.etag !== candidate.published_etag)
394
+ throw ownershipFailure('conflict', 'pending ownership candidate changed before signing rejection cleanup');
395
+ return {};
396
+ }, true);
397
+ }
350
398
  async transferOwneraid(task) {
351
399
  this.requireLocal(task.owner_aid);
352
400
  ownershipAid(task.new_owner_aid, 'new_owner_aid');
@@ -387,12 +435,30 @@ export class OwnershipFiles {
387
435
  if (beforeDelete.exists && (beforeDelete.content !== source.raw || beforeDelete.sha256 !== source.hash))
388
436
  throw ownershipFailure('conflict', 'owner file changed before cleanup');
389
437
  if (beforeDelete.exists) {
438
+ if (!Number.isSafeInteger(beforeDelete.version) || beforeDelete.version <= 0)
439
+ throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
390
440
  task.assertActive?.();
391
- await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid);
441
+ await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid, beforeDelete.version);
392
442
  }
393
443
  task.files.cleanup.deleted = true;
394
444
  await task.persist();
395
445
  }
446
+ async cancelOwnerProposal(task) {
447
+ this.requireLocal(task.owner_aid);
448
+ const source = this.savedFile(task, 'owner');
449
+ if (!source)
450
+ throw ownershipFailure('conflict', 'owner binding proposal is unavailable');
451
+ await this.assertBase(task, await this.readAgent(task.sub_aid));
452
+ const current = await this.readOwner(task.owner_aid, task.sub_aid);
453
+ if (!current.exists)
454
+ return;
455
+ if (current.content !== source.raw || current.sha256 !== source.hash)
456
+ throw ownershipFailure('conflict', 'owner binding proposal changed before rejection cleanup');
457
+ if (!Number.isSafeInteger(current.version) || current.version <= 0)
458
+ throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
459
+ task.assertActive?.();
460
+ await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid, current.version);
461
+ }
396
462
  async readAgent(subAid) {
397
463
  if (this.deps.currentType && subAid === this.deps.currentAid().aid)
398
464
  requireOwnershipApplicable(this.deps.currentType());
@@ -465,6 +531,15 @@ export class OwnershipFiles {
465
531
  }
466
532
  async assertBase(task, agent) {
467
533
  ownershipInteger(task.base_relation_version, 'base_relation_version', 0);
534
+ const candidate = task.files.agent ? { raw: task.files.agent.raw, sha256: task.files.agent.hash, etag: task.files.agent.published_etag } : task.pending_agent_md ?? (task.base_agent_md?.pending ? task.base_agent_md : undefined);
535
+ if (task.operation === 'bind' && task.base_agent_md?.pending && agent.lock === 'unlocked' && (agent.pending || agent.ownership_status === 'pending') && agent.relation_version === task.base_relation_version && agent.content === task.base_agent_md.raw && ownershipSha256(agent.content) === task.base_agent_md.sha256 && agent.etag === task.base_agent_md.etag)
536
+ return;
537
+ if (task.operation === 'bind' && (agent.pending || agent.ownership_status === 'pending') && agent.lock === 'unlocked' && agent.relation_version === task.target_relation_version && candidate && agent.content === candidate.raw && ownershipSha256(agent.content) === candidate.sha256 && (!candidate.etag || agent.etag === candidate.etag)) {
538
+ const parsed = await this.verifyFile(agent.content, 'agent', task.sub_aid);
539
+ if (parsed.statement?.owner_aid !== task.owner_aid)
540
+ throw ownershipFailure('conflict', 'pending owner does not match this task');
541
+ return;
542
+ }
468
543
  const target = ownershipInteger(task.target_relation_version, 'target_relation_version');
469
544
  if (target !== task.base_relation_version + 1 && !(task.operation === 'release' && target === task.base_relation_version) || agent.relation_version !== task.base_relation_version)
470
545
  throw ownershipFailure('conflict', 'NS relation_version changed');
@@ -475,17 +550,19 @@ export class OwnershipFiles {
475
550
  }
476
551
  const parsed = await this.verifyFile(agent.content, 'agent', task.sub_aid);
477
552
  const base = task.base_agent_md;
553
+ if (base?.pending && (agent.content !== base.raw || agent.etag !== base.etag))
554
+ throw ownershipFailure('conflict', 'the pending ownership original was replaced');
478
555
  if (base && (agent.exists !== base.exists || (base.statement ? !parsed.statement || Object.keys(base.statement).some((field) => base.statement[field] !== parsed.statement[field]) : parsed.statement || agent.content !== base.raw || ownershipSha256(agent.content) !== base.sha256)))
479
556
  throw ownershipFailure('conflict', 'the original ownership statement was replaced');
480
557
  if (agent.relation_version !== (parsed.statement?.relation_version ?? 0))
481
558
  throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
482
559
  if (task.operation === 'bind' && parsed.frontmatter.type === 'human')
483
560
  throw ownershipFailure('invalid', 'human cannot declare an owner');
484
- if (task.operation === 'bind' && agent.lock !== 'unlocked')
561
+ if (task.operation === 'bind' && parsed.statement && !(agent.lock === 'unlocked' && (agent.pending || agent.ownership_status === 'pending')) && (await this.currentConsensus(agent)).status !== 'released')
485
562
  throw ownershipFailure('owner_locked', 'an existing owner relation is locked');
486
- if (task.operation === 'bind' && parsed.statement?.owner_aid === this.targetOwner(task))
563
+ if (task.operation === 'bind' && parsed.statement?.owner_aid === this.targetOwner(task) && !(agent.pending || agent.ownership_status === 'pending'))
487
564
  throw ownershipFailure('conflict', 'clear the released agent.md statement before binding the same owner');
488
- if (task.operation !== 'bind' && (agent.lock !== 'locked' || parsed.statement?.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
565
+ if (task.operation !== 'bind' && (parsed.statement?.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version || (await this.currentConsensus(agent)).status !== 'consensus'))
489
566
  throw ownershipFailure('conflict', 'the original relation is no longer current');
490
567
  }
491
568
  async requireOriginal(task) {
@@ -494,6 +571,14 @@ export class OwnershipFiles {
494
571
  if (consensus.status !== 'consensus' || consensus.owner_aid !== task.owner_aid || consensus.relation_version !== task.base_relation_version)
495
572
  throw ownershipFailure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'the original ownership consensus cannot be confirmed', consensus.status !== 'invalid');
496
573
  }
574
+ async currentConsensus(agent) {
575
+ const result = await this.checkConsensus(agent.sub_aid);
576
+ if (result.status === 'invalid' || result.status === 'unavailable' || result.status === 'not_applicable')
577
+ throw ownershipFailure(result.code ?? result.status, result.reason ?? 'ownership materials cannot be verified', result.retryable);
578
+ if (result.relation_version !== agent.relation_version || result.agent_md_etag !== agent.etag || result.agent_md_sha256 !== (agent.exists ? ownershipSha256(agent.content) : ''))
579
+ throw ownershipFailure('unavailable', 'agent.md changed during ownership verification', true);
580
+ return result;
581
+ }
497
582
  savedFile(task, key) {
498
583
  const saved = task.files[key];
499
584
  if (saved && (typeof saved.raw !== 'string' || saved.hash !== ownershipSha256(saved.raw) || !this.validSavedCondition(key, saved.condition) || saved.published_etag !== undefined && (typeof saved.published_etag !== 'string' || !saved.published_etag)))
@@ -562,6 +647,8 @@ export class OwnershipFiles {
562
647
  async publishAgent(task, prepare, cleanup = false) {
563
648
  let saved = this.savedFile(task, 'agent');
564
649
  const current = await this.readAgent(task.sub_aid);
650
+ if (task.stage && !cleanup && current.stage_supported !== true)
651
+ throw ownershipFailure('unsupported_protocol', 'NS does not support Ownership Stage', true);
565
652
  if (!saved) {
566
653
  const fields = await prepare(current);
567
654
  if (cleanup && !parseOwnershipFile(current.content, 'agent', { now: this.deps.now() }).statement)
@@ -573,7 +660,8 @@ export class OwnershipFiles {
573
660
  await task.persist();
574
661
  }
575
662
  await this.verifyFile(saved.raw, 'agent', task.sub_aid);
576
- if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === (cleanup ? 0 : task.target_relation_version) && current.lock === (cleanup ? 'unlocked' : 'locked')) {
663
+ const pendingBind = task.operation === 'bind' && !cleanup && current.lock === 'unlocked' && (current.pending || current.ownership_status === 'pending');
664
+ if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === (cleanup ? 0 : task.target_relation_version) && (current.lock === (cleanup ? 'unlocked' : 'locked') || pendingBind)) {
577
665
  if (saved.published_etag !== undefined && current.etag !== saved.published_etag)
578
666
  throw ownershipFailure('conflict', 'agent.md published ETag changed');
579
667
  saved.published_etag = current.etag;
@@ -583,26 +671,34 @@ export class OwnershipFiles {
583
671
  if (saved.condition.if_none_match === '*' ? current.exists : !current.exists || current.etag !== saved.condition.if_match)
584
672
  throw ownershipFailure('conflict', 'agent.md original ETag condition changed');
585
673
  if (cleanup) {
586
- if (current.lock !== 'unlocked' || current.relation_version !== task.base_relation_version)
674
+ if ((await this.currentConsensus(current)).lock !== 'unlocked' || current.relation_version !== task.base_relation_version)
587
675
  throw ownershipFailure('conflict', 'release target is no longer current');
588
676
  }
589
677
  else {
590
678
  await this.assertBase(task, current);
591
679
  if (task.operation === 'transfer')
592
680
  await this.requireTransfer(task);
593
- const target = await this.readOwner(this.targetOwner(task), task.sub_aid);
594
- if (!target.exists)
595
- throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
596
- const declaration = await this.verifyFile(target.content, 'owner', task.sub_aid, this.targetOwner(task));
597
- const savedOwner = task.files.owner;
598
- if (savedOwner && (savedOwner.raw !== target.content || savedOwner.hash !== target.sha256))
599
- throw ownershipFailure('conflict', 'fixed target owner declaration changed');
600
- if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
601
- throw ownershipFailure('conflict', 'target owner declaration changed');
681
+ const target = task.operation === 'bind' && task.stage ? undefined : await this.readOwner(this.targetOwner(task), task.sub_aid);
682
+ if (target) {
683
+ if (!target.exists)
684
+ throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
685
+ const declaration = await this.verifyFile(target.content, 'owner', task.sub_aid, this.targetOwner(task));
686
+ const savedOwner = task.files.owner;
687
+ if (task.owner_proposal && (target.content !== task.owner_proposal.raw || target.sha256 !== task.owner_proposal.sha256))
688
+ throw ownershipFailure('conflict', 'owner binding proposal changed');
689
+ if (savedOwner && (savedOwner.raw !== target.content || savedOwner.hash !== target.sha256))
690
+ throw ownershipFailure('conflict', 'fixed target owner declaration changed');
691
+ if (declaration.action === 'declare' && declaration.statement.relation_version < task.target_relation_version)
692
+ throw ownershipFailure('unavailable', 'target owner declaration is not ready', true);
693
+ if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
694
+ throw ownershipFailure('conflict', 'target owner declaration changed');
695
+ }
602
696
  }
603
697
  task.assertActive?.();
604
- const published = await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition, task.assertActive);
605
- if (published.content !== saved.raw || published.relation_version !== (cleanup ? 0 : task.target_relation_version) || published.lock !== (cleanup ? 'unlocked' : 'locked'))
698
+ const published = task.stage && !cleanup ? await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition, task.assertActive, true) : await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition, task.assertActive);
699
+ if (task.stage && !cleanup && (published.stage_supported !== true || published.lock !== 'unlocked' || !(published.pending || published.ownership_status === 'pending')))
700
+ throw ownershipFailure('unsupported_protocol', 'NS did not confirm Ownership Stage', true);
701
+ if (published.content !== saved.raw || published.relation_version !== (cleanup ? 0 : task.target_relation_version) || published.lock !== (cleanup ? 'unlocked' : 'locked') && !(task.operation === 'bind' && !cleanup && published.lock === 'unlocked' && (published.pending || published.ownership_status === 'pending')))
606
702
  throw ownershipFailure('unavailable', 'agent.md publication could not be verified', true);
607
703
  saved.published_etag = published.etag;
608
704
  await task.persist();
@@ -715,10 +811,12 @@ export class OwnershipStorageAdapter {
715
811
  await this.call('storage.complete_upload', complete);
716
812
  return this.readOwnerMd(ownerAid, subAid);
717
813
  }
718
- async deleteOwnerMd(ownerAid, subAid) {
814
+ async deleteOwnerMd(ownerAid, subAid, expectedVersion) {
719
815
  this.requireLocal(ownerAid);
720
816
  ownershipAid(subAid, 'sub_aid');
721
- await this.call('storage.delete_object', { owner_aid: ownerAid, bucket: 'default', object_key: `sub_aids/${subAid}/owner.md` });
817
+ if (!Number.isSafeInteger(expectedVersion) || expectedVersion <= 0)
818
+ throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
819
+ await this.call('storage.delete_object', { owner_aid: ownerAid, bucket: 'default', object_key: `sub_aids/${subAid}/owner.md`, expected_version: expectedVersion });
722
820
  if ((await this.readOwnerMd(ownerAid, subAid)).exists)
723
821
  throw ownershipFailure('unavailable', 'Storage deletion could not be confirmed', true);
724
822
  }