@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.
- package/CHANGELOG.md +104 -0
- package/_packed_docs/CHANGELOG.md +104 -0
- package/_packed_docs/INDEX.md +59 -34
- package/_packed_docs/KITE_DOCS_GUIDE.md +59 -52
- 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
- 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
- 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
- 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
- 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
- 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
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +33 -22
- 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
- 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
- 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
- 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
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +163 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
- package/_packed_docs/sdk/09-group-rpc-manual.md +15 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
- 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
- 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
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +33 -25
- package/_packed_docs/sdk/INDEX.md +67 -33
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +74 -57
- 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
- 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
- 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
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +28 -20
- 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
- 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
- package/dist/agent-md-schema.js +6 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +22 -2
- package/dist/agent-md.js +92 -68
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.js +50 -15
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +22 -2
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.js +13 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +87 -1
- package/dist/client/delivery.js +1387 -139
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +3 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +149 -37
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +831 -304
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +412 -68
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +18 -1
- package/dist/events.js +148 -9
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +1 -0
- package/dist/group-fs.js +37 -19
- package/dist/group-fs.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +6 -1
- package/dist/keystore/aid-db.js +184 -42
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -0
- package/dist/keystore/local-token-store.d.ts +9 -1
- package/dist/keystore/local-token-store.js +79 -14
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +46 -0
- package/dist/org-protocol.js +802 -12
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +36 -5
- package/dist/ownership-v21.js +168 -52
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +24 -2
- package/dist/ownership-workflow.js +682 -41
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +6 -0
- package/dist/storage/lowlevel.js +24 -7
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/read-cache.d.ts +70 -0
- package/dist/storage/read-cache.js +625 -0
- package/dist/storage/read-cache.js.map +1 -0
- package/dist/storage/vfs.d.ts +2 -0
- package/dist/storage/vfs.js +50 -15
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +86 -3
- package/dist/tools/cross-sdk-agent.js +758 -46
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +104 -46
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/ownership-v21.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/ownership-v21.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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 (
|
|
276
|
-
|
|
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
|
-
|
|
336
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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' && (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
-
|
|
592
|
-
|
|
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
|
-
|
|
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
|
}
|