@agentunion/fastaun 0.5.20 → 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 (128) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/_packed_docs/CHANGELOG.md +104 -0
  3. package/_packed_docs/INDEX.md +59 -34
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +59 -52
  5. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
  6. package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
  7. package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
  8. package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
  9. 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
  10. 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
  11. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
  12. package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
  13. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
  14. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
  15. package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +33 -22
  16. package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
  17. package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
  18. 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
  19. 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
  20. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  21. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
  22. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
  23. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
  24. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
  25. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +163 -14
  26. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
  27. package/_packed_docs/sdk/09-group-rpc-manual.md +15 -2
  28. package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
  29. package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
  30. package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +73 -0
  31. package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +90 -0
  32. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +33 -25
  33. package/_packed_docs/sdk/INDEX.md +67 -33
  34. package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +74 -57
  35. 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 -300
  36. 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 -382
  37. 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 -708
  38. package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +28 -20
  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 +190 -97
  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 +158 -127
  41. package/dist/agent-md-schema.js +6 -4
  42. package/dist/agent-md-schema.js.map +1 -1
  43. package/dist/agent-md.d.ts +22 -2
  44. package/dist/agent-md.js +92 -68
  45. package/dist/agent-md.js.map +1 -1
  46. package/dist/aid-store.d.ts +13 -0
  47. package/dist/aid-store.js +50 -15
  48. package/dist/aid-store.js.map +1 -1
  49. package/dist/auth.d.ts +1 -0
  50. package/dist/auth.js +22 -2
  51. package/dist/auth.js.map +1 -1
  52. package/dist/cert-utils.d.ts +1 -0
  53. package/dist/cert-utils.js +13 -0
  54. package/dist/cert-utils.js.map +1 -1
  55. package/dist/client/delivery.d.ts +87 -1
  56. package/dist/client/delivery.js +1387 -139
  57. package/dist/client/delivery.js.map +1 -1
  58. package/dist/client/group-state.js +3 -0
  59. package/dist/client/group-state.js.map +1 -1
  60. package/dist/client/rpc-pipeline.d.ts +4 -1
  61. package/dist/client/rpc-pipeline.js +149 -37
  62. package/dist/client/rpc-pipeline.js.map +1 -1
  63. package/dist/client/v2-e2ee.d.ts +12 -2
  64. package/dist/client/v2-e2ee.js +831 -304
  65. package/dist/client/v2-e2ee.js.map +1 -1
  66. package/dist/client.d.ts +26 -2
  67. package/dist/client.js +412 -68
  68. package/dist/client.js.map +1 -1
  69. package/dist/config.d.ts +4 -0
  70. package/dist/config.js +1 -0
  71. package/dist/config.js.map +1 -1
  72. package/dist/events.d.ts +18 -1
  73. package/dist/events.js +148 -9
  74. package/dist/events.js.map +1 -1
  75. package/dist/facades.d.ts +43 -1
  76. package/dist/facades.js +363 -7
  77. package/dist/facades.js.map +1 -1
  78. package/dist/group-fs.d.ts +1 -0
  79. package/dist/group-fs.js +37 -19
  80. package/dist/group-fs.js.map +1 -1
  81. package/dist/index.d.ts +5 -3
  82. package/dist/index.js +1 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/keystore/aid-db.d.ts +6 -1
  85. package/dist/keystore/aid-db.js +184 -42
  86. package/dist/keystore/aid-db.js.map +1 -1
  87. package/dist/keystore/index.d.ts +14 -0
  88. package/dist/keystore/local-token-store.d.ts +9 -1
  89. package/dist/keystore/local-token-store.js +79 -14
  90. package/dist/keystore/local-token-store.js.map +1 -1
  91. package/dist/org-protocol.d.ts +46 -0
  92. package/dist/org-protocol.js +802 -12
  93. package/dist/org-protocol.js.map +1 -1
  94. package/dist/ownership-v21.d.ts +36 -5
  95. package/dist/ownership-v21.js +168 -52
  96. package/dist/ownership-v21.js.map +1 -1
  97. package/dist/ownership-workflow.d.ts +24 -2
  98. package/dist/ownership-workflow.js +682 -41
  99. package/dist/ownership-workflow.js.map +1 -1
  100. package/dist/secret-store/file-store.js +84 -0
  101. package/dist/secret-store/file-store.js.map +1 -1
  102. package/dist/seq-tracker.d.ts +1 -0
  103. package/dist/seq-tracker.js +2 -2
  104. package/dist/seq-tracker.js.map +1 -1
  105. package/dist/storage/lowlevel.d.ts +6 -0
  106. package/dist/storage/lowlevel.js +24 -7
  107. package/dist/storage/lowlevel.js.map +1 -1
  108. package/dist/storage/read-cache.d.ts +70 -0
  109. package/dist/storage/read-cache.js +625 -0
  110. package/dist/storage/read-cache.js.map +1 -0
  111. package/dist/storage/vfs.d.ts +2 -0
  112. package/dist/storage/vfs.js +50 -15
  113. package/dist/storage/vfs.js.map +1 -1
  114. package/dist/tools/cross-sdk-agent.d.ts +86 -3
  115. package/dist/tools/cross-sdk-agent.js +758 -46
  116. package/dist/tools/cross-sdk-agent.js.map +1 -1
  117. package/dist/transport.d.ts +3 -0
  118. package/dist/transport.js +104 -46
  119. package/dist/transport.js.map +1 -1
  120. package/dist/types.d.ts +1 -0
  121. package/dist/v2/e2ee/encrypt-group.js +1 -0
  122. package/dist/v2/e2ee/encrypt-group.js.map +1 -1
  123. package/dist/v2/e2ee/encrypt-p2p.js +1 -0
  124. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  125. package/dist/v2/e2ee/types.d.ts +1 -0
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +1 -1
@@ -44,15 +44,26 @@ export interface OwnershipFile {
44
44
  action?: 'declare' | 'release' | 'transfer_owner';
45
45
  transfer?: OwnershipTransferV21;
46
46
  }
47
+ export interface OwnershipOwnerMd {
48
+ owner_aid: string;
49
+ bucket: string;
50
+ path: string;
51
+ exists: boolean;
52
+ content: string;
53
+ sha256: string;
54
+ observed_at: number;
55
+ }
47
56
  export interface OwnershipConsensusResult {
48
57
  status: OwnershipConsensusStatus;
49
58
  sub_aid: string;
50
59
  owner_aid: string | null;
51
60
  relation_version: number;
52
61
  lock: 'locked' | 'unlocked';
62
+ ownership_status?: string;
53
63
  agent_md_etag: string;
54
64
  agent_md_sha256: string;
55
65
  owner_md_sha256: string;
66
+ owner_md?: OwnershipOwnerMd;
56
67
  code?: string;
57
68
  reason?: string;
58
69
  retryable?: boolean;
@@ -74,11 +85,15 @@ export interface OwnershipAgentObservation {
74
85
  protocol: string;
75
86
  relation_version: number;
76
87
  lock: 'locked' | 'unlocked';
88
+ ownership_status?: string;
89
+ pending?: boolean;
90
+ stage_supported?: boolean;
77
91
  owner_aid?: string | null;
78
92
  observed_at: number;
79
93
  recovery_ready?: boolean;
80
94
  }
81
95
  export interface OwnershipOwnerObservation {
96
+ revision?: string;
82
97
  owner_aid: string;
83
98
  sub_aid?: string;
84
99
  bucket: string;
@@ -105,12 +120,14 @@ export interface OwnershipSavedFile {
105
120
  exists: boolean;
106
121
  raw: string;
107
122
  sha256: string;
123
+ revision?: string;
108
124
  };
109
125
  expires_at?: number;
110
126
  deleted?: boolean;
111
127
  }
112
128
  export interface OwnershipFileTask {
113
129
  operation_id?: string;
130
+ stage?: boolean;
114
131
  operation: 'bind' | 'release' | 'transfer';
115
132
  sub_aid: string;
116
133
  owner_aid: string;
@@ -125,6 +142,16 @@ export interface OwnershipFileTask {
125
142
  sha256: string;
126
143
  etag: string;
127
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;
128
155
  };
129
156
  base_owner_md?: {
130
157
  exists: boolean;
@@ -133,6 +160,7 @@ export interface OwnershipFileTask {
133
160
  };
134
161
  authorization_sha256?: string;
135
162
  expires_at?: number;
163
+ assertActive?(): void;
136
164
  persist(): Promise<void>;
137
165
  }
138
166
  export interface OwnershipFileDependencies {
@@ -140,10 +168,10 @@ export interface OwnershipFileDependencies {
140
168
  currentType?(): string;
141
169
  now(): number;
142
170
  readAgentMdFresh(subAid: string): Promise<OwnershipAgentObservation>;
143
- submitAgentMdRaw(subAid: string, raw: string, condition: OwnershipSavedFile['condition']): Promise<OwnershipAgentObservation>;
171
+ submitAgentMdRaw(subAid: string, raw: string, condition: OwnershipSavedFile['condition'], assertActive?: () => void, stage?: boolean): Promise<OwnershipAgentObservation>;
144
172
  readOwnerMd(ownerAid: string, subAid: string): Promise<OwnershipOwnerObservation>;
145
- writeOwnerMd(ownerAid: string, subAid: string, raw: string): Promise<OwnershipOwnerObservation>;
146
- deleteOwnerMd(ownerAid: string, subAid: string): Promise<unknown>;
173
+ writeOwnerMd(ownerAid: string, subAid: string, raw: string, original?: Pick<OwnershipOwnerObservation, 'exists' | 'content' | 'sha256' | 'version'>, assertActive?: () => void): Promise<OwnershipOwnerObservation>;
174
+ deleteOwnerMd(ownerAid: string, subAid: string, expectedVersion: number): Promise<unknown>;
147
175
  resolveCertificate(aid: string, fingerprint: string, purpose?: 'signing' | 'group_identity'): Promise<OwnershipCertificate>;
148
176
  }
149
177
  export declare function ownershipFailure(code: string, reason: string, retryable?: boolean): StateError;
@@ -159,8 +187,10 @@ export declare class OwnershipFiles {
159
187
  declareOwneraid(task: OwnershipFileTask): Promise<OwnershipAgentObservation>;
160
188
  revokeSubaid(task: OwnershipFileTask): Promise<OwnershipOwnerObservation>;
161
189
  revokeOwneraid(task: OwnershipFileTask): Promise<OwnershipAgentObservation>;
190
+ cancelPendingBind(task: OwnershipFileTask, candidate: OwnershipSavedFile): Promise<OwnershipAgentObservation>;
162
191
  transferOwneraid(task: OwnershipFileTask): Promise<OwnershipOwnerObservation>;
163
192
  cleanupOwnerFile(task: OwnershipFileTask): Promise<void>;
193
+ cancelOwnerProposal(task: OwnershipFileTask): Promise<void>;
164
194
  readAgent(subAid: string): Promise<OwnershipAgentObservation>;
165
195
  readOwner(ownerAid: string, subAid: string): Promise<OwnershipOwnerObservation>;
166
196
  verifyFile(raw: string, role: OwnershipFileRole, subAid: string, ownerAid?: string): Promise<OwnershipFile>;
@@ -170,6 +200,7 @@ export declare class OwnershipFiles {
170
200
  private statement;
171
201
  private assertBase;
172
202
  private requireOriginal;
203
+ private currentConsensus;
173
204
  private savedFile;
174
205
  private validSavedCondition;
175
206
  private publishOwner;
@@ -189,8 +220,8 @@ export declare class OwnershipStorageAdapter {
189
220
  private metadata;
190
221
  readObject(ownerAid: string, path: string): Promise<OwnershipOwnerObservation>;
191
222
  readOwnerMd(ownerAid: string, subAid: string): Promise<OwnershipOwnerObservation>;
192
- writeOwnerMd(ownerAid: string, subAid: string, raw: string): Promise<OwnershipOwnerObservation>;
193
- deleteOwnerMd(ownerAid: string, subAid: string): Promise<void>;
223
+ writeOwnerMd(ownerAid: string, subAid: string, raw: string, original?: Pick<OwnershipOwnerObservation, 'exists' | 'content' | 'sha256' | 'version'>, assertActive?: () => void): Promise<OwnershipOwnerObservation>;
224
+ deleteOwnerMd(ownerAid: string, subAid: string, expectedVersion: number): Promise<void>;
194
225
  private requireLocal;
195
226
  }
196
227
  export {};
@@ -1,5 +1,5 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
- import { updateOwnershipAgentMd } from './agent-md.js';
2
+ import { buildDefaultAgentMd, updateOwnershipAgentMd } from './agent-md.js';
3
3
  import { parseFileFrontmatter, validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
4
4
  import { certFingerprint, isStrictBase64, parseAgentMdTailSignature, publicKeyMatchesFingerprint, signDocument, verifyDocument } from './cert-utils.js';
5
5
  import { StateError, ValidationError } from './errors.js';
@@ -124,7 +124,7 @@ export function parseOwnershipFile(content, role, context = {}) {
124
124
  let action;
125
125
  let transfer;
126
126
  if (role === 'agent') {
127
- frontmatter = validateAgentMdDocument(parsed.payload, { expectedAid: context.expectedSubAid, validateOwnership: false, now: context.now }).frontmatter;
127
+ frontmatter = validateAgentMdDocument(parsed.payload, { expectedAid: context.expectedSubAid, validateOwnership: true, now: context.now }).frontmatter;
128
128
  requireOwnershipApplicable(frontmatter.type);
129
129
  statement = validateOwnershipAgentFrontmatter(frontmatter, context.expectedSubAid);
130
130
  if (context.expectedOwnerAid && statement && statement.owner_aid !== context.expectedOwnerAid)
@@ -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;
@@ -258,9 +276,13 @@ export class OwnershipFiles {
258
276
  const owner = await this.readOwner(ownerAid, subAid);
259
277
  ownerBefore = owner;
260
278
  result.owner_md_sha256 = owner.exists ? owner.sha256 ?? '' : '';
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 };
261
280
  if (owner.exists) {
262
281
  const declaration = await this.verifyFile(owner.content, 'owner', subAid, ownerAid);
263
- if (statement) {
282
+ if (statement && (before.pending || before.ownership_status === 'pending')) {
283
+ result.status = 'single_sided';
284
+ }
285
+ else if (statement) {
264
286
  if (declaration.statement.relation_version !== before.relation_version)
265
287
  throw new ValidationError('ownership relation_version mismatch');
266
288
  result.status = declaration.action === 'release' ? 'released' : 'consensus';
@@ -270,10 +292,14 @@ export class OwnershipFiles {
270
292
  }
271
293
  }
272
294
  else if (statement)
273
- result.status = 'released';
295
+ result.status = before.pending || before.ownership_status === 'pending' ? 'single_sided' : 'released';
274
296
  }
275
- if (statement && before.lock !== (result.status === 'released' ? 'unlocked' : 'locked'))
276
- 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;
277
303
  result.owner_aid = result.status === 'consensus' ? statement.owner_aid : null;
278
304
  const after = await this.readAgent(subAid);
279
305
  if (ownerAid && ownerBefore) {
@@ -312,13 +338,22 @@ export class OwnershipFiles {
312
338
  await this.assertBase(task, agent);
313
339
  if (task.operation === 'transfer')
314
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) } };
315
343
  const owner = await this.readOwner(ownerAid, task.sub_aid);
316
344
  if (!owner.exists)
317
345
  throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
318
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);
319
354
  if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
320
355
  throw ownershipFailure('conflict', 'target owner declaration does not match this task');
321
- return { owner: ownerAid, ownership: { statement: this.statement(task, ownerAid, task.target_relation_version) } };
356
+ return { owner: ownerAid, ownership: { statement: task.owner_proposal ? declaration.statement : this.statement(task, ownerAid, task.target_relation_version) } };
322
357
  });
323
358
  }
324
359
  async revokeSubaid(task) {
@@ -332,12 +367,13 @@ export class OwnershipFiles {
332
367
  async revokeOwneraid(task) {
333
368
  this.requireLocal(task.sub_aid);
334
369
  return this.publishAgent(task, async (agent) => {
335
- if (agent.lock === 'locked')
336
- 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');
337
373
  const parsed = parseOwnershipFile(agent.content, 'agent', { expectedSubAid: task.sub_aid });
338
374
  if (!parsed.statement && agent.relation_version === 0)
339
375
  return {};
340
- if (agent.relation_version !== task.target_relation_version)
376
+ if (agent.relation_version !== task.base_relation_version)
341
377
  throw ownershipFailure('conflict', 'release relation_version changed');
342
378
  if (parsed.statement && (parsed.statement.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
343
379
  throw ownershipFailure('conflict', 'agent.md no longer contains the released relation');
@@ -346,6 +382,19 @@ export class OwnershipFiles {
346
382
  return {};
347
383
  }, true);
348
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
+ }
349
398
  async transferOwneraid(task) {
350
399
  this.requireLocal(task.owner_aid);
351
400
  ownershipAid(task.new_owner_aid, 'new_owner_aid');
@@ -385,11 +434,31 @@ export class OwnershipFiles {
385
434
  const beforeDelete = await this.readOwner(task.owner_aid, task.sub_aid);
386
435
  if (beforeDelete.exists && (beforeDelete.content !== source.raw || beforeDelete.sha256 !== source.hash))
387
436
  throw ownershipFailure('conflict', 'owner file changed before cleanup');
388
- if (beforeDelete.exists)
389
- await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid);
437
+ if (beforeDelete.exists) {
438
+ if (!Number.isSafeInteger(beforeDelete.version) || beforeDelete.version <= 0)
439
+ throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
440
+ task.assertActive?.();
441
+ await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid, beforeDelete.version);
442
+ }
390
443
  task.files.cleanup.deleted = true;
391
444
  await task.persist();
392
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
+ }
393
462
  async readAgent(subAid) {
394
463
  if (this.deps.currentType && subAid === this.deps.currentAid().aid)
395
464
  requireOwnershipApplicable(this.deps.currentType());
@@ -462,23 +531,38 @@ export class OwnershipFiles {
462
531
  }
463
532
  async assertBase(task, agent) {
464
533
  ownershipInteger(task.base_relation_version, 'base_relation_version', 0);
465
- if (ownershipInteger(task.target_relation_version, 'target_relation_version') !== task.base_relation_version + (task.operation === 'release' ? 0 : 1) || agent.relation_version !== task.base_relation_version)
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
+ }
543
+ const target = ownershipInteger(task.target_relation_version, 'target_relation_version');
544
+ if (target !== task.base_relation_version + 1 && !(task.operation === 'release' && target === task.base_relation_version) || agent.relation_version !== task.base_relation_version)
466
545
  throw ownershipFailure('conflict', 'NS relation_version changed');
467
- if (!agent.exists)
468
- throw ownershipFailure('unavailable', 'initial agent.md is not published', true);
546
+ if (!agent.exists) {
547
+ if (task.operation !== 'bind' || task.base_relation_version !== 0 || agent.lock !== 'unlocked' || task.base_agent_md?.exists)
548
+ throw ownershipFailure('conflict', 'original agent.md is missing');
549
+ return;
550
+ }
469
551
  const parsed = await this.verifyFile(agent.content, 'agent', task.sub_aid);
470
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');
471
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)))
472
556
  throw ownershipFailure('conflict', 'the original ownership statement was replaced');
473
557
  if (agent.relation_version !== (parsed.statement?.relation_version ?? 0))
474
558
  throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
475
559
  if (task.operation === 'bind' && parsed.frontmatter.type === 'human')
476
560
  throw ownershipFailure('invalid', 'human cannot declare an owner');
477
- 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')
478
562
  throw ownershipFailure('owner_locked', 'an existing owner relation is locked');
479
- 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'))
480
564
  throw ownershipFailure('conflict', 'clear the released agent.md statement before binding the same owner');
481
- if (task.operation !== 'bind' && (agent.lock !== 'locked' || parsed.statement?.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
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'))
482
566
  throw ownershipFailure('conflict', 'the original relation is no longer current');
483
567
  }
484
568
  async requireOriginal(task) {
@@ -487,6 +571,14 @@ export class OwnershipFiles {
487
571
  if (consensus.status !== 'consensus' || consensus.owner_aid !== task.owner_aid || consensus.relation_version !== task.base_relation_version)
488
572
  throw ownershipFailure(consensus.status === 'invalid' ? 'invalid' : 'unavailable', 'the original ownership consensus cannot be confirmed', consensus.status !== 'invalid');
489
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
+ }
490
582
  savedFile(task, key) {
491
583
  const saved = task.files[key];
492
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)))
@@ -524,6 +616,8 @@ export class OwnershipFiles {
524
616
  await task.persist();
525
617
  }
526
618
  await this.verifyFile(saved.raw, 'owner', task.sub_aid, ownerAid);
619
+ if (saved.original_owner?.revision && current.revision !== saved.original_owner.revision)
620
+ throw ownershipFailure('conflict', 'legacy owner original revision changed');
527
621
  if (current.exists && current.sha256 === saved.hash && current.content === saved.raw) {
528
622
  await task.persist();
529
623
  return current;
@@ -539,9 +633,12 @@ export class OwnershipFiles {
539
633
  if (key === 'transfer' && saved.expires_at <= this.deps.now())
540
634
  throw ownershipFailure('conflict', 'transfer authorization expired');
541
635
  const beforeWrite = await this.readOwner(ownerAid, task.sub_aid);
636
+ if (original.revision && beforeWrite.revision !== original.revision)
637
+ throw ownershipFailure('conflict', 'legacy owner original revision changed before publication');
542
638
  if (beforeWrite.exists !== original.exists || beforeWrite.content !== original.raw || (beforeWrite.exists ? beforeWrite.sha256 : '') !== original.sha256)
543
639
  throw ownershipFailure('conflict', 'owner file original material changed before publication');
544
- const published = await this.deps.writeOwnerMd(ownerAid, task.sub_aid, saved.raw);
640
+ task.assertActive?.();
641
+ const published = await this.deps.writeOwnerMd(ownerAid, task.sub_aid, saved.raw, beforeWrite, task.assertActive);
545
642
  if (!published.exists || published.sha256 !== saved.hash || published.content !== saved.raw)
546
643
  throw ownershipFailure('unavailable', 'owner publication could not be verified', true);
547
644
  await task.persist();
@@ -550,46 +647,58 @@ export class OwnershipFiles {
550
647
  async publishAgent(task, prepare, cleanup = false) {
551
648
  let saved = this.savedFile(task, 'agent');
552
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);
553
652
  if (!saved) {
554
653
  const fields = await prepare(current);
555
654
  if (cleanup && !parseOwnershipFile(current.content, 'agent', { now: this.deps.now() }).statement)
556
655
  return current;
557
- const payload = updateOwnershipAgentMd(current.content, fields);
656
+ const payload = updateOwnershipAgentMd(current.exists ? current.content : buildDefaultAgentMd(task.sub_aid, { type: this.deps.currentType?.() ?? 'agent' }), fields);
558
657
  const raw = signOwnershipFile(this.deps.currentAid(), payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent', { now: this.deps.now(), expectedSubAid: task.sub_aid });
559
658
  saved = { raw, hash: ownershipSha256(raw), condition: current.exists ? { if_match: current.etag } : { if_none_match: '*' } };
560
659
  task.files.agent = saved;
561
660
  await task.persist();
562
661
  }
563
662
  await this.verifyFile(saved.raw, 'agent', task.sub_aid);
564
- if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === (cleanup ? 0 : task.target_relation_version) && current.lock === (cleanup ? 'unlocked' : 'locked')) {
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)) {
565
665
  if (saved.published_etag !== undefined && current.etag !== saved.published_etag)
566
666
  throw ownershipFailure('conflict', 'agent.md published ETag changed');
567
667
  saved.published_etag = current.etag;
568
668
  await task.persist();
569
669
  return current;
570
670
  }
571
- if (current.etag !== saved.condition.if_match)
671
+ if (saved.condition.if_none_match === '*' ? current.exists : !current.exists || current.etag !== saved.condition.if_match)
572
672
  throw ownershipFailure('conflict', 'agent.md original ETag condition changed');
573
673
  if (cleanup) {
574
- if (current.lock !== 'unlocked' || current.relation_version !== task.target_relation_version)
674
+ if ((await this.currentConsensus(current)).lock !== 'unlocked' || current.relation_version !== task.base_relation_version)
575
675
  throw ownershipFailure('conflict', 'release target is no longer current');
576
676
  }
577
677
  else {
578
678
  await this.assertBase(task, current);
579
679
  if (task.operation === 'transfer')
580
680
  await this.requireTransfer(task);
581
- const target = await this.readOwner(this.targetOwner(task), task.sub_aid);
582
- if (!target.exists)
583
- throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
584
- const declaration = await this.verifyFile(target.content, 'owner', task.sub_aid, this.targetOwner(task));
585
- const savedOwner = task.files.owner;
586
- if (savedOwner && (savedOwner.raw !== target.content || savedOwner.hash !== target.sha256))
587
- throw ownershipFailure('conflict', 'fixed target owner declaration changed');
588
- if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
589
- throw ownershipFailure('conflict', 'target owner declaration changed');
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
+ }
590
696
  }
591
- const published = await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition);
592
- if (published.content !== saved.raw || published.relation_version !== (cleanup ? 0 : task.target_relation_version) || published.lock !== (cleanup ? 'unlocked' : 'locked'))
697
+ task.assertActive?.();
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')))
593
702
  throw ownershipFailure('unavailable', 'agent.md publication could not be verified', true);
594
703
  saved.published_etag = published.etag;
595
704
  await task.persist();
@@ -640,7 +749,7 @@ export class OwnershipStorageAdapter {
640
749
  if (before.exists) {
641
750
  const object = await this.call('storage.get_object', params);
642
751
  this.metadata(object, ownerAid, path);
643
- if (object.exists !== true || object.sha256 !== before.sha256 || typeof object.content !== 'string')
752
+ if (object.exists !== true || object.sha256 !== before.sha256 || before.revision !== undefined && object.revision !== before.revision || typeof object.content !== 'string')
644
753
  throw ownershipFailure('unavailable', 'Storage object changed or full Base64 content is unavailable', true);
645
754
  if (object.content !== '' && !isStrictBase64(object.content))
646
755
  throw ownershipFailure('unavailable', 'Storage Base64 content is unavailable', true);
@@ -655,26 +764,29 @@ export class OwnershipStorageAdapter {
655
764
  }
656
765
  final = await this.call('storage.head_object', params);
657
766
  this.metadata(final, ownerAid, path);
658
- if (final.exists !== true || final.sha256 !== before.sha256 || this.now() - began > 30)
767
+ if (final.exists !== true || final.sha256 !== before.sha256 || before.revision !== undefined && final.revision !== before.revision || this.now() - began > 30)
659
768
  throw ownershipFailure('unavailable', 'Storage material changed during authoritative read', true);
660
769
  }
661
770
  const service = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
662
771
  return { owner_aid: ownerAid, bucket: 'default', path, exists: Boolean(final.exists), observed_at: Number(final.observed_at),
663
- content, sha256: final.exists ? String(final.sha256) : undefined, authoritative: true, recovery_ready: final.recovery_ready, service_aid: service, authenticated_service_aid: service, version: final.exists && Number.isSafeInteger(final.version) && Number(final.version) > 0 ? final.version : undefined };
772
+ content, sha256: final.exists ? String(final.sha256) : undefined, authoritative: true, recovery_ready: final.recovery_ready, service_aid: service, authenticated_service_aid: service, version: final.exists && Number.isSafeInteger(final.version) && Number(final.version) > 0 ? final.version : undefined, ...(typeof final.revision === 'string' && final.revision ? { revision: final.revision } : {}) };
664
773
  }
665
774
  async readOwnerMd(ownerAid, subAid) {
666
775
  ownershipAid(subAid, 'sub_aid');
667
776
  return this.readObject(ownerAid, `sub_aids/${subAid}/owner.md`);
668
777
  }
669
- async writeOwnerMd(ownerAid, subAid, raw) {
778
+ async writeOwnerMd(ownerAid, subAid, raw, original, assertActive) {
670
779
  this.requireLocal(ownerAid);
671
780
  ownershipAid(subAid, 'sub_aid');
672
781
  const path = `sub_aids/${subAid}/owner.md`;
673
782
  const before = await this.readOwnerMd(ownerAid, subAid);
783
+ if (original && (before.exists !== original.exists || before.content !== original.content || (before.exists ? before.sha256 : '') !== (original.exists ? original.sha256 : '') || original.revision !== undefined && before.revision !== original.revision))
784
+ throw ownershipFailure('conflict', 'owner file original material changed');
674
785
  const bytes = new TextEncoder().encode(raw);
675
- const expectedVersion = before.exists ? before.version : 0;
786
+ const expectedVersion = (original ?? before).exists ? (original ?? before).version : 0;
676
787
  if (typeof expectedVersion !== 'number' || !Number.isSafeInteger(expectedVersion) || expectedVersion < (before.exists ? 1 : 0))
677
788
  throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
789
+ assertActive?.();
678
790
  const session = await this.call('storage.create_upload_session', { owner_aid: ownerAid, bucket: 'default', object_key: path, size_bytes: bytes.length, content_type: 'text/markdown; charset=utf-8', expected_version: expectedVersion, overwrite: true });
679
791
  const uploadURL = String(session.upload_url ?? ''), sessionID = String(session.session_id ?? '');
680
792
  if (!uploadURL || !sessionID)
@@ -685,6 +797,7 @@ export class OwnershipStorageAdapter {
685
797
  if (value !== undefined && value !== null && key.toLowerCase() !== 'content-type')
686
798
  headers[key] = String(value);
687
799
  headers['Content-Type'] = 'text/markdown; charset=utf-8';
800
+ assertActive?.();
688
801
  if (this.deps.httpPut)
689
802
  await this.deps.httpPut(uploadURL, bytes, headers);
690
803
  else {
@@ -694,13 +807,16 @@ export class OwnershipStorageAdapter {
694
807
  }
695
808
  const complete = { owner_aid: ownerAid, bucket: 'default', object_key: path, session_id: sessionID, size_bytes: bytes.length, sha256: ownershipSha256(bytes), content_type: 'text/markdown; charset=utf-8', is_private: false, overwrite: true };
696
809
  complete.expected_version = expectedVersion;
810
+ assertActive?.();
697
811
  await this.call('storage.complete_upload', complete);
698
812
  return this.readOwnerMd(ownerAid, subAid);
699
813
  }
700
- async deleteOwnerMd(ownerAid, subAid) {
814
+ async deleteOwnerMd(ownerAid, subAid, expectedVersion) {
701
815
  this.requireLocal(ownerAid);
702
816
  ownershipAid(subAid, 'sub_aid');
703
- 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 });
704
820
  if ((await this.readOwnerMd(ownerAid, subAid)).exists)
705
821
  throw ownershipFailure('unavailable', 'Storage deletion could not be confirmed', true);
706
822
  }