@agentunion/fastaun 0.5.19 → 0.5.21
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 +176 -78
- package/_packed_docs/CHANGELOG.md +176 -78
- package/_packed_docs/INDEX.md +288 -269
- package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
- package/_packed_docs/agent.md/SCHEMA.md +7 -7
- 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/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
- 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/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 +70 -63
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
- package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
- package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -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 +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
- package/_packed_docs/sdk/INDEX.md +197 -167
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- 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 +363 -0
- 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 +553 -0
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
- package/dist/agent-md-schema.d.ts +13 -1
- package/dist/agent-md-schema.js +63 -33
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +30 -3
- package/dist/agent-md.js +293 -152
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +42 -0
- package/dist/aid-store.js +224 -44
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.js +18 -13
- package/dist/aid.js.map +1 -1
- package/dist/auth.js +78 -2
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +44 -1
- package/dist/cert-utils.js +157 -56
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +68 -1
- package/dist/client/delivery.js +1196 -135
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +5 -1
- package/dist/client/rpc-pipeline.js +240 -111
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +842 -379
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +420 -75
- 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 +23 -4
- package/dist/events.js +171 -15
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +368 -12
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +7 -2
- package/dist/keystore/aid-db.js +194 -43
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +15 -1
- package/dist/keystore/local-token-store.d.ts +10 -2
- package/dist/keystore/local-token-store.js +81 -16
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.js +829 -22
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +40 -9
- package/dist/ownership-v21.js +174 -79
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +22 -0
- package/dist/ownership-workflow.js +861 -66
- 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/tools/cross-sdk-agent.d.ts +67 -3
- package/dist/tools/cross-sdk-agent.js +575 -42
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +108 -44
- 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.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { certFingerprint, isStrictBase64, parseAgentMdTailSignature, publicKeyMatchesFingerprint, signDocument, verifyDocument } from './cert-utils.js';
|
|
5
5
|
import { StateError, ValidationError } from './errors.js';
|
|
6
6
|
const AID_RE = /^[a-z0-9_][a-z0-9_-]*(?:\.[a-z0-9_][a-z0-9_-]*)+$/;
|
|
7
7
|
export const OWNERSHIP_UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
@@ -108,7 +108,10 @@ export function parseOwnershipFile(content, role, context = {}) {
|
|
|
108
108
|
if (raw.length - new TextEncoder().encode(parsed.payload).length > 2048)
|
|
109
109
|
throw new ValidationError('ownership file signature exceeds 2048 bytes');
|
|
110
110
|
const fields = parsed.fields;
|
|
111
|
-
if (fields)
|
|
111
|
+
if (fields?.signer_aid !== undefined)
|
|
112
|
+
throw new ValidationError('ownership main signature contains unknown signer_aid');
|
|
113
|
+
for (const item of parsed.signatures) {
|
|
114
|
+
const fields = item.fields;
|
|
112
115
|
if (!/^sha256:[0-9a-f]{64}$/.test(fields.cert_fingerprint) || 'public_key_fingerprint' in fields && !/^sha256:[0-9a-f]{64}$/.test(fields.public_key_fingerprint))
|
|
113
116
|
throw new ValidationError('ownership file fingerprint is invalid');
|
|
114
117
|
if (!/^[1-9][0-9]*$/.test(fields.timestamp) || !Number.isSafeInteger(Number(fields.timestamp)))
|
|
@@ -121,7 +124,7 @@ export function parseOwnershipFile(content, role, context = {}) {
|
|
|
121
124
|
let action;
|
|
122
125
|
let transfer;
|
|
123
126
|
if (role === 'agent') {
|
|
124
|
-
frontmatter = validateAgentMdDocument(parsed.payload, { expectedAid: context.expectedSubAid, validateOwnership:
|
|
127
|
+
frontmatter = validateAgentMdDocument(parsed.payload, { expectedAid: context.expectedSubAid, validateOwnership: true, now: context.now }).frontmatter;
|
|
125
128
|
requireOwnershipApplicable(frontmatter.type);
|
|
126
129
|
statement = validateOwnershipAgentFrontmatter(frontmatter, context.expectedSubAid);
|
|
127
130
|
if (context.expectedOwnerAid && statement && statement.owner_aid !== context.expectedOwnerAid)
|
|
@@ -187,8 +190,18 @@ export function verifyOwnershipFile(content, role, certificate, context = {}) {
|
|
|
187
190
|
throw new ValidationError('ownership certificate fingerprint mismatch');
|
|
188
191
|
if ('public_key_fingerprint' in fields && !publicKeyMatchesFingerprint(certificate.cert_pem, fields.public_key_fingerprint))
|
|
189
192
|
throw new ValidationError('ownership public key fingerprint mismatch');
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
const additional = new Map();
|
|
194
|
+
for (const item of parseAgentMdTailSignature(new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(parsed.raw)).signatures.slice(1)) {
|
|
195
|
+
const fields = item.fields, extra = context.certificateResolver?.(fields.signer_aid, fields.cert_fingerprint, Number(fields.timestamp));
|
|
196
|
+
if (extra && (extra.chain_valid === false || extra.revoked === true || typeof extra.state === 'string' && extra.state !== 'active_signing'))
|
|
197
|
+
throw new ValidationError('ownership additional signing certificate is not currently verified and active_signing', { stringCode: 'invalid' });
|
|
198
|
+
if (!extra || extra.chain_valid !== true || extra.revoked !== false || extra.revocation_checked !== true || extra.state !== 'active_signing' || !Number.isSafeInteger(extra.observed_at) || extra.observed_at <= 0 || extra.observed_at > now + 5 || extra.observed_at < now - 30 || !extra.cert_pem?.trim())
|
|
199
|
+
throw new StateError('ownership additional certificate current status is unavailable', { stringCode: 'unavailable', retryable: true });
|
|
200
|
+
additional.set(`${fields.signer_aid};${fields.cert_fingerprint}`, extra.cert_pem);
|
|
201
|
+
}
|
|
202
|
+
const verified = verifyDocument(new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(parsed.raw), { expectedAid: signer, primaryCertPem: certificate.cert_pem, requireCurrentValidity: true, now, certificateResolver: (aid, fingerprint) => additional.get(`${aid};${fingerprint}`) });
|
|
203
|
+
if (!verified.valid)
|
|
204
|
+
throw new ValidationError(verified.reason ?? 'ownership file signature verification failed');
|
|
192
205
|
return parsed;
|
|
193
206
|
}
|
|
194
207
|
export function signOwnershipFile(identity, content, role, context = {}) {
|
|
@@ -204,8 +217,7 @@ export function signOwnershipFile(identity, content, role, context = {}) {
|
|
|
204
217
|
throw new ValidationError('ownership payload must end with a newline before signature');
|
|
205
218
|
const now = Math.trunc(context.now ?? Date.now() / 1000);
|
|
206
219
|
validateAgentMdCertificate(identity.certPem, identity.aid, now, { now });
|
|
207
|
-
|
|
208
|
-
return parsed.payload + buildAgentMdSignatureBlock(identity.certFingerprint, now, signature, publicKeyFingerprint(identity.certPem), role === 'agent' ? 'AUN-SIGNATURE-AGENTMD-V2' : 'AUN-SIGNATURE-OWNERSHIP-V2');
|
|
220
|
+
return signDocument(identity, parsed.payload, { timestamp: now, tag: role === 'agent' ? 'AGENTMD-V2' : 'OWNERSHIP-V2' }).signed;
|
|
209
221
|
}
|
|
210
222
|
export function ownershipSha256(content) {
|
|
211
223
|
return createHash('sha256').update(content).digest('hex');
|
|
@@ -225,14 +237,13 @@ export class OwnershipFiles {
|
|
|
225
237
|
async checkConsensus(subAid, target) {
|
|
226
238
|
ownershipAid(subAid, 'sub_aid');
|
|
227
239
|
const began = this.deps.now();
|
|
228
|
-
const result = { status: 'unavailable', sub_aid: subAid, owner_aid: null, relation_version: 0, lock: 'unlocked', agent_md_etag: '', agent_md_sha256: '',
|
|
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: '' };
|
|
229
241
|
try {
|
|
230
242
|
const before = await this.readAgent(subAid);
|
|
231
243
|
Object.assign(result, { relation_version: before.relation_version, lock: before.lock, agent_md_etag: before.etag, agent_md_sha256: before.exists ? ownershipSha256(before.content) : '' });
|
|
232
|
-
const
|
|
233
|
-
const agent = before.exists && !released ? await this.verifyFile(before.content, 'agent', subAid) : undefined;
|
|
244
|
+
const agent = before.exists ? await this.verifyFile(before.content, 'agent', subAid) : undefined;
|
|
234
245
|
const statement = agent?.statement;
|
|
235
|
-
if (before.
|
|
246
|
+
if (before.relation_version !== (statement?.relation_version ?? 0))
|
|
236
247
|
throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
|
|
237
248
|
if (before.lock === 'locked' && (!statement || statement.relation_version !== before.relation_version))
|
|
238
249
|
throw ownershipFailure('unavailable', 'NS locked relation does not match agent.md', true);
|
|
@@ -240,29 +251,37 @@ export class OwnershipFiles {
|
|
|
240
251
|
if (before.owner_aid !== undefined && before.owner_aid !== currentOwner)
|
|
241
252
|
throw ownershipFailure('unavailable', 'NS owner metadata does not match agent.md', true);
|
|
242
253
|
result.owner_aid = currentOwner;
|
|
243
|
-
result.status =
|
|
244
|
-
const ownerAid =
|
|
254
|
+
result.status = statement ? 'single_sided' : 'unbound';
|
|
255
|
+
const ownerAid = statement?.owner_aid ?? target?.owner_aid;
|
|
256
|
+
let ownerBefore;
|
|
245
257
|
if (ownerAid) {
|
|
246
258
|
const owner = await this.readOwner(ownerAid, subAid);
|
|
247
|
-
|
|
248
|
-
result.owner_md_sha256 = owner.sha256 ?? '';
|
|
259
|
+
ownerBefore = owner;
|
|
260
|
+
result.owner_md_sha256 = owner.exists ? owner.sha256 ?? '' : '';
|
|
261
|
+
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 };
|
|
249
262
|
if (owner.exists) {
|
|
250
263
|
const declaration = await this.verifyFile(owner.content, 'owner', subAid, ownerAid);
|
|
251
|
-
if (
|
|
264
|
+
if (statement) {
|
|
252
265
|
if (declaration.statement.relation_version !== before.relation_version)
|
|
253
266
|
throw new ValidationError('ownership relation_version mismatch');
|
|
254
|
-
|
|
255
|
-
throw ownershipFailure('unavailable', 'NS has not confirmed the current release', true);
|
|
256
|
-
result.status = 'consensus';
|
|
267
|
+
result.status = declaration.action === 'release' ? 'released' : 'consensus';
|
|
257
268
|
}
|
|
258
269
|
else if (target && declaration.action === 'declare' && declaration.statement.relation_version === target.relation_version && target.relation_version === before.relation_version + 1) {
|
|
259
270
|
result.status = 'single_sided';
|
|
260
271
|
}
|
|
261
272
|
}
|
|
262
|
-
else if (
|
|
263
|
-
|
|
273
|
+
else if (statement)
|
|
274
|
+
result.status = 'released';
|
|
264
275
|
}
|
|
276
|
+
if (statement && before.lock !== (result.status === 'released' ? 'unlocked' : 'locked'))
|
|
277
|
+
throw ownershipFailure('unavailable', 'NS lock does not match current ownership materials', true);
|
|
278
|
+
result.owner_aid = result.status === 'consensus' ? statement.owner_aid : null;
|
|
265
279
|
const after = await this.readAgent(subAid);
|
|
280
|
+
if (ownerAid && ownerBefore) {
|
|
281
|
+
const ownerAfter = await this.readOwner(ownerAid, subAid);
|
|
282
|
+
if (ownerBefore.exists !== ownerAfter.exists || ownerBefore.content !== ownerAfter.content || ownerBefore.sha256 !== ownerAfter.sha256)
|
|
283
|
+
throw ownershipFailure('unavailable', 'owner.md changed during consensus check', true);
|
|
284
|
+
}
|
|
266
285
|
if (before.exists !== after.exists || before.etag !== after.etag || before.relation_version !== after.relation_version || before.lock !== after.lock || before.content !== after.content || before.owner_aid !== after.owner_aid || this.deps.now() - began > 30)
|
|
267
286
|
throw ownershipFailure('unavailable', 'NS relation changed or verification window elapsed during consensus check', true);
|
|
268
287
|
return result;
|
|
@@ -316,11 +335,15 @@ export class OwnershipFiles {
|
|
|
316
335
|
return this.publishAgent(task, async (agent) => {
|
|
317
336
|
if (agent.lock === 'locked')
|
|
318
337
|
throw ownershipFailure('owner_locked', 'NS has not confirmed release');
|
|
319
|
-
if (agent.relation_version !== task.target_relation_version)
|
|
320
|
-
throw ownershipFailure('conflict', 'release relation_version changed');
|
|
321
338
|
const parsed = parseOwnershipFile(agent.content, 'agent', { expectedSubAid: task.sub_aid });
|
|
339
|
+
if (!parsed.statement && agent.relation_version === 0)
|
|
340
|
+
return {};
|
|
341
|
+
if (agent.relation_version !== task.base_relation_version)
|
|
342
|
+
throw ownershipFailure('conflict', 'release relation_version changed');
|
|
322
343
|
if (parsed.statement && (parsed.statement.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
|
|
323
344
|
throw ownershipFailure('conflict', 'agent.md no longer contains the released relation');
|
|
345
|
+
if (task.expected_agent_statement && Object.keys(task.expected_agent_statement).some((field) => task.expected_agent_statement[field] !== parsed.statement?.[field]))
|
|
346
|
+
throw ownershipFailure('conflict', 'agent.md no longer contains the approved statement');
|
|
324
347
|
return {};
|
|
325
348
|
}, true);
|
|
326
349
|
}
|
|
@@ -338,8 +361,8 @@ export class OwnershipFiles {
|
|
|
338
361
|
async cleanupOwnerFile(task) {
|
|
339
362
|
this.requireLocal(task.owner_aid);
|
|
340
363
|
const source = this.savedFile(task, task.operation === 'release' ? 'release' : 'transfer');
|
|
341
|
-
if (!source
|
|
342
|
-
throw ownershipFailure('conflict', 'original
|
|
364
|
+
if (!source)
|
|
365
|
+
throw ownershipFailure('conflict', 'original owner file is unavailable');
|
|
343
366
|
const consensus = await this.checkConsensus(task.sub_aid);
|
|
344
367
|
if (consensus.status === 'not_applicable')
|
|
345
368
|
throw ownershipFailure('not_applicable', consensus.reason ?? 'group/org use their dedicated lifecycle ownership');
|
|
@@ -347,19 +370,26 @@ export class OwnershipFiles {
|
|
|
347
370
|
throw new ValidationError(consensus.reason ?? 'ownership consensus is invalid', { stringCode: 'invalid' });
|
|
348
371
|
if (consensus.status === 'unavailable')
|
|
349
372
|
throw ownershipFailure(consensus.code ?? 'unavailable', consensus.reason ?? 'ownership consensus is unavailable', true);
|
|
350
|
-
if (
|
|
351
|
-
|
|
373
|
+
if (task.operation === 'release' && consensus.status === 'released')
|
|
374
|
+
return;
|
|
375
|
+
if (task.operation === 'release' ? consensus.status !== 'unbound' && consensus.status !== 'consensus' : consensus.relation_version !== task.target_relation_version || consensus.status !== 'consensus' || consensus.owner_aid !== task.new_owner_aid)
|
|
376
|
+
throw ownershipFailure('conflict', 'current materials do not permit this task cleanup');
|
|
352
377
|
const current = await this.readOwner(task.owner_aid, task.sub_aid);
|
|
353
|
-
if (current.exists && (current.
|
|
354
|
-
throw ownershipFailure('conflict', 'owner file
|
|
378
|
+
if (current.exists && (current.content !== source.raw || current.sha256 !== source.hash))
|
|
379
|
+
throw ownershipFailure('conflict', 'owner file changed before cleanup');
|
|
355
380
|
if (!task.files.cleanup) {
|
|
356
|
-
task.files.cleanup = { raw: source.raw, hash: source.hash, condition: {
|
|
381
|
+
task.files.cleanup = { raw: source.raw, hash: source.hash, condition: {} };
|
|
357
382
|
await task.persist();
|
|
358
383
|
}
|
|
359
384
|
else
|
|
360
385
|
this.savedFile(task, 'cleanup');
|
|
361
|
-
|
|
362
|
-
|
|
386
|
+
const beforeDelete = await this.readOwner(task.owner_aid, task.sub_aid);
|
|
387
|
+
if (beforeDelete.exists && (beforeDelete.content !== source.raw || beforeDelete.sha256 !== source.hash))
|
|
388
|
+
throw ownershipFailure('conflict', 'owner file changed before cleanup');
|
|
389
|
+
if (beforeDelete.exists) {
|
|
390
|
+
task.assertActive?.();
|
|
391
|
+
await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid);
|
|
392
|
+
}
|
|
363
393
|
task.files.cleanup.deleted = true;
|
|
364
394
|
await task.persist();
|
|
365
395
|
}
|
|
@@ -379,7 +409,7 @@ export class OwnershipFiles {
|
|
|
379
409
|
async readOwner(ownerAid, subAid) {
|
|
380
410
|
const value = await this.deps.readOwnerMd(ownerAid, subAid);
|
|
381
411
|
const now = this.deps.now();
|
|
382
|
-
if (value.owner_aid !== ownerAid || value.sub_aid !== undefined && value.sub_aid !== subAid || value.bucket !== 'default' || value.path !== `sub_aids/${subAid}/owner.md` || typeof value.exists !== 'boolean' ||
|
|
412
|
+
if (value.owner_aid !== ownerAid || value.sub_aid !== undefined && value.sub_aid !== subAid || value.bucket !== 'default' || value.path !== `sub_aids/${subAid}/owner.md` || typeof value.exists !== 'boolean' || !Number.isSafeInteger(value.observed_at) || value.observed_at > now + 5 || value.observed_at < now - 30 || value.authoritative !== true || value.recovery_ready !== undefined && value.recovery_ready !== true || value.service_aid !== `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}` || value.service_aid !== value.authenticated_service_aid || typeof value.content !== 'string' || value.exists && !/^[0-9a-f]{64}$/.test(value.sha256 ?? ''))
|
|
383
413
|
throw ownershipFailure('unavailable', 'Storage authoritative observation is incomplete or changed', true);
|
|
384
414
|
if (value.exists && value.sha256 !== ownershipSha256(value.content))
|
|
385
415
|
throw ownershipFailure('unavailable', 'Storage complete file digest does not match authoritative observation', true);
|
|
@@ -396,13 +426,19 @@ export class OwnershipFiles {
|
|
|
396
426
|
const parsed = parseOwnershipFile(raw, role, context);
|
|
397
427
|
const signer = role === 'agent' ? subAid : String(parsed.frontmatter.owner_aid);
|
|
398
428
|
const certificate = await this.deps.resolveCertificate(signer, parsed.signature.cert_fingerprint, role === 'agent' && parsed.frontmatter.type === 'group' ? 'group_identity' : undefined);
|
|
399
|
-
|
|
429
|
+
const additional = new Map();
|
|
430
|
+
for (const item of parseAgentMdTailSignature(raw).signatures.slice(1))
|
|
431
|
+
additional.set(`${item.fields.signer_aid};${item.fields.cert_fingerprint}`, await this.deps.resolveCertificate(item.fields.signer_aid, item.fields.cert_fingerprint));
|
|
432
|
+
return verifyOwnershipFile(raw, role, certificate, { ...context, now: this.deps.now(), certificateResolver: (aid, fingerprint) => additional.get(`${aid};${fingerprint}`) });
|
|
400
433
|
}
|
|
401
434
|
async requireTransfer(task) {
|
|
402
435
|
const owner = await this.readOwner(task.owner_aid, task.sub_aid);
|
|
403
436
|
if (!owner.exists)
|
|
404
437
|
throw ownershipFailure('unavailable', 'original owner authorization is missing', true);
|
|
405
438
|
const authorization = await this.verifyFile(owner.content, 'owner', task.sub_aid, task.owner_aid);
|
|
439
|
+
const saved = task.files.transfer;
|
|
440
|
+
if (saved && (saved.raw !== owner.content || saved.hash !== owner.sha256))
|
|
441
|
+
throw ownershipFailure('conflict', 'fixed transfer authorization changed');
|
|
406
442
|
if (authorization.action !== 'transfer_owner' || authorization.statement.relation_version !== task.base_relation_version || authorization.transfer.new_owner_aid !== task.new_owner_aid || authorization.transfer.target_relation_version !== task.target_relation_version)
|
|
407
443
|
throw ownershipFailure('conflict', 'transfer authorization does not match this task');
|
|
408
444
|
const expiresAt = authorization.transfer.expires_at;
|
|
@@ -429,17 +465,26 @@ export class OwnershipFiles {
|
|
|
429
465
|
}
|
|
430
466
|
async assertBase(task, agent) {
|
|
431
467
|
ownershipInteger(task.base_relation_version, 'base_relation_version', 0);
|
|
432
|
-
|
|
468
|
+
const target = ownershipInteger(task.target_relation_version, 'target_relation_version');
|
|
469
|
+
if (target !== task.base_relation_version + 1 && !(task.operation === 'release' && target === task.base_relation_version) || agent.relation_version !== task.base_relation_version)
|
|
433
470
|
throw ownershipFailure('conflict', 'NS relation_version changed');
|
|
434
|
-
if (!agent.exists)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
471
|
+
if (!agent.exists) {
|
|
472
|
+
if (task.operation !== 'bind' || task.base_relation_version !== 0 || agent.lock !== 'unlocked' || task.base_agent_md?.exists)
|
|
473
|
+
throw ownershipFailure('conflict', 'original agent.md is missing');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const parsed = await this.verifyFile(agent.content, 'agent', task.sub_aid);
|
|
477
|
+
const base = task.base_agent_md;
|
|
478
|
+
if (base && (agent.exists !== base.exists || (base.statement ? !parsed.statement || Object.keys(base.statement).some((field) => base.statement[field] !== parsed.statement[field]) : parsed.statement || agent.content !== base.raw || ownershipSha256(agent.content) !== base.sha256)))
|
|
479
|
+
throw ownershipFailure('conflict', 'the original ownership statement was replaced');
|
|
480
|
+
if (agent.relation_version !== (parsed.statement?.relation_version ?? 0))
|
|
438
481
|
throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
|
|
439
482
|
if (task.operation === 'bind' && parsed.frontmatter.type === 'human')
|
|
440
483
|
throw ownershipFailure('invalid', 'human cannot declare an owner');
|
|
441
484
|
if (task.operation === 'bind' && agent.lock !== 'unlocked')
|
|
442
485
|
throw ownershipFailure('owner_locked', 'an existing owner relation is locked');
|
|
486
|
+
if (task.operation === 'bind' && parsed.statement?.owner_aid === this.targetOwner(task))
|
|
487
|
+
throw ownershipFailure('conflict', 'clear the released agent.md statement before binding the same owner');
|
|
443
488
|
if (task.operation !== 'bind' && (agent.lock !== 'locked' || parsed.statement?.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
|
|
444
489
|
throw ownershipFailure('conflict', 'the original relation is no longer current');
|
|
445
490
|
}
|
|
@@ -451,49 +496,66 @@ export class OwnershipFiles {
|
|
|
451
496
|
}
|
|
452
497
|
savedFile(task, key) {
|
|
453
498
|
const saved = task.files[key];
|
|
454
|
-
if (saved && (typeof saved.raw !== 'string' || saved.hash !== ownershipSha256(saved.raw) || !this.validSavedCondition(key, saved.condition) || saved.
|
|
499
|
+
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)))
|
|
455
500
|
throw ownershipFailure('persistence_unavailable', 'saved ownership file is corrupt');
|
|
501
|
+
const original = saved?.original_owner;
|
|
502
|
+
if (saved && key !== 'agent' && key !== 'cleanup' && (!original || typeof original.exists !== 'boolean' || typeof original.raw !== 'string' || original.sha256 !== (original.exists ? ownershipSha256(original.raw) : '') || !original.exists && original.raw !== ''))
|
|
503
|
+
throw ownershipFailure('persistence_unavailable', 'original owner.md evidence is corrupt');
|
|
456
504
|
return saved;
|
|
457
505
|
}
|
|
458
506
|
validSavedCondition(key, condition) {
|
|
459
|
-
if (!condition || typeof condition !== 'object'
|
|
507
|
+
if (!condition || typeof condition !== 'object')
|
|
508
|
+
return false;
|
|
509
|
+
if (key !== 'agent')
|
|
510
|
+
return Object.keys(condition).length === 0;
|
|
511
|
+
if (Object.keys(condition).length !== 1)
|
|
460
512
|
return false;
|
|
461
513
|
if (key === 'agent')
|
|
462
514
|
return typeof condition.if_match === 'string' && condition.if_match.length > 0 || condition.if_none_match === '*';
|
|
463
|
-
return
|
|
515
|
+
return false;
|
|
464
516
|
}
|
|
465
517
|
async publishOwner(task, key, ownerAid, prepare) {
|
|
466
518
|
let saved = this.savedFile(task, key);
|
|
467
519
|
await this.readAgent(task.sub_aid);
|
|
468
520
|
const current = await this.readOwner(ownerAid, task.sub_aid);
|
|
469
521
|
if (!saved) {
|
|
522
|
+
const base = task.base_owner_md;
|
|
523
|
+
if (base && ownerAid === task.base_agent_md?.statement?.owner_aid && (current.exists !== base.exists || current.content !== base.raw || (current.exists ? current.sha256 : '') !== base.sha256))
|
|
524
|
+
throw ownershipFailure('conflict', 'original owner.md material changed before publication');
|
|
470
525
|
const ownership = await prepare(current);
|
|
471
526
|
const raw = signOwnershipFile(this.deps.currentAid(), `---\nowner_aid: ${ownerAid}\nsub_aid: ${task.sub_aid}\nownership: ${JSON.stringify(ownership)}\n---\n`, 'owner', { now: this.deps.now(), expectedOwnerAid: ownerAid, expectedSubAid: task.sub_aid });
|
|
472
|
-
saved = { raw, hash: ownershipSha256(raw), condition: {
|
|
527
|
+
saved = { raw, hash: ownershipSha256(raw), condition: {}, original_owner: { exists: current.exists, raw: current.content, sha256: current.exists ? ownershipSha256(current.content) : '' } };
|
|
473
528
|
if (key === 'transfer')
|
|
474
529
|
saved.expires_at = ownership.transfer.expires_at;
|
|
475
530
|
task.files[key] = saved;
|
|
476
531
|
await task.persist();
|
|
477
532
|
}
|
|
478
533
|
await this.verifyFile(saved.raw, 'owner', task.sub_aid, ownerAid);
|
|
534
|
+
if (saved.original_owner?.revision && current.revision !== saved.original_owner.revision)
|
|
535
|
+
throw ownershipFailure('conflict', 'legacy owner original revision changed');
|
|
479
536
|
if (current.exists && current.sha256 === saved.hash && current.content === saved.raw) {
|
|
480
|
-
if (saved.published_revision !== undefined && current.revision !== saved.published_revision)
|
|
481
|
-
throw ownershipFailure('conflict', 'owner file published revision changed');
|
|
482
|
-
saved.published_revision = current.revision;
|
|
483
537
|
await task.persist();
|
|
484
538
|
return current;
|
|
485
539
|
}
|
|
486
|
-
|
|
487
|
-
|
|
540
|
+
const original = saved.original_owner;
|
|
541
|
+
if (!original || typeof original.exists !== 'boolean' || typeof original.raw !== 'string' || original.sha256 !== (original.exists ? ownershipSha256(original.raw) : ''))
|
|
542
|
+
throw ownershipFailure('persistence_unavailable', 'owner file original material is missing or corrupt');
|
|
543
|
+
if (current.exists !== original.exists || current.content !== original.raw || (current.exists ? current.sha256 : '') !== original.sha256)
|
|
544
|
+
throw ownershipFailure('conflict', 'owner file original material changed');
|
|
488
545
|
await this.assertBase(task, await this.readAgent(task.sub_aid));
|
|
489
546
|
if (key === 'owner' && task.operation === 'transfer')
|
|
490
547
|
await this.requireTransfer(task);
|
|
491
548
|
if (key === 'transfer' && saved.expires_at <= this.deps.now())
|
|
492
549
|
throw ownershipFailure('conflict', 'transfer authorization expired');
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
550
|
+
const beforeWrite = await this.readOwner(ownerAid, task.sub_aid);
|
|
551
|
+
if (original.revision && beforeWrite.revision !== original.revision)
|
|
552
|
+
throw ownershipFailure('conflict', 'legacy owner original revision changed before publication');
|
|
553
|
+
if (beforeWrite.exists !== original.exists || beforeWrite.content !== original.raw || (beforeWrite.exists ? beforeWrite.sha256 : '') !== original.sha256)
|
|
554
|
+
throw ownershipFailure('conflict', 'owner file original material changed before publication');
|
|
555
|
+
task.assertActive?.();
|
|
556
|
+
const published = await this.deps.writeOwnerMd(ownerAid, task.sub_aid, saved.raw, beforeWrite, task.assertActive);
|
|
557
|
+
if (!published.exists || published.sha256 !== saved.hash || published.content !== saved.raw)
|
|
495
558
|
throw ownershipFailure('unavailable', 'owner publication could not be verified', true);
|
|
496
|
-
saved.published_revision = published.revision;
|
|
497
559
|
await task.persist();
|
|
498
560
|
return published;
|
|
499
561
|
}
|
|
@@ -504,24 +566,24 @@ export class OwnershipFiles {
|
|
|
504
566
|
const fields = await prepare(current);
|
|
505
567
|
if (cleanup && !parseOwnershipFile(current.content, 'agent', { now: this.deps.now() }).statement)
|
|
506
568
|
return current;
|
|
507
|
-
const payload = updateOwnershipAgentMd(current.content, fields);
|
|
569
|
+
const payload = updateOwnershipAgentMd(current.exists ? current.content : buildDefaultAgentMd(task.sub_aid, { type: this.deps.currentType?.() ?? 'agent' }), fields);
|
|
508
570
|
const raw = signOwnershipFile(this.deps.currentAid(), payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent', { now: this.deps.now(), expectedSubAid: task.sub_aid });
|
|
509
571
|
saved = { raw, hash: ownershipSha256(raw), condition: current.exists ? { if_match: current.etag } : { if_none_match: '*' } };
|
|
510
572
|
task.files.agent = saved;
|
|
511
573
|
await task.persist();
|
|
512
574
|
}
|
|
513
575
|
await this.verifyFile(saved.raw, 'agent', task.sub_aid);
|
|
514
|
-
if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === task.target_relation_version && current.lock === (cleanup ? 'unlocked' : 'locked')) {
|
|
515
|
-
if (saved.
|
|
576
|
+
if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === (cleanup ? 0 : task.target_relation_version) && current.lock === (cleanup ? 'unlocked' : 'locked')) {
|
|
577
|
+
if (saved.published_etag !== undefined && current.etag !== saved.published_etag)
|
|
516
578
|
throw ownershipFailure('conflict', 'agent.md published ETag changed');
|
|
517
|
-
saved.
|
|
579
|
+
saved.published_etag = current.etag;
|
|
518
580
|
await task.persist();
|
|
519
581
|
return current;
|
|
520
582
|
}
|
|
521
|
-
if (current.etag !== saved.condition.if_match)
|
|
583
|
+
if (saved.condition.if_none_match === '*' ? current.exists : !current.exists || current.etag !== saved.condition.if_match)
|
|
522
584
|
throw ownershipFailure('conflict', 'agent.md original ETag condition changed');
|
|
523
585
|
if (cleanup) {
|
|
524
|
-
if (current.lock !== 'unlocked' || current.relation_version !== task.
|
|
586
|
+
if (current.lock !== 'unlocked' || current.relation_version !== task.base_relation_version)
|
|
525
587
|
throw ownershipFailure('conflict', 'release target is no longer current');
|
|
526
588
|
}
|
|
527
589
|
else {
|
|
@@ -532,13 +594,17 @@ export class OwnershipFiles {
|
|
|
532
594
|
if (!target.exists)
|
|
533
595
|
throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
|
|
534
596
|
const declaration = await this.verifyFile(target.content, 'owner', task.sub_aid, this.targetOwner(task));
|
|
597
|
+
const savedOwner = task.files.owner;
|
|
598
|
+
if (savedOwner && (savedOwner.raw !== target.content || savedOwner.hash !== target.sha256))
|
|
599
|
+
throw ownershipFailure('conflict', 'fixed target owner declaration changed');
|
|
535
600
|
if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
|
|
536
601
|
throw ownershipFailure('conflict', 'target owner declaration changed');
|
|
537
602
|
}
|
|
538
|
-
|
|
539
|
-
|
|
603
|
+
task.assertActive?.();
|
|
604
|
+
const published = await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition, task.assertActive);
|
|
605
|
+
if (published.content !== saved.raw || published.relation_version !== (cleanup ? 0 : task.target_relation_version) || published.lock !== (cleanup ? 'unlocked' : 'locked'))
|
|
540
606
|
throw ownershipFailure('unavailable', 'agent.md publication could not be verified', true);
|
|
541
|
-
saved.
|
|
607
|
+
saved.published_etag = published.etag;
|
|
542
608
|
await task.persist();
|
|
543
609
|
return published;
|
|
544
610
|
}
|
|
@@ -557,21 +623,24 @@ export class OwnershipStorageAdapter {
|
|
|
557
623
|
return result;
|
|
558
624
|
}
|
|
559
625
|
catch (error) {
|
|
626
|
+
const reading = method === 'storage.head_object' || method === 'storage.get_object';
|
|
560
627
|
const code = error && typeof error === 'object' ? error.code : undefined;
|
|
561
628
|
if (error instanceof ValidationError && code !== -32601 || error instanceof StateError && error.stringCode === 'invalid')
|
|
562
629
|
throw new ValidationError(error.message, { stringCode: 'invalid' });
|
|
563
630
|
if (error instanceof StateError && ['unavailable', 'unsupported_protocol', 'conflict'].includes(error.stringCode))
|
|
564
631
|
throw ownershipFailure(error.stringCode, error.message, error.stringCode !== 'conflict');
|
|
632
|
+
if (reading && error instanceof StateError && error.stringCode === 'forbidden')
|
|
633
|
+
throw ownershipFailure('unavailable', error.message, true);
|
|
565
634
|
if (error instanceof StateError && error.stringCode)
|
|
566
635
|
throw error;
|
|
567
|
-
throw ownershipFailure(code === -32009 ? 'conflict' : code === -32004 ? 'forbidden' : code === -32601 ? 'unsupported_protocol' : 'unavailable', error instanceof Error ? error.message : String(error),
|
|
636
|
+
throw ownershipFailure(code === -32009 ? 'conflict' : code === -32004 ? reading ? 'unavailable' : 'forbidden' : code === -32601 ? 'unsupported_protocol' : 'unavailable', error instanceof Error ? error.message : String(error), code !== -32009 && (reading || code !== -32004));
|
|
568
637
|
}
|
|
569
638
|
}
|
|
570
639
|
metadata(value, ownerAid, path) {
|
|
571
640
|
const now = this.now();
|
|
572
641
|
const expectedService = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
|
|
573
|
-
if (value.consistency !== 'authoritative' || value.owner_aid !== ownerAid || value.bucket !== 'default' || value.path !== path || typeof value.exists !== 'boolean' ||
|
|
574
|
-
throw ownershipFailure('unavailable', 'Storage authoritative namespace
|
|
642
|
+
if (value.consistency !== 'authoritative' || value.owner_aid !== ownerAid || value.bucket !== 'default' || value.path !== path || typeof value.exists !== 'boolean' || !Number.isSafeInteger(value.observed_at) || Number(value.observed_at) < now - 30 || Number(value.observed_at) > now + 5 || value.recovery_ready !== undefined && value.recovery_ready !== true || value.service_aid !== undefined && value.service_aid !== expectedService || value.authenticated_service_aid !== undefined && value.authenticated_service_aid !== expectedService || value.exists && (typeof value.sha256 !== 'string' || !/^[0-9a-f]{64}$/.test(value.sha256)))
|
|
643
|
+
throw ownershipFailure('unavailable', 'Storage authoritative namespace or freshness metadata is incomplete', true);
|
|
575
644
|
}
|
|
576
645
|
async readObject(ownerAid, path) {
|
|
577
646
|
ownershipAid(ownerAid, 'owner_aid');
|
|
@@ -582,9 +651,9 @@ export class OwnershipStorageAdapter {
|
|
|
582
651
|
let content = '';
|
|
583
652
|
let final = before;
|
|
584
653
|
if (before.exists) {
|
|
585
|
-
const object = await this.call('storage.get_object',
|
|
654
|
+
const object = await this.call('storage.get_object', params);
|
|
586
655
|
this.metadata(object, ownerAid, path);
|
|
587
|
-
if (object.exists !== true || object.
|
|
656
|
+
if (object.exists !== true || object.sha256 !== before.sha256 || before.revision !== undefined && object.revision !== before.revision || typeof object.content !== 'string')
|
|
588
657
|
throw ownershipFailure('unavailable', 'Storage object changed or full Base64 content is unavailable', true);
|
|
589
658
|
if (object.content !== '' && !isStrictBase64(object.content))
|
|
590
659
|
throw ownershipFailure('unavailable', 'Storage Base64 content is unavailable', true);
|
|
@@ -599,33 +668,59 @@ export class OwnershipStorageAdapter {
|
|
|
599
668
|
}
|
|
600
669
|
final = await this.call('storage.head_object', params);
|
|
601
670
|
this.metadata(final, ownerAid, path);
|
|
602
|
-
if (final.exists !== true || final.
|
|
603
|
-
throw ownershipFailure('unavailable', 'Storage
|
|
671
|
+
if (final.exists !== true || final.sha256 !== before.sha256 || before.revision !== undefined && final.revision !== before.revision || this.now() - began > 30)
|
|
672
|
+
throw ownershipFailure('unavailable', 'Storage material changed during authoritative read', true);
|
|
604
673
|
}
|
|
605
674
|
const service = `st.${ownerAid.slice(ownerAid.indexOf('.') + 1)}`;
|
|
606
|
-
return { owner_aid: ownerAid, bucket: 'default', path, exists: Boolean(final.exists),
|
|
607
|
-
content, sha256: final.exists ? String(final.sha256) : undefined, authoritative: true, recovery_ready: final.recovery_ready, service_aid: service, authenticated_service_aid: service };
|
|
675
|
+
return { owner_aid: ownerAid, bucket: 'default', path, exists: Boolean(final.exists), observed_at: Number(final.observed_at),
|
|
676
|
+
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 } : {}) };
|
|
608
677
|
}
|
|
609
678
|
async readOwnerMd(ownerAid, subAid) {
|
|
610
679
|
ownershipAid(subAid, 'sub_aid');
|
|
611
680
|
return this.readObject(ownerAid, `sub_aids/${subAid}/owner.md`);
|
|
612
681
|
}
|
|
613
|
-
async writeOwnerMd(ownerAid, subAid, raw,
|
|
682
|
+
async writeOwnerMd(ownerAid, subAid, raw, original, assertActive) {
|
|
614
683
|
this.requireLocal(ownerAid);
|
|
615
684
|
ownershipAid(subAid, 'sub_aid');
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
685
|
+
const path = `sub_aids/${subAid}/owner.md`;
|
|
686
|
+
const before = await this.readOwnerMd(ownerAid, subAid);
|
|
687
|
+
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))
|
|
688
|
+
throw ownershipFailure('conflict', 'owner file original material changed');
|
|
689
|
+
const bytes = new TextEncoder().encode(raw);
|
|
690
|
+
const expectedVersion = (original ?? before).exists ? (original ?? before).version : 0;
|
|
691
|
+
if (typeof expectedVersion !== 'number' || !Number.isSafeInteger(expectedVersion) || expectedVersion < (before.exists ? 1 : 0))
|
|
692
|
+
throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
|
|
693
|
+
assertActive?.();
|
|
694
|
+
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 });
|
|
695
|
+
const uploadURL = String(session.upload_url ?? ''), sessionID = String(session.session_id ?? '');
|
|
696
|
+
if (!uploadURL || !sessionID)
|
|
697
|
+
throw ownershipFailure('unavailable', 'Storage owner.md upload session is incomplete', true);
|
|
698
|
+
const headers = {};
|
|
699
|
+
if (session.headers && typeof session.headers === 'object')
|
|
700
|
+
for (const [key, value] of Object.entries(session.headers))
|
|
701
|
+
if (value !== undefined && value !== null && key.toLowerCase() !== 'content-type')
|
|
702
|
+
headers[key] = String(value);
|
|
703
|
+
headers['Content-Type'] = 'text/markdown; charset=utf-8';
|
|
704
|
+
assertActive?.();
|
|
705
|
+
if (this.deps.httpPut)
|
|
706
|
+
await this.deps.httpPut(uploadURL, bytes, headers);
|
|
707
|
+
else {
|
|
708
|
+
const response = await fetch(uploadURL, { method: 'PUT', body: bytes, headers });
|
|
709
|
+
if (!response.ok)
|
|
710
|
+
throw ownershipFailure('unavailable', `Storage owner.md HTTP PUT failed: ${response.status}`, true);
|
|
711
|
+
}
|
|
712
|
+
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 };
|
|
713
|
+
complete.expected_version = expectedVersion;
|
|
714
|
+
assertActive?.();
|
|
715
|
+
await this.call('storage.complete_upload', complete);
|
|
619
716
|
return this.readOwnerMd(ownerAid, subAid);
|
|
620
717
|
}
|
|
621
|
-
async deleteOwnerMd(ownerAid, subAid
|
|
718
|
+
async deleteOwnerMd(ownerAid, subAid) {
|
|
622
719
|
this.requireLocal(ownerAid);
|
|
623
720
|
ownershipAid(subAid, 'sub_aid');
|
|
624
|
-
|
|
625
|
-
throw ownershipFailure('invalid', 'original Storage revision is required');
|
|
626
|
-
await this.call('storage.delete_object', { owner_aid: ownerAid, bucket: 'default', object_key: `sub_aids/${subAid}/owner.md`, expected_revision: originalRevision });
|
|
721
|
+
await this.call('storage.delete_object', { owner_aid: ownerAid, bucket: 'default', object_key: `sub_aids/${subAid}/owner.md` });
|
|
627
722
|
if ((await this.readOwnerMd(ownerAid, subAid)).exists)
|
|
628
|
-
throw ownershipFailure('unavailable', 'Storage
|
|
723
|
+
throw ownershipFailure('unavailable', 'Storage deletion could not be confirmed', true);
|
|
629
724
|
}
|
|
630
725
|
requireLocal(ownerAid) {
|
|
631
726
|
if (ownershipAid(ownerAid, 'owner_aid') !== this.deps.currentAid())
|