@agentunion/fastaun 0.5.19 → 0.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -78
- package/_packed_docs/CHANGELOG.md +129 -78
- package/_packed_docs/INDEX.md +201 -195
- package/_packed_docs/KITE_DOCS_GUIDE.md +93 -90
- package/_packed_docs/agent.md/SCHEMA.md +7 -7
- 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 +1072 -1072
- 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 +443 -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 +658 -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/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +67 -63
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +35 -8
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
- package/_packed_docs/sdk/09-group-rpc-manual.md +258 -254
- package/_packed_docs/sdk/09-message-rpc-manual.md +7 -4
- package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +61 -52
- package/_packed_docs/sdk/INDEX.md +92 -80
- 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 +922 -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 +300 -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 +382 -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 +476 -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 +61 -33
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +8 -1
- package/dist/agent-md.js +243 -119
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +29 -0
- package/dist/aid-store.js +179 -34
- 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 +74 -2
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +43 -1
- package/dist/cert-utils.js +144 -56
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +9 -0
- package/dist/client/delivery.js +179 -11
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.js +125 -82
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.js +95 -89
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.js +48 -19
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +5 -3
- package/dist/events.js +25 -8
- package/dist/events.js.map +1 -1
- package/dist/facades.js +5 -5
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +1 -1
- package/dist/keystore/aid-db.js +11 -2
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +1 -1
- package/dist/keystore/local-token-store.d.ts +1 -1
- package/dist/keystore/local-token-store.js +2 -2
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.js +77 -13
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +26 -8
- package/dist/ownership-v21.js +147 -70
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +5 -0
- package/dist/ownership-workflow.js +366 -56
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +2 -1
- package/dist/tools/cross-sdk-agent.js +109 -6
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +10 -0
- package/dist/transport.js.map +1 -1
- 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
2
|
import { 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)))
|
|
@@ -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,36 @@ 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 ?? '' : '';
|
|
249
261
|
if (owner.exists) {
|
|
250
262
|
const declaration = await this.verifyFile(owner.content, 'owner', subAid, ownerAid);
|
|
251
|
-
if (
|
|
263
|
+
if (statement) {
|
|
252
264
|
if (declaration.statement.relation_version !== before.relation_version)
|
|
253
265
|
throw new ValidationError('ownership relation_version mismatch');
|
|
254
|
-
|
|
255
|
-
throw ownershipFailure('unavailable', 'NS has not confirmed the current release', true);
|
|
256
|
-
result.status = 'consensus';
|
|
266
|
+
result.status = declaration.action === 'release' ? 'released' : 'consensus';
|
|
257
267
|
}
|
|
258
268
|
else if (target && declaration.action === 'declare' && declaration.statement.relation_version === target.relation_version && target.relation_version === before.relation_version + 1) {
|
|
259
269
|
result.status = 'single_sided';
|
|
260
270
|
}
|
|
261
271
|
}
|
|
262
|
-
else if (
|
|
263
|
-
|
|
272
|
+
else if (statement)
|
|
273
|
+
result.status = 'released';
|
|
264
274
|
}
|
|
275
|
+
if (statement && before.lock !== (result.status === 'released' ? 'unlocked' : 'locked'))
|
|
276
|
+
throw ownershipFailure('unavailable', 'NS lock does not match current ownership materials', true);
|
|
277
|
+
result.owner_aid = result.status === 'consensus' ? statement.owner_aid : null;
|
|
265
278
|
const after = await this.readAgent(subAid);
|
|
279
|
+
if (ownerAid && ownerBefore) {
|
|
280
|
+
const ownerAfter = await this.readOwner(ownerAid, subAid);
|
|
281
|
+
if (ownerBefore.exists !== ownerAfter.exists || ownerBefore.content !== ownerAfter.content || ownerBefore.sha256 !== ownerAfter.sha256)
|
|
282
|
+
throw ownershipFailure('unavailable', 'owner.md changed during consensus check', true);
|
|
283
|
+
}
|
|
266
284
|
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
285
|
throw ownershipFailure('unavailable', 'NS relation changed or verification window elapsed during consensus check', true);
|
|
268
286
|
return result;
|
|
@@ -316,11 +334,15 @@ export class OwnershipFiles {
|
|
|
316
334
|
return this.publishAgent(task, async (agent) => {
|
|
317
335
|
if (agent.lock === 'locked')
|
|
318
336
|
throw ownershipFailure('owner_locked', 'NS has not confirmed release');
|
|
337
|
+
const parsed = parseOwnershipFile(agent.content, 'agent', { expectedSubAid: task.sub_aid });
|
|
338
|
+
if (!parsed.statement && agent.relation_version === 0)
|
|
339
|
+
return {};
|
|
319
340
|
if (agent.relation_version !== task.target_relation_version)
|
|
320
341
|
throw ownershipFailure('conflict', 'release relation_version changed');
|
|
321
|
-
const parsed = parseOwnershipFile(agent.content, 'agent', { expectedSubAid: task.sub_aid });
|
|
322
342
|
if (parsed.statement && (parsed.statement.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
|
|
323
343
|
throw ownershipFailure('conflict', 'agent.md no longer contains the released relation');
|
|
344
|
+
if (task.expected_agent_statement && Object.keys(task.expected_agent_statement).some((field) => task.expected_agent_statement[field] !== parsed.statement?.[field]))
|
|
345
|
+
throw ownershipFailure('conflict', 'agent.md no longer contains the approved statement');
|
|
324
346
|
return {};
|
|
325
347
|
}, true);
|
|
326
348
|
}
|
|
@@ -338,8 +360,8 @@ export class OwnershipFiles {
|
|
|
338
360
|
async cleanupOwnerFile(task) {
|
|
339
361
|
this.requireLocal(task.owner_aid);
|
|
340
362
|
const source = this.savedFile(task, task.operation === 'release' ? 'release' : 'transfer');
|
|
341
|
-
if (!source
|
|
342
|
-
throw ownershipFailure('conflict', 'original
|
|
363
|
+
if (!source)
|
|
364
|
+
throw ownershipFailure('conflict', 'original owner file is unavailable');
|
|
343
365
|
const consensus = await this.checkConsensus(task.sub_aid);
|
|
344
366
|
if (consensus.status === 'not_applicable')
|
|
345
367
|
throw ownershipFailure('not_applicable', consensus.reason ?? 'group/org use their dedicated lifecycle ownership');
|
|
@@ -347,19 +369,24 @@ export class OwnershipFiles {
|
|
|
347
369
|
throw new ValidationError(consensus.reason ?? 'ownership consensus is invalid', { stringCode: 'invalid' });
|
|
348
370
|
if (consensus.status === 'unavailable')
|
|
349
371
|
throw ownershipFailure(consensus.code ?? 'unavailable', consensus.reason ?? 'ownership consensus is unavailable', true);
|
|
350
|
-
if (
|
|
351
|
-
|
|
372
|
+
if (task.operation === 'release' && consensus.status === 'released')
|
|
373
|
+
return;
|
|
374
|
+
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)
|
|
375
|
+
throw ownershipFailure('conflict', 'current materials do not permit this task cleanup');
|
|
352
376
|
const current = await this.readOwner(task.owner_aid, task.sub_aid);
|
|
353
|
-
if (current.exists && (current.
|
|
354
|
-
throw ownershipFailure('conflict', 'owner file
|
|
377
|
+
if (current.exists && (current.content !== source.raw || current.sha256 !== source.hash))
|
|
378
|
+
throw ownershipFailure('conflict', 'owner file changed before cleanup');
|
|
355
379
|
if (!task.files.cleanup) {
|
|
356
|
-
task.files.cleanup = { raw: source.raw, hash: source.hash, condition: {
|
|
380
|
+
task.files.cleanup = { raw: source.raw, hash: source.hash, condition: {} };
|
|
357
381
|
await task.persist();
|
|
358
382
|
}
|
|
359
383
|
else
|
|
360
384
|
this.savedFile(task, 'cleanup');
|
|
361
|
-
|
|
362
|
-
|
|
385
|
+
const beforeDelete = await this.readOwner(task.owner_aid, task.sub_aid);
|
|
386
|
+
if (beforeDelete.exists && (beforeDelete.content !== source.raw || beforeDelete.sha256 !== source.hash))
|
|
387
|
+
throw ownershipFailure('conflict', 'owner file changed before cleanup');
|
|
388
|
+
if (beforeDelete.exists)
|
|
389
|
+
await this.deps.deleteOwnerMd(task.owner_aid, task.sub_aid);
|
|
363
390
|
task.files.cleanup.deleted = true;
|
|
364
391
|
await task.persist();
|
|
365
392
|
}
|
|
@@ -379,7 +406,7 @@ export class OwnershipFiles {
|
|
|
379
406
|
async readOwner(ownerAid, subAid) {
|
|
380
407
|
const value = await this.deps.readOwnerMd(ownerAid, subAid);
|
|
381
408
|
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' ||
|
|
409
|
+
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
410
|
throw ownershipFailure('unavailable', 'Storage authoritative observation is incomplete or changed', true);
|
|
384
411
|
if (value.exists && value.sha256 !== ownershipSha256(value.content))
|
|
385
412
|
throw ownershipFailure('unavailable', 'Storage complete file digest does not match authoritative observation', true);
|
|
@@ -396,13 +423,19 @@ export class OwnershipFiles {
|
|
|
396
423
|
const parsed = parseOwnershipFile(raw, role, context);
|
|
397
424
|
const signer = role === 'agent' ? subAid : String(parsed.frontmatter.owner_aid);
|
|
398
425
|
const certificate = await this.deps.resolveCertificate(signer, parsed.signature.cert_fingerprint, role === 'agent' && parsed.frontmatter.type === 'group' ? 'group_identity' : undefined);
|
|
399
|
-
|
|
426
|
+
const additional = new Map();
|
|
427
|
+
for (const item of parseAgentMdTailSignature(raw).signatures.slice(1))
|
|
428
|
+
additional.set(`${item.fields.signer_aid};${item.fields.cert_fingerprint}`, await this.deps.resolveCertificate(item.fields.signer_aid, item.fields.cert_fingerprint));
|
|
429
|
+
return verifyOwnershipFile(raw, role, certificate, { ...context, now: this.deps.now(), certificateResolver: (aid, fingerprint) => additional.get(`${aid};${fingerprint}`) });
|
|
400
430
|
}
|
|
401
431
|
async requireTransfer(task) {
|
|
402
432
|
const owner = await this.readOwner(task.owner_aid, task.sub_aid);
|
|
403
433
|
if (!owner.exists)
|
|
404
434
|
throw ownershipFailure('unavailable', 'original owner authorization is missing', true);
|
|
405
435
|
const authorization = await this.verifyFile(owner.content, 'owner', task.sub_aid, task.owner_aid);
|
|
436
|
+
const saved = task.files.transfer;
|
|
437
|
+
if (saved && (saved.raw !== owner.content || saved.hash !== owner.sha256))
|
|
438
|
+
throw ownershipFailure('conflict', 'fixed transfer authorization changed');
|
|
406
439
|
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
440
|
throw ownershipFailure('conflict', 'transfer authorization does not match this task');
|
|
408
441
|
const expiresAt = authorization.transfer.expires_at;
|
|
@@ -429,17 +462,22 @@ export class OwnershipFiles {
|
|
|
429
462
|
}
|
|
430
463
|
async assertBase(task, agent) {
|
|
431
464
|
ownershipInteger(task.base_relation_version, 'base_relation_version', 0);
|
|
432
|
-
if (ownershipInteger(task.target_relation_version, 'target_relation_version') !== task.base_relation_version + 1 || agent.relation_version !== task.base_relation_version)
|
|
465
|
+
if (ownershipInteger(task.target_relation_version, 'target_relation_version') !== task.base_relation_version + (task.operation === 'release' ? 0 : 1) || agent.relation_version !== task.base_relation_version)
|
|
433
466
|
throw ownershipFailure('conflict', 'NS relation_version changed');
|
|
434
467
|
if (!agent.exists)
|
|
435
468
|
throw ownershipFailure('unavailable', 'initial agent.md is not published', true);
|
|
436
|
-
const parsed =
|
|
437
|
-
|
|
469
|
+
const parsed = await this.verifyFile(agent.content, 'agent', task.sub_aid);
|
|
470
|
+
const base = task.base_agent_md;
|
|
471
|
+
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
|
+
throw ownershipFailure('conflict', 'the original ownership statement was replaced');
|
|
473
|
+
if (agent.relation_version !== (parsed.statement?.relation_version ?? 0))
|
|
438
474
|
throw ownershipFailure('unavailable', 'NS relation metadata does not match agent.md', true);
|
|
439
475
|
if (task.operation === 'bind' && parsed.frontmatter.type === 'human')
|
|
440
476
|
throw ownershipFailure('invalid', 'human cannot declare an owner');
|
|
441
477
|
if (task.operation === 'bind' && agent.lock !== 'unlocked')
|
|
442
478
|
throw ownershipFailure('owner_locked', 'an existing owner relation is locked');
|
|
479
|
+
if (task.operation === 'bind' && parsed.statement?.owner_aid === this.targetOwner(task))
|
|
480
|
+
throw ownershipFailure('conflict', 'clear the released agent.md statement before binding the same owner');
|
|
443
481
|
if (task.operation !== 'bind' && (agent.lock !== 'locked' || parsed.statement?.owner_aid !== task.owner_aid || parsed.statement.relation_version !== task.base_relation_version))
|
|
444
482
|
throw ownershipFailure('conflict', 'the original relation is no longer current');
|
|
445
483
|
}
|
|
@@ -451,25 +489,35 @@ export class OwnershipFiles {
|
|
|
451
489
|
}
|
|
452
490
|
savedFile(task, key) {
|
|
453
491
|
const saved = task.files[key];
|
|
454
|
-
if (saved && (typeof saved.raw !== 'string' || saved.hash !== ownershipSha256(saved.raw) || !this.validSavedCondition(key, saved.condition) || saved.
|
|
492
|
+
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
493
|
throw ownershipFailure('persistence_unavailable', 'saved ownership file is corrupt');
|
|
494
|
+
const original = saved?.original_owner;
|
|
495
|
+
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 !== ''))
|
|
496
|
+
throw ownershipFailure('persistence_unavailable', 'original owner.md evidence is corrupt');
|
|
456
497
|
return saved;
|
|
457
498
|
}
|
|
458
499
|
validSavedCondition(key, condition) {
|
|
459
|
-
if (!condition || typeof condition !== 'object'
|
|
500
|
+
if (!condition || typeof condition !== 'object')
|
|
501
|
+
return false;
|
|
502
|
+
if (key !== 'agent')
|
|
503
|
+
return Object.keys(condition).length === 0;
|
|
504
|
+
if (Object.keys(condition).length !== 1)
|
|
460
505
|
return false;
|
|
461
506
|
if (key === 'agent')
|
|
462
507
|
return typeof condition.if_match === 'string' && condition.if_match.length > 0 || condition.if_none_match === '*';
|
|
463
|
-
return
|
|
508
|
+
return false;
|
|
464
509
|
}
|
|
465
510
|
async publishOwner(task, key, ownerAid, prepare) {
|
|
466
511
|
let saved = this.savedFile(task, key);
|
|
467
512
|
await this.readAgent(task.sub_aid);
|
|
468
513
|
const current = await this.readOwner(ownerAid, task.sub_aid);
|
|
469
514
|
if (!saved) {
|
|
515
|
+
const base = task.base_owner_md;
|
|
516
|
+
if (base && ownerAid === task.base_agent_md?.statement?.owner_aid && (current.exists !== base.exists || current.content !== base.raw || (current.exists ? current.sha256 : '') !== base.sha256))
|
|
517
|
+
throw ownershipFailure('conflict', 'original owner.md material changed before publication');
|
|
470
518
|
const ownership = await prepare(current);
|
|
471
519
|
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: {
|
|
520
|
+
saved = { raw, hash: ownershipSha256(raw), condition: {}, original_owner: { exists: current.exists, raw: current.content, sha256: current.exists ? ownershipSha256(current.content) : '' } };
|
|
473
521
|
if (key === 'transfer')
|
|
474
522
|
saved.expires_at = ownership.transfer.expires_at;
|
|
475
523
|
task.files[key] = saved;
|
|
@@ -477,23 +525,25 @@ export class OwnershipFiles {
|
|
|
477
525
|
}
|
|
478
526
|
await this.verifyFile(saved.raw, 'owner', task.sub_aid, ownerAid);
|
|
479
527
|
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
528
|
await task.persist();
|
|
484
529
|
return current;
|
|
485
530
|
}
|
|
486
|
-
|
|
487
|
-
|
|
531
|
+
const original = saved.original_owner;
|
|
532
|
+
if (!original || typeof original.exists !== 'boolean' || typeof original.raw !== 'string' || original.sha256 !== (original.exists ? ownershipSha256(original.raw) : ''))
|
|
533
|
+
throw ownershipFailure('persistence_unavailable', 'owner file original material is missing or corrupt');
|
|
534
|
+
if (current.exists !== original.exists || current.content !== original.raw || (current.exists ? current.sha256 : '') !== original.sha256)
|
|
535
|
+
throw ownershipFailure('conflict', 'owner file original material changed');
|
|
488
536
|
await this.assertBase(task, await this.readAgent(task.sub_aid));
|
|
489
537
|
if (key === 'owner' && task.operation === 'transfer')
|
|
490
538
|
await this.requireTransfer(task);
|
|
491
539
|
if (key === 'transfer' && saved.expires_at <= this.deps.now())
|
|
492
540
|
throw ownershipFailure('conflict', 'transfer authorization expired');
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
541
|
+
const beforeWrite = await this.readOwner(ownerAid, task.sub_aid);
|
|
542
|
+
if (beforeWrite.exists !== original.exists || beforeWrite.content !== original.raw || (beforeWrite.exists ? beforeWrite.sha256 : '') !== original.sha256)
|
|
543
|
+
throw ownershipFailure('conflict', 'owner file original material changed before publication');
|
|
544
|
+
const published = await this.deps.writeOwnerMd(ownerAid, task.sub_aid, saved.raw);
|
|
545
|
+
if (!published.exists || published.sha256 !== saved.hash || published.content !== saved.raw)
|
|
495
546
|
throw ownershipFailure('unavailable', 'owner publication could not be verified', true);
|
|
496
|
-
saved.published_revision = published.revision;
|
|
497
547
|
await task.persist();
|
|
498
548
|
return published;
|
|
499
549
|
}
|
|
@@ -511,10 +561,10 @@ export class OwnershipFiles {
|
|
|
511
561
|
await task.persist();
|
|
512
562
|
}
|
|
513
563
|
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.
|
|
564
|
+
if (current.exists && ownershipSha256(current.content) === saved.hash && current.content === saved.raw && current.relation_version === (cleanup ? 0 : task.target_relation_version) && current.lock === (cleanup ? 'unlocked' : 'locked')) {
|
|
565
|
+
if (saved.published_etag !== undefined && current.etag !== saved.published_etag)
|
|
516
566
|
throw ownershipFailure('conflict', 'agent.md published ETag changed');
|
|
517
|
-
saved.
|
|
567
|
+
saved.published_etag = current.etag;
|
|
518
568
|
await task.persist();
|
|
519
569
|
return current;
|
|
520
570
|
}
|
|
@@ -532,13 +582,16 @@ export class OwnershipFiles {
|
|
|
532
582
|
if (!target.exists)
|
|
533
583
|
throw ownershipFailure('unavailable', 'target owner declaration is missing', true);
|
|
534
584
|
const declaration = await this.verifyFile(target.content, 'owner', task.sub_aid, this.targetOwner(task));
|
|
585
|
+
const savedOwner = task.files.owner;
|
|
586
|
+
if (savedOwner && (savedOwner.raw !== target.content || savedOwner.hash !== target.sha256))
|
|
587
|
+
throw ownershipFailure('conflict', 'fixed target owner declaration changed');
|
|
535
588
|
if (declaration.action !== 'declare' || declaration.statement.relation_version !== task.target_relation_version)
|
|
536
589
|
throw ownershipFailure('conflict', 'target owner declaration changed');
|
|
537
590
|
}
|
|
538
591
|
const published = await this.deps.submitAgentMdRaw(task.sub_aid, saved.raw, saved.condition);
|
|
539
|
-
if (published.content !== saved.raw || published.relation_version !== task.target_relation_version || published.lock !== (cleanup ? 'unlocked' : 'locked'))
|
|
592
|
+
if (published.content !== saved.raw || published.relation_version !== (cleanup ? 0 : task.target_relation_version) || published.lock !== (cleanup ? 'unlocked' : 'locked'))
|
|
540
593
|
throw ownershipFailure('unavailable', 'agent.md publication could not be verified', true);
|
|
541
|
-
saved.
|
|
594
|
+
saved.published_etag = published.etag;
|
|
542
595
|
await task.persist();
|
|
543
596
|
return published;
|
|
544
597
|
}
|
|
@@ -557,21 +610,24 @@ export class OwnershipStorageAdapter {
|
|
|
557
610
|
return result;
|
|
558
611
|
}
|
|
559
612
|
catch (error) {
|
|
613
|
+
const reading = method === 'storage.head_object' || method === 'storage.get_object';
|
|
560
614
|
const code = error && typeof error === 'object' ? error.code : undefined;
|
|
561
615
|
if (error instanceof ValidationError && code !== -32601 || error instanceof StateError && error.stringCode === 'invalid')
|
|
562
616
|
throw new ValidationError(error.message, { stringCode: 'invalid' });
|
|
563
617
|
if (error instanceof StateError && ['unavailable', 'unsupported_protocol', 'conflict'].includes(error.stringCode))
|
|
564
618
|
throw ownershipFailure(error.stringCode, error.message, error.stringCode !== 'conflict');
|
|
619
|
+
if (reading && error instanceof StateError && error.stringCode === 'forbidden')
|
|
620
|
+
throw ownershipFailure('unavailable', error.message, true);
|
|
565
621
|
if (error instanceof StateError && error.stringCode)
|
|
566
622
|
throw error;
|
|
567
|
-
throw ownershipFailure(code === -32009 ? 'conflict' : code === -32004 ? 'forbidden' : code === -32601 ? 'unsupported_protocol' : 'unavailable', error instanceof Error ? error.message : String(error),
|
|
623
|
+
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
624
|
}
|
|
569
625
|
}
|
|
570
626
|
metadata(value, ownerAid, path) {
|
|
571
627
|
const now = this.now();
|
|
572
628
|
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
|
|
629
|
+
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)))
|
|
630
|
+
throw ownershipFailure('unavailable', 'Storage authoritative namespace or freshness metadata is incomplete', true);
|
|
575
631
|
}
|
|
576
632
|
async readObject(ownerAid, path) {
|
|
577
633
|
ownershipAid(ownerAid, 'owner_aid');
|
|
@@ -582,9 +638,9 @@ export class OwnershipStorageAdapter {
|
|
|
582
638
|
let content = '';
|
|
583
639
|
let final = before;
|
|
584
640
|
if (before.exists) {
|
|
585
|
-
const object = await this.call('storage.get_object',
|
|
641
|
+
const object = await this.call('storage.get_object', params);
|
|
586
642
|
this.metadata(object, ownerAid, path);
|
|
587
|
-
if (object.exists !== true || object.
|
|
643
|
+
if (object.exists !== true || object.sha256 !== before.sha256 || typeof object.content !== 'string')
|
|
588
644
|
throw ownershipFailure('unavailable', 'Storage object changed or full Base64 content is unavailable', true);
|
|
589
645
|
if (object.content !== '' && !isStrictBase64(object.content))
|
|
590
646
|
throw ownershipFailure('unavailable', 'Storage Base64 content is unavailable', true);
|
|
@@ -599,33 +655,54 @@ export class OwnershipStorageAdapter {
|
|
|
599
655
|
}
|
|
600
656
|
final = await this.call('storage.head_object', params);
|
|
601
657
|
this.metadata(final, ownerAid, path);
|
|
602
|
-
if (final.exists !== true || final.
|
|
603
|
-
throw ownershipFailure('unavailable', 'Storage
|
|
658
|
+
if (final.exists !== true || final.sha256 !== before.sha256 || this.now() - began > 30)
|
|
659
|
+
throw ownershipFailure('unavailable', 'Storage material changed during authoritative read', true);
|
|
604
660
|
}
|
|
605
661
|
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 };
|
|
662
|
+
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 };
|
|
608
664
|
}
|
|
609
665
|
async readOwnerMd(ownerAid, subAid) {
|
|
610
666
|
ownershipAid(subAid, 'sub_aid');
|
|
611
667
|
return this.readObject(ownerAid, `sub_aids/${subAid}/owner.md`);
|
|
612
668
|
}
|
|
613
|
-
async writeOwnerMd(ownerAid, subAid, raw
|
|
669
|
+
async writeOwnerMd(ownerAid, subAid, raw) {
|
|
614
670
|
this.requireLocal(ownerAid);
|
|
615
671
|
ownershipAid(subAid, 'sub_aid');
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
672
|
+
const path = `sub_aids/${subAid}/owner.md`;
|
|
673
|
+
const before = await this.readOwnerMd(ownerAid, subAid);
|
|
674
|
+
const bytes = new TextEncoder().encode(raw);
|
|
675
|
+
const expectedVersion = before.exists ? before.version : 0;
|
|
676
|
+
if (typeof expectedVersion !== 'number' || !Number.isSafeInteger(expectedVersion) || expectedVersion < (before.exists ? 1 : 0))
|
|
677
|
+
throw ownershipFailure('unavailable', 'Storage owner.md version is unavailable', true);
|
|
678
|
+
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
|
+
const uploadURL = String(session.upload_url ?? ''), sessionID = String(session.session_id ?? '');
|
|
680
|
+
if (!uploadURL || !sessionID)
|
|
681
|
+
throw ownershipFailure('unavailable', 'Storage owner.md upload session is incomplete', true);
|
|
682
|
+
const headers = {};
|
|
683
|
+
if (session.headers && typeof session.headers === 'object')
|
|
684
|
+
for (const [key, value] of Object.entries(session.headers))
|
|
685
|
+
if (value !== undefined && value !== null && key.toLowerCase() !== 'content-type')
|
|
686
|
+
headers[key] = String(value);
|
|
687
|
+
headers['Content-Type'] = 'text/markdown; charset=utf-8';
|
|
688
|
+
if (this.deps.httpPut)
|
|
689
|
+
await this.deps.httpPut(uploadURL, bytes, headers);
|
|
690
|
+
else {
|
|
691
|
+
const response = await fetch(uploadURL, { method: 'PUT', body: bytes, headers });
|
|
692
|
+
if (!response.ok)
|
|
693
|
+
throw ownershipFailure('unavailable', `Storage owner.md HTTP PUT failed: ${response.status}`, true);
|
|
694
|
+
}
|
|
695
|
+
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
|
+
complete.expected_version = expectedVersion;
|
|
697
|
+
await this.call('storage.complete_upload', complete);
|
|
619
698
|
return this.readOwnerMd(ownerAid, subAid);
|
|
620
699
|
}
|
|
621
|
-
async deleteOwnerMd(ownerAid, subAid
|
|
700
|
+
async deleteOwnerMd(ownerAid, subAid) {
|
|
622
701
|
this.requireLocal(ownerAid);
|
|
623
702
|
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 });
|
|
703
|
+
await this.call('storage.delete_object', { owner_aid: ownerAid, bucket: 'default', object_key: `sub_aids/${subAid}/owner.md` });
|
|
627
704
|
if ((await this.readOwnerMd(ownerAid, subAid)).exists)
|
|
628
|
-
throw ownershipFailure('unavailable', 'Storage
|
|
705
|
+
throw ownershipFailure('unavailable', 'Storage deletion could not be confirmed', true);
|
|
629
706
|
}
|
|
630
707
|
requireLocal(ownerAid) {
|
|
631
708
|
if (ownershipAid(ownerAid, 'owner_aid') !== this.deps.currentAid())
|