@dharma-ai-labs/agent-fabric 0.2.49 → 0.2.50
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/dist/index.d.ts +88 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +826 -175
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +2 -2
- package/dist/repositoryCandidateSync.d.ts +55 -0
- package/dist/repositoryCandidateSync.d.ts.map +1 -0
- package/dist/repositoryCandidateSync.js +177 -0
- package/dist/repositoryCandidateSync.js.map +1 -0
- package/dist/repositoryCandidateSync.test.d.ts +2 -0
- package/dist/repositoryCandidateSync.test.d.ts.map +1 -0
- package/dist/repositoryCandidateSync.test.js +141 -0
- package/dist/repositoryCandidateSync.test.js.map +1 -0
- package/dist/repositoryInstalledKnowledge.d.ts +13 -0
- package/dist/repositoryInstalledKnowledge.d.ts.map +1 -0
- package/dist/repositoryInstalledKnowledge.js +25 -0
- package/dist/repositoryInstalledKnowledge.js.map +1 -0
- package/dist/repositoryInstallerFiles.d.ts +6 -0
- package/dist/repositoryInstallerFiles.d.ts.map +1 -0
- package/dist/repositoryInstallerFiles.js +113 -0
- package/dist/repositoryInstallerFiles.js.map +1 -0
- package/dist/repositoryKnowledge.d.ts +119 -0
- package/dist/repositoryKnowledge.d.ts.map +1 -0
- package/dist/repositoryKnowledge.js +462 -0
- package/dist/repositoryKnowledge.js.map +1 -0
- package/dist/repositoryKnowledge.test.d.ts +2 -0
- package/dist/repositoryKnowledge.test.d.ts.map +1 -0
- package/dist/repositoryKnowledge.test.js +245 -0
- package/dist/repositoryKnowledge.test.js.map +1 -0
- package/dist/repositoryKnowledgeRetention.test.d.ts +2 -0
- package/dist/repositoryKnowledgeRetention.test.d.ts.map +1 -0
- package/dist/repositoryKnowledgeRetention.test.js +386 -0
- package/dist/repositoryKnowledgeRetention.test.js.map +1 -0
- package/dist/repositoryOnboardingCaller.test.d.ts +2 -0
- package/dist/repositoryOnboardingCaller.test.d.ts.map +1 -0
- package/dist/repositoryOnboardingCaller.test.js +197 -0
- package/dist/repositoryOnboardingCaller.test.js.map +1 -0
- package/dist/repositoryOnboardingFlow.test.d.ts +2 -0
- package/dist/repositoryOnboardingFlow.test.d.ts.map +1 -0
- package/dist/repositoryOnboardingFlow.test.js +47 -0
- package/dist/repositoryOnboardingFlow.test.js.map +1 -0
- package/dist/repositoryPackage.d.ts +115 -0
- package/dist/repositoryPackage.d.ts.map +1 -0
- package/dist/repositoryPackage.js +1095 -0
- package/dist/repositoryPackage.js.map +1 -0
- package/dist/repositoryPackage.test.d.ts +2 -0
- package/dist/repositoryPackage.test.d.ts.map +1 -0
- package/dist/repositoryPackage.test.js +709 -0
- package/dist/repositoryPackage.test.js.map +1 -0
- package/dist/repositoryPackageDelivery.d.ts +29 -0
- package/dist/repositoryPackageDelivery.d.ts.map +1 -0
- package/dist/repositoryPackageDelivery.js +140 -0
- package/dist/repositoryPackageDelivery.js.map +1 -0
- package/dist/repositoryPackageDelivery.test.d.ts +2 -0
- package/dist/repositoryPackageDelivery.test.d.ts.map +1 -0
- package/dist/repositoryPackageDelivery.test.js +787 -0
- package/dist/repositoryPackageDelivery.test.js.map +1 -0
- package/dist/repositoryPackageDeliveryCaller.test.d.ts +2 -0
- package/dist/repositoryPackageDeliveryCaller.test.d.ts.map +1 -0
- package/dist/repositoryPackageDeliveryCaller.test.js +18 -0
- package/dist/repositoryPackageDeliveryCaller.test.js.map +1 -0
- package/dist/repositoryPackageDeliveryPreflight.test.d.ts +2 -0
- package/dist/repositoryPackageDeliveryPreflight.test.d.ts.map +1 -0
- package/dist/repositoryPackageDeliveryPreflight.test.js +29 -0
- package/dist/repositoryPackageDeliveryPreflight.test.js.map +1 -0
- package/dist/repositoryPackageTransfer.d.ts +85 -0
- package/dist/repositoryPackageTransfer.d.ts.map +1 -0
- package/dist/repositoryPackageTransfer.js +233 -0
- package/dist/repositoryPackageTransfer.js.map +1 -0
- package/dist/repositoryPackageTransfer.test.d.ts +2 -0
- package/dist/repositoryPackageTransfer.test.d.ts.map +1 -0
- package/dist/repositoryPackageTransfer.test.js +173 -0
- package/dist/repositoryPackageTransfer.test.js.map +1 -0
- package/dist/repositoryPackageTransferV2.test.d.ts +2 -0
- package/dist/repositoryPackageTransferV2.test.d.ts.map +1 -0
- package/dist/repositoryPackageTransferV2.test.js +549 -0
- package/dist/repositoryPackageTransferV2.test.js.map +1 -0
- package/dist/repositoryPackageTransferV2Schema.test.d.ts +2 -0
- package/dist/repositoryPackageTransferV2Schema.test.d.ts.map +1 -0
- package/dist/repositoryPackageTransferV2Schema.test.js +48 -0
- package/dist/repositoryPackageTransferV2Schema.test.js.map +1 -0
- package/dist/repositoryReleaseMetadata.d.ts +37 -0
- package/dist/repositoryReleaseMetadata.d.ts.map +1 -0
- package/dist/repositoryReleaseMetadata.js +251 -0
- package/dist/repositoryReleaseMetadata.js.map +1 -0
- package/dist/repositoryReleaseMetadata.test.d.ts +2 -0
- package/dist/repositoryReleaseMetadata.test.d.ts.map +1 -0
- package/dist/repositoryReleaseMetadata.test.js +297 -0
- package/dist/repositoryReleaseMetadata.test.js.map +1 -0
- package/dist/repositoryRoleDerivation.d.ts +11 -0
- package/dist/repositoryRoleDerivation.d.ts.map +1 -0
- package/dist/repositoryRoleDerivation.js +59 -0
- package/dist/repositoryRoleDerivation.js.map +1 -0
- package/dist/repositoryRoleDerivation.test.d.ts +2 -0
- package/dist/repositoryRoleDerivation.test.d.ts.map +1 -0
- package/dist/repositoryRoleDerivation.test.js +33 -0
- package/dist/repositoryRoleDerivation.test.js.map +1 -0
- package/dist/repositoryRoleMetadata.d.ts +63 -0
- package/dist/repositoryRoleMetadata.d.ts.map +1 -0
- package/dist/repositoryRoleMetadata.js +203 -0
- package/dist/repositoryRoleMetadata.js.map +1 -0
- package/dist/repositoryRoleMetadata.test.d.ts +2 -0
- package/dist/repositoryRoleMetadata.test.d.ts.map +1 -0
- package/dist/repositoryRoleMetadata.test.js +296 -0
- package/dist/repositoryRoleMetadata.test.js.map +1 -0
- package/dist/repositoryRoleQuestion.d.ts +36 -0
- package/dist/repositoryRoleQuestion.d.ts.map +1 -0
- package/dist/repositoryRoleQuestion.js +68 -0
- package/dist/repositoryRoleQuestion.js.map +1 -0
- package/dist/repositoryRoleQuestion.test.d.ts +2 -0
- package/dist/repositoryRoleQuestion.test.d.ts.map +1 -0
- package/dist/repositoryRoleQuestion.test.js +66 -0
- package/dist/repositoryRoleQuestion.test.js.map +1 -0
- package/dist/repositorySourceAuthorization.d.ts +43 -0
- package/dist/repositorySourceAuthorization.d.ts.map +1 -0
- package/dist/repositorySourceAuthorization.js +164 -0
- package/dist/repositorySourceAuthorization.js.map +1 -0
- package/dist/repositorySourceCapture.test.d.ts +2 -0
- package/dist/repositorySourceCapture.test.d.ts.map +1 -0
- package/dist/repositorySourceCapture.test.js +303 -0
- package/dist/repositorySourceCapture.test.js.map +1 -0
- package/dist/repositorySourceSync.d.ts +54 -0
- package/dist/repositorySourceSync.d.ts.map +1 -0
- package/dist/repositorySourceSync.js +88 -0
- package/dist/repositorySourceSync.js.map +1 -0
- package/dist/repositorySourceSync.test.d.ts +2 -0
- package/dist/repositorySourceSync.test.d.ts.map +1 -0
- package/dist/repositorySourceSync.test.js +218 -0
- package/dist/repositorySourceSync.test.js.map +1 -0
- package/dist/schemas/repository-knowledge-retention-v1.schema.json +26 -0
- package/dist/schemas/repository-knowledge-v2.schema.json +74 -0
- package/dist/schemas/repository-knowledge.schema.json +63 -0
- package/dist/schemas/repository-package-chunk-v2.schema.json +15 -0
- package/dist/schemas/repository-package-chunk.schema.json +18 -0
- package/dist/schemas/repository-package-delivery-v1.schema.json +51 -0
- package/dist/schemas/repository-package-envelope-v1.schema.json +16 -0
- package/dist/schemas/repository-package-transfer-v2.schema.json +36 -0
- package/dist/schemas/repository-package-transfer.schema.json +34 -0
- package/dist/schemas/repository-package-v2.schema.json +491 -0
- package/dist/schemas/repository-package-v3.schema.json +36 -0
- package/dist/schemas/repository-package.schema.json +94 -0
- package/dist/schemas/repository-release-manifest.schema.json +34 -0
- package/dist/schemas/repository-role-metadata.schema.json +82 -0
- package/dist/schemas/skill-preparation-pointer-v1.schema.json +28 -0
- package/dist/schemas/skill-preparation-v1.schema.json +32 -0
- package/dist/skillPollResponse.test.d.ts +2 -0
- package/dist/skillPollResponse.test.d.ts.map +1 -0
- package/dist/skillPollResponse.test.js +37 -0
- package/dist/skillPollResponse.test.js.map +1 -0
- package/dist/skillPreparation.test.d.ts +2 -0
- package/dist/skillPreparation.test.d.ts.map +1 -0
- package/dist/skillPreparation.test.js +163 -0
- package/dist/skillPreparation.test.js.map +1 -0
- package/dist/skillPreparationCache.d.ts +34 -0
- package/dist/skillPreparationCache.d.ts.map +1 -0
- package/dist/skillPreparationCache.js +371 -0
- package/dist/skillPreparationCache.js.map +1 -0
- package/dist/skillPreparationCache.test.d.ts +2 -0
- package/dist/skillPreparationCache.test.d.ts.map +1 -0
- package/dist/skillPreparationCache.test.js +316 -0
- package/dist/skillPreparationCache.test.js.map +1 -0
- package/dist/skillPreparationCaller.test.d.ts +2 -0
- package/dist/skillPreparationCaller.test.d.ts.map +1 -0
- package/dist/skillPreparationCaller.test.js +32 -0
- package/dist/skillPreparationCaller.test.js.map +1 -0
- package/dist/skillPreparationPump.d.ts +11 -0
- package/dist/skillPreparationPump.d.ts.map +1 -0
- package/dist/skillPreparationPump.js +41 -0
- package/dist/skillPreparationPump.js.map +1 -0
- package/dist/skillPreparationPump.test.d.ts +2 -0
- package/dist/skillPreparationPump.test.d.ts.map +1 -0
- package/dist/skillPreparationPump.test.js +87 -0
- package/dist/skillPreparationPump.test.js.map +1 -0
- package/dist/skillPreparationRecord.d.ts +2 -0
- package/dist/skillPreparationRecord.d.ts.map +1 -0
- package/dist/skillPreparationRecord.js +36 -0
- package/dist/skillPreparationRecord.js.map +1 -0
- package/dist/skillPreparationTransaction.d.ts +13 -0
- package/dist/skillPreparationTransaction.d.ts.map +1 -0
- package/dist/skillPreparationTransaction.js +236 -0
- package/dist/skillPreparationTransaction.js.map +1 -0
- package/dist/skillPreparationTransaction.test.d.ts +2 -0
- package/dist/skillPreparationTransaction.test.d.ts.map +1 -0
- package/dist/skillPreparationTransaction.test.js +181 -0
- package/dist/skillPreparationTransaction.test.js.map +1 -0
- package/dist/skillSyncPolicy.test.d.ts +2 -0
- package/dist/skillSyncPolicy.test.d.ts.map +1 -0
- package/dist/skillSyncPolicy.test.js +156 -0
- package/dist/skillSyncPolicy.test.js.map +1 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +1 -1
- package/dist/usage.js.map +1 -1
- package/dist/windowsProcessPortability.test.d.ts +2 -0
- package/dist/windowsProcessPortability.test.d.ts.map +1 -0
- package/dist/windowsProcessPortability.test.js +14 -0
- package/dist/windowsProcessPortability.test.js.map +1 -0
- package/package.json +7 -4
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash, generateKeyPairSync } from 'node:crypto';
|
|
4
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
7
|
+
import { canonicalize, signCanonicalObject, } from '@dharma-ai-labs/agent-fabric-contracts';
|
|
8
|
+
import { calculateBundleHash, contentHash, getActiveSkillBundleAuthorization, installSkillBundle, } from '@dharma-ai-labs/agent-fabric-skill-manager';
|
|
9
|
+
import { planRepositoryPackageTransferV2, } from './repositoryPackageTransfer.js';
|
|
10
|
+
import { receiveRepositoryPackageDelivery } from './repositoryPackageDelivery.js';
|
|
11
|
+
import { serializeSkillPreparationRecord } from './skillPreparationRecord.js';
|
|
12
|
+
const keys = generateKeyPairSync('ed25519');
|
|
13
|
+
const ROOT = '.agents/skills/dharma-agent-fabric/';
|
|
14
|
+
const CATALOG = ROOT + 'knowledge/CATALOG.json';
|
|
15
|
+
const MANIFEST = ROOT + 'MANIFEST.json';
|
|
16
|
+
const PROMPT = '.dharma/onboarding-prompt.md';
|
|
17
|
+
const CREATED = '2026-09-18T18:00:00.000Z';
|
|
18
|
+
const EXPIRES = '2026-09-18T18:01:00.000Z';
|
|
19
|
+
const localScope = {
|
|
20
|
+
organizationId: 'org_delivery_fixture',
|
|
21
|
+
repositoryBindingId: '73a95988-fd64-41ba-a0b9-6c8867d03788',
|
|
22
|
+
repositoryAgentId: '57f61652-a5eb-46e4-930c-9478cd4a9c31',
|
|
23
|
+
deviceId: 'a24b5a90-3b7a-4a81-9503-c9f49be790c3',
|
|
24
|
+
workspaceId: 'b24b5a90-3b7a-4a81-9503-c9f49be790c3',
|
|
25
|
+
provider: 'codex',
|
|
26
|
+
};
|
|
27
|
+
const pin = (letter) => 'sha256:' + letter.repeat(64);
|
|
28
|
+
const hash = (value) => 'sha256:' + createHash('sha256').update(value).digest('hex');
|
|
29
|
+
function file(path, text) {
|
|
30
|
+
return { path, contentBase64: Buffer.from(text).toString('base64'),
|
|
31
|
+
sha256: hash(text), sizeBytes: Buffer.byteLength(text) };
|
|
32
|
+
}
|
|
33
|
+
// This is the legitimate minimal producer-shaped fixture from the metadata
|
|
34
|
+
// tests: a canonical catalog, complete manifest, prompt, root and source skill.
|
|
35
|
+
// Source provenance is hashed metadata, not an independent current grant.
|
|
36
|
+
function metadataFiles() {
|
|
37
|
+
const { organizationId, repositoryAgentId } = localScope;
|
|
38
|
+
const knowledgeBaseId = 'repository-kb:' + hash(canonicalize({
|
|
39
|
+
schema: 'dharma.repository-knowledge-identity/v1', organizationId, repositoryAgentId,
|
|
40
|
+
}));
|
|
41
|
+
const projection = {
|
|
42
|
+
schema: 'dharma.repository-concept-projection/v1', organizationId, repositoryAgentId,
|
|
43
|
+
policyHash: pin('a'), snapshotHash: pin('b'), authority: 'unsigned_projection',
|
|
44
|
+
publicationAuthorized: false, concepts: [], acceptedProposalHashes: [], unresolved: [],
|
|
45
|
+
};
|
|
46
|
+
const catalog = {
|
|
47
|
+
schema: 'dharma.repository-knowledge/v2', managedBy: 'dharma-agent-fabric',
|
|
48
|
+
organizationId, repositoryAgentId, knowledgeBaseId, generation: 1,
|
|
49
|
+
authority: 'requires_verified_release', policyHash: pin('a'), sourceSnapshotHash: pin('b'),
|
|
50
|
+
sourceLocalCatalogHash: pin('c'), projectionHash: hash(canonicalize(projection)),
|
|
51
|
+
repoAtlas: {
|
|
52
|
+
associationId: '244b5a90-3b7a-4a81-9503-c9f49be790c3', knowledgeBaseId,
|
|
53
|
+
organizationId, repositoryAgentId, basis: 'repository_initialization',
|
|
54
|
+
sourceWindowIds: [], analysisHash: null,
|
|
55
|
+
},
|
|
56
|
+
concepts: [], unresolved: [],
|
|
57
|
+
};
|
|
58
|
+
const copies = [
|
|
59
|
+
{ ...file(ROOT + 'SKILL.md', '# Repository Agent Fabric\n'), role: 'skill' },
|
|
60
|
+
{ ...file(ROOT + 'skills/source/.claude/skills/verifier/SKILL.md', '# Verifier\n'), role: 'skill' },
|
|
61
|
+
{ ...file(PROMPT, 'Use the organization repository package.\n'), role: 'onboarding_prompt' },
|
|
62
|
+
{ ...file(CATALOG, canonicalize(catalog) + '\n'), role: 'knowledge' },
|
|
63
|
+
];
|
|
64
|
+
const entryPath = '.claude/skills/verifier/SKILL.md';
|
|
65
|
+
const manifest = {
|
|
66
|
+
schema: 'dharma.repository-release-manifest/v1', organizationId, repositoryAgentId,
|
|
67
|
+
generation: 1, authority: 'requires_verified_release', sourceManifestHash: pin('e'),
|
|
68
|
+
sourceSnapshotHash: pin('b'), policyHash: pin('a'), knowledgeBaseId,
|
|
69
|
+
atlasAssociationId: catalog.repoAtlas.associationId,
|
|
70
|
+
sourceSkills: [{
|
|
71
|
+
path: '.claude/skills/verifier', providerRoot: '.claude/skills', entryPath,
|
|
72
|
+
contentHash: hash(canonicalize([{ path: entryPath, sha256: copies[1].sha256 }])),
|
|
73
|
+
availability: 'available', filePaths: [entryPath],
|
|
74
|
+
observation: { state: 'not_observed', authority: 'caller_supplied_not_runtime_verified', references: [] },
|
|
75
|
+
}],
|
|
76
|
+
files: copies.map(({ contentBase64: _content, ...row }) => row)
|
|
77
|
+
.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0),
|
|
78
|
+
};
|
|
79
|
+
return [...copies.map(({ role: _role, ...row }) => row), file(MANIFEST, canonicalize(manifest) + '\n')];
|
|
80
|
+
}
|
|
81
|
+
// Preserve the installer's existing basename-prefixed path/NUL/bytes/NUL hash,
|
|
82
|
+
// not the canonical source inventory hash.
|
|
83
|
+
function treeHash(files) {
|
|
84
|
+
const digest = createHash('sha256');
|
|
85
|
+
for (const row of files.filter(row => row.path.startsWith(ROOT))
|
|
86
|
+
.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) {
|
|
87
|
+
digest.update('dharma-agent-fabric/' + row.path.slice(ROOT.length));
|
|
88
|
+
digest.update('\0');
|
|
89
|
+
digest.update(Buffer.from(row.contentBase64, 'base64'));
|
|
90
|
+
digest.update('\0');
|
|
91
|
+
}
|
|
92
|
+
return 'sha256:' + digest.digest('hex');
|
|
93
|
+
}
|
|
94
|
+
function fixture(commit = 'a'.repeat(40)) {
|
|
95
|
+
const files = metadataFiles();
|
|
96
|
+
const scope = { ...localScope };
|
|
97
|
+
const transferScope = {
|
|
98
|
+
organizationId: scope.organizationId, repositoryBindingId: scope.repositoryBindingId,
|
|
99
|
+
repositoryAgentId: scope.repositoryAgentId,
|
|
100
|
+
releaseId: 'c24b5a90-3b7a-4a81-9503-c9f49be790c3', generation: 1, gitCommit: commit,
|
|
101
|
+
};
|
|
102
|
+
const plan = planRepositoryPackageTransferV2({ ...transferScope, files });
|
|
103
|
+
const descriptor = {
|
|
104
|
+
schema: 'dharma.repository-package-delivery/v1', delivery: 'authenticated_chunks',
|
|
105
|
+
transferSchema: 'dharma.repository-package-transfer/v2', ...transferScope,
|
|
106
|
+
policyHash: pin('a'), sourceSnapshotHash: pin('b'), sourceManifestHash: pin('e'),
|
|
107
|
+
sourceLocalCatalogHash: pin('c'), catalogHash: files.find(row => row.path === CATALOG).sha256,
|
|
108
|
+
manifestHash: files.find(row => row.path === MANIFEST).sha256, transferIndexHash: plan.indexHash,
|
|
109
|
+
fileCount: files.length, totalBytes: plan.index.totalBytes, maximumFileBytes: 262_144,
|
|
110
|
+
createdAt: CREATED, expiresAt: EXPIRES,
|
|
111
|
+
};
|
|
112
|
+
const base = {
|
|
113
|
+
schema: 'dharma.skill-bundle/v2', bundleId: 'd24b5a90-3b7a-4a81-9503-c9f49be790c3',
|
|
114
|
+
organizationId: scope.organizationId, version: '1.0.0', operation: 'install',
|
|
115
|
+
skills: [{
|
|
116
|
+
skillId: 'dharma-agent-fabric', version: '1.0.0', repository: 'https://github.com/example/repository.git',
|
|
117
|
+
commit, contentHash: treeHash(files), path: ROOT.slice(0, -1),
|
|
118
|
+
}],
|
|
119
|
+
riskClass: 'R0', targetSelectors: {
|
|
120
|
+
organizationAgentIds: [scope.repositoryAgentId], deviceIds: [scope.deviceId],
|
|
121
|
+
workspaceIds: [scope.workspaceId], providers: [scope.provider],
|
|
122
|
+
},
|
|
123
|
+
activationPolicy: 'next_session', rollbackBundleId: null, evaluationReceiptId: 'fixture:evaluated',
|
|
124
|
+
createdAt: CREATED, expiresAt: '2026-09-18T18:02:00.000Z',
|
|
125
|
+
};
|
|
126
|
+
const unsignedBundle = { ...base, bundleHash: calculateBundleHash(base) };
|
|
127
|
+
const bundle = { ...unsignedBundle, signature: signCanonicalObject(unsignedBundle, keys.privateKey) };
|
|
128
|
+
const unsignedEnvelope = {
|
|
129
|
+
schema: 'dharma.repository-package-envelope/v1', bundleId: bundle.bundleId,
|
|
130
|
+
bundleHash: bundle.bundleHash, descriptor,
|
|
131
|
+
};
|
|
132
|
+
const envelope = { ...unsignedEnvelope, signature: signCanonicalObject(unsignedEnvelope, keys.privateKey) };
|
|
133
|
+
return { scope, files, plan, bundle, envelope };
|
|
134
|
+
}
|
|
135
|
+
function signEnvelope(input) {
|
|
136
|
+
const { signature: _signature, ...unsigned } = input.envelope;
|
|
137
|
+
input.envelope.signature = signCanonicalObject(unsigned, keys.privateKey);
|
|
138
|
+
}
|
|
139
|
+
function signBundle(input) {
|
|
140
|
+
const { signature: _signature, bundleHash: _bundleHash, ...base } = input.bundle;
|
|
141
|
+
const unsigned = { ...base, bundleHash: calculateBundleHash(base) };
|
|
142
|
+
input.bundle = { ...unsigned, signature: signCanonicalObject(unsigned, keys.privateKey) };
|
|
143
|
+
input.envelope.bundleId = input.bundle.bundleId;
|
|
144
|
+
input.envelope.bundleHash = input.bundle.bundleHash;
|
|
145
|
+
signEnvelope(input);
|
|
146
|
+
}
|
|
147
|
+
function repackage(input) {
|
|
148
|
+
const descriptor = input.envelope.descriptor;
|
|
149
|
+
input.plan = planRepositoryPackageTransferV2({
|
|
150
|
+
organizationId: descriptor.organizationId, repositoryBindingId: descriptor.repositoryBindingId,
|
|
151
|
+
repositoryAgentId: descriptor.repositoryAgentId, releaseId: descriptor.releaseId,
|
|
152
|
+
generation: descriptor.generation, gitCommit: descriptor.gitCommit, files: input.files,
|
|
153
|
+
});
|
|
154
|
+
descriptor.transferIndexHash = input.plan.indexHash;
|
|
155
|
+
descriptor.fileCount = input.files.length;
|
|
156
|
+
descriptor.totalBytes = input.plan.index.totalBytes;
|
|
157
|
+
descriptor.catalogHash = input.files.find(row => row.path === CATALOG).sha256;
|
|
158
|
+
descriptor.manifestHash = input.files.find(row => row.path === MANIFEST).sha256;
|
|
159
|
+
input.bundle.skills[0].contentHash = treeHash(input.files);
|
|
160
|
+
signBundle(input);
|
|
161
|
+
}
|
|
162
|
+
function readDocument(input, path) {
|
|
163
|
+
return JSON.parse(Buffer.from(input.files.find(row => row.path === path).contentBase64, 'base64').toString('utf8'));
|
|
164
|
+
}
|
|
165
|
+
function replaceDocument(input, path, document, text = canonicalize(document) + '\n') {
|
|
166
|
+
input.files[input.files.findIndex(row => row.path === path)] = file(path, text);
|
|
167
|
+
if (path === CATALOG) {
|
|
168
|
+
const manifest = readDocument(input, MANIFEST);
|
|
169
|
+
const row = manifest.files
|
|
170
|
+
.find(row => row.path === CATALOG);
|
|
171
|
+
const replacement = input.files.find(row => row.path === CATALOG);
|
|
172
|
+
row.sha256 = replacement.sha256;
|
|
173
|
+
row.sizeBytes = replacement.sizeBytes;
|
|
174
|
+
replaceDocument(input, MANIFEST, manifest);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function harness(input) {
|
|
178
|
+
const calls = { index: 0, chunks: [] };
|
|
179
|
+
const clock = { milliseconds: Date.parse('2026-09-18T18:00:01.000Z') };
|
|
180
|
+
const request = {
|
|
181
|
+
envelope: input.envelope, bundle: input.bundle, serverPublicKey: keys.publicKey,
|
|
182
|
+
scope: input.scope, now: () => new Date(clock.milliseconds),
|
|
183
|
+
fetchIndex: async () => { calls.index++; return structuredClone(input.plan.index); },
|
|
184
|
+
fetchChunk: async (fileIndex, chunkIndex) => {
|
|
185
|
+
calls.chunks.push([fileIndex, chunkIndex]);
|
|
186
|
+
const chunk = input.plan.chunks.find(row => row.fileIndex === fileIndex && row.chunkIndex === chunkIndex);
|
|
187
|
+
assert.ok(chunk, 'Fixture must supply a requested indexed chunk.');
|
|
188
|
+
return structuredClone(chunk);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
return { request, calls, clock };
|
|
192
|
+
}
|
|
193
|
+
async function rejectBeforeFetch(change) {
|
|
194
|
+
const input = fixture();
|
|
195
|
+
change(input);
|
|
196
|
+
const { request, calls } = harness(input);
|
|
197
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
198
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
199
|
+
}
|
|
200
|
+
test('signed package delivery returns the complete five-file metadata package and a live currentness guard', async () => {
|
|
201
|
+
const input = fixture();
|
|
202
|
+
const { request, calls } = harness(input);
|
|
203
|
+
const result = await receiveRepositoryPackageDelivery(request);
|
|
204
|
+
assert.deepEqual(result.files, input.files);
|
|
205
|
+
assert.deepEqual(result.bundle, input.bundle);
|
|
206
|
+
assert.equal(result.files.length, 5);
|
|
207
|
+
assert.equal(calls.index, 1);
|
|
208
|
+
assert.equal(calls.chunks.length, input.plan.chunks.length);
|
|
209
|
+
assert.deepEqual([...calls.chunks].sort(), input.plan.chunks.map(row => [row.fileIndex, row.chunkIndex]).sort());
|
|
210
|
+
assert.equal(typeof result.assertCurrent, 'function');
|
|
211
|
+
assert.doesNotThrow(() => result.assertCurrent());
|
|
212
|
+
assert.deepEqual(Object.keys(result).sort(), ['assertCurrent', 'bundle', 'envelope', 'files', 'index']);
|
|
213
|
+
});
|
|
214
|
+
test('signed package preparation retains verified provenance independently of mutable transport objects', async () => {
|
|
215
|
+
const input = fixture();
|
|
216
|
+
const { request } = harness(input);
|
|
217
|
+
const transportIndex = structuredClone(input.plan.index);
|
|
218
|
+
request.fetchIndex = async () => transportIndex;
|
|
219
|
+
const expectedEnvelope = structuredClone(input.envelope);
|
|
220
|
+
const expectedIndex = structuredClone(transportIndex);
|
|
221
|
+
const result = await receiveRepositoryPackageDelivery(request);
|
|
222
|
+
input.envelope.descriptor.gitCommit = 'f'.repeat(40);
|
|
223
|
+
transportIndex.files[0].sha256 = pin('f');
|
|
224
|
+
assert.deepEqual(result.envelope, expectedEnvelope);
|
|
225
|
+
assert.deepEqual(result.index, expectedIndex);
|
|
226
|
+
assert.doesNotThrow(result.assertCurrent);
|
|
227
|
+
});
|
|
228
|
+
test('pending package record checks outer scope and immutable index provenance', async () => {
|
|
229
|
+
const input = fixture();
|
|
230
|
+
const { request } = harness(input);
|
|
231
|
+
const prepared = await receiveRepositoryPackageDelivery(request);
|
|
232
|
+
const { repositoryBindingId, repositoryAgentId, organizationId, workspaceId, deviceId, provider } = input.scope;
|
|
233
|
+
const record = { schema: 'dharma.skill-preparation/v1', repositoryBindingId, repositoryAgentId,
|
|
234
|
+
organizationId, workspaceId, deviceId, provider, policyHash: pin('a'), rolloutId: 'fixture-rollout',
|
|
235
|
+
bundle: prepared.bundle, repositoryPackage: { envelope: prepared.envelope, index: prepared.index },
|
|
236
|
+
preparedAt: CREATED, activationAuthorized: false };
|
|
237
|
+
assert.deepEqual(JSON.parse(await serializeSkillPreparationRecord(record)), record);
|
|
238
|
+
await assert.rejects(serializeSkillPreparationRecord({ ...record,
|
|
239
|
+
repositoryBindingId: 'f24b5a90-3b7a-4a81-9503-c9f49be790c3' }), /repository scope mismatch/);
|
|
240
|
+
const tampered = structuredClone(record);
|
|
241
|
+
tampered.repositoryPackage.index.files[0].sha256 = pin('f');
|
|
242
|
+
await assert.rejects(serializeSkillPreparationRecord(tampered), /provenance mismatch/);
|
|
243
|
+
});
|
|
244
|
+
test('signed package delivery accepts an immutable 64-character commit without narrowing the descriptor', async () => {
|
|
245
|
+
const input = fixture('b'.repeat(64));
|
|
246
|
+
const { request } = harness(input);
|
|
247
|
+
const result = await receiveRepositoryPackageDelivery(request);
|
|
248
|
+
assert.deepEqual(result.files, input.files);
|
|
249
|
+
result.assertCurrent();
|
|
250
|
+
});
|
|
251
|
+
const rejectedParents = [
|
|
252
|
+
['bad signature', input => { input.bundle.signature = 'invalid'; }],
|
|
253
|
+
['unsigned hash mutation', input => { input.bundle.bundleHash = pin('f'); }],
|
|
254
|
+
['cross-schema bundle', input => { input.bundle.schema = 'dharma.skill-bundle/v3'; signBundle(input); }],
|
|
255
|
+
['foreign organization', input => { input.bundle.organizationId = 'org_foreign'; signBundle(input); }],
|
|
256
|
+
['clear operation', input => { input.bundle.operation = 'clear'; input.bundle.skills = []; signBundle(input); }],
|
|
257
|
+
['zero skills', input => { input.bundle.skills = []; signBundle(input); }],
|
|
258
|
+
['multiple skills', input => { input.bundle.skills.push({ ...input.bundle.skills[0] }); signBundle(input); }],
|
|
259
|
+
['wrong skill path', input => { input.bundle.skills[0].path = 'skills/other'; signBundle(input); }],
|
|
260
|
+
['inline files', input => { input.bundle.skills[0].files = [{ path: 'SKILL.md', contentBase64: 'YQ==', sha256: hash('a') }]; signBundle(input); }],
|
|
261
|
+
['empty inline files', input => { input.bundle.skills[0].files = []; signBundle(input); }],
|
|
262
|
+
['foreign pinned commit', input => { input.bundle.skills[0].commit = 'c'.repeat(40); signBundle(input); }],
|
|
263
|
+
['mutable pinned ref', input => { input.bundle.skills[0].commit = 'main'; signBundle(input); }],
|
|
264
|
+
['wildcard repository agent', input => { input.bundle.targetSelectors.organizationAgentIds = []; signBundle(input); }],
|
|
265
|
+
['foreign repository selector', input => { input.bundle.targetSelectors.organizationAgentIds = ['67f61652-a5eb-46e4-930c-9478cd4a9c31']; signBundle(input); }],
|
|
266
|
+
['foreign device selector', input => { input.bundle.targetSelectors.deviceIds = ['e24b5a90-3b7a-4a81-9503-c9f49be790c3']; signBundle(input); }],
|
|
267
|
+
['foreign workspace selector', input => { input.bundle.targetSelectors.workspaceIds = ['e24b5a90-3b7a-4a81-9503-c9f49be790c3']; signBundle(input); }],
|
|
268
|
+
['foreign provider selector', input => { input.bundle.targetSelectors.providers = ['claude']; signBundle(input); }],
|
|
269
|
+
['invalid creation', input => { input.bundle.createdAt = 'not-a-date'; signBundle(input); }],
|
|
270
|
+
['invalid creation calendar', input => { input.bundle.createdAt = '2026-02-30T18:00:00.000Z'; signBundle(input); }],
|
|
271
|
+
['future creation', input => { input.bundle.createdAt = '2026-09-18T18:00:02.000Z'; signBundle(input); }],
|
|
272
|
+
['expired parent', input => { input.bundle.expiresAt = '2026-09-18T18:00:01.000Z'; signBundle(input); }],
|
|
273
|
+
['invalid parent expiry', input => { input.bundle.expiresAt = 'not-a-date'; signBundle(input); }],
|
|
274
|
+
['descriptor outlives parent', input => { input.bundle.expiresAt = '2026-09-18T18:00:30.000Z'; signBundle(input); }],
|
|
275
|
+
];
|
|
276
|
+
for (const [name, change] of rejectedParents)
|
|
277
|
+
test('package delivery makes no fetch for parent ' + name, async () => rejectBeforeFetch(change));
|
|
278
|
+
const rejectedEnvelopes = [
|
|
279
|
+
['bad signature', input => { input.envelope.signature = 'invalid'; }],
|
|
280
|
+
['wrong envelope schema', input => { input.envelope.schema = 'dharma.repository-package-envelope/v2'; signEnvelope(input); }],
|
|
281
|
+
['foreign bundle id', input => { input.envelope.bundleId = 'e24b5a90-3b7a-4a81-9503-c9f49be790c3'; signEnvelope(input); }],
|
|
282
|
+
['foreign bundle hash', input => { input.envelope.bundleHash = pin('f'); signEnvelope(input); }],
|
|
283
|
+
['wrong descriptor schema', input => { input.envelope.descriptor.schema = 'dharma.repository-package-delivery/v2'; signEnvelope(input); }],
|
|
284
|
+
['inline delivery', input => { input.envelope.descriptor.delivery = 'inline'; signEnvelope(input); }],
|
|
285
|
+
['v1 transfer downgrade', input => { input.envelope.descriptor.transferSchema = 'dharma.repository-package-transfer/v1'; signEnvelope(input); }],
|
|
286
|
+
['foreign binding', input => { input.envelope.descriptor.repositoryBindingId = 'e24b5a90-3b7a-4a81-9503-c9f49be790c3'; signEnvelope(input); }],
|
|
287
|
+
['foreign repository', input => { input.envelope.descriptor.repositoryAgentId = '67f61652-a5eb-46e4-930c-9478cd4a9c31'; signEnvelope(input); }],
|
|
288
|
+
['foreign org', input => { input.envelope.descriptor.organizationId = 'org_foreign'; signEnvelope(input); }],
|
|
289
|
+
['future descriptor', input => { input.envelope.descriptor.createdAt = '2026-09-18T18:00:02.000Z'; signEnvelope(input); }],
|
|
290
|
+
['expired descriptor', input => { input.envelope.descriptor.expiresAt = '2026-09-18T18:00:01.000Z'; signEnvelope(input); }],
|
|
291
|
+
['equal lifetime', input => { input.envelope.descriptor.expiresAt = input.envelope.descriptor.createdAt; signEnvelope(input); }],
|
|
292
|
+
['reversed lifetime', input => { input.envelope.descriptor.expiresAt = '2026-09-18T17:59:59.999Z'; signEnvelope(input); }],
|
|
293
|
+
['invalid calendar day', input => { input.envelope.descriptor.createdAt = '2026-02-30T18:00:00.000Z'; signEnvelope(input); }],
|
|
294
|
+
['noncanonical timestamp', input => { input.envelope.descriptor.createdAt = '2026-09-18T18:00:00Z'; signEnvelope(input); }],
|
|
295
|
+
['offset timestamp', input => { input.envelope.descriptor.expiresAt = '2026-09-18T18:01:00.000+00:00'; signEnvelope(input); }],
|
|
296
|
+
];
|
|
297
|
+
for (const [name, change] of rejectedEnvelopes)
|
|
298
|
+
test('package delivery makes no fetch for envelope ' + name, async () => rejectBeforeFetch(change));
|
|
299
|
+
test('package delivery enforces strict public envelope and parent shapes even if signed', async () => {
|
|
300
|
+
for (const key of ['url', 'token', 'authority', 'keyVersion']) {
|
|
301
|
+
const input = fixture();
|
|
302
|
+
const extra = { ...input.envelope, [key]: 'extra' };
|
|
303
|
+
const { signature: _signature, ...unsigned } = extra;
|
|
304
|
+
extra.signature = signCanonicalObject(unsigned, keys.privateKey);
|
|
305
|
+
const { request, calls } = harness(input);
|
|
306
|
+
request.envelope = extra;
|
|
307
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
308
|
+
assert.equal(calls.index, 0);
|
|
309
|
+
}
|
|
310
|
+
const input = fixture();
|
|
311
|
+
const extraBundle = { ...input.bundle, repositoryPackage: input.envelope.descriptor };
|
|
312
|
+
const { signature: _signature, bundleHash: _bundleHash, ...base } = extraBundle;
|
|
313
|
+
const unsigned = { ...base, bundleHash: hash(canonicalize(base)) };
|
|
314
|
+
input.envelope.bundleHash = unsigned.bundleHash;
|
|
315
|
+
signEnvelope(input);
|
|
316
|
+
const { request, calls } = harness(input);
|
|
317
|
+
request.bundle = { ...unsigned, signature: signCanonicalObject(unsigned, keys.privateKey) };
|
|
318
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
319
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
320
|
+
});
|
|
321
|
+
test('package delivery rejects envelope required-field omissions and extra descriptor fields before fetch', async () => {
|
|
322
|
+
for (const key of ['schema', 'bundleId', 'bundleHash', 'descriptor', 'signature']) {
|
|
323
|
+
const input = fixture();
|
|
324
|
+
const envelope = { ...input.envelope };
|
|
325
|
+
delete envelope[key];
|
|
326
|
+
const { request, calls } = harness(input);
|
|
327
|
+
request.envelope = envelope;
|
|
328
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
329
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
330
|
+
}
|
|
331
|
+
const input = fixture();
|
|
332
|
+
Object.assign(input.envelope.descriptor, { url: 'https://example.invalid/package' });
|
|
333
|
+
signEnvelope(input);
|
|
334
|
+
const { request, calls } = harness(input);
|
|
335
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
336
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
337
|
+
});
|
|
338
|
+
for (const key of ['organizationId', 'repositoryBindingId', 'repositoryAgentId', 'deviceId', 'workspaceId', 'provider']) {
|
|
339
|
+
test('package delivery snapshots and validates the local scope field ' + key, async () => {
|
|
340
|
+
const input = fixture();
|
|
341
|
+
const { request, calls } = harness(input);
|
|
342
|
+
const scope = { ...input.scope };
|
|
343
|
+
scope[key] = key === 'provider' ? 'foreign' : String(scope[key]) + '\n';
|
|
344
|
+
request.scope = scope;
|
|
345
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
346
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
for (const key of ['policyHash', 'sourceSnapshotHash', 'sourceManifestHash', 'sourceLocalCatalogHash',
|
|
350
|
+
'catalogHash', 'manifestHash', 'transferIndexHash']) {
|
|
351
|
+
test('package delivery rejects newline-suffixed signed descriptor pin ' + key + ' before fetch', async () => {
|
|
352
|
+
await rejectBeforeFetch(input => { input.envelope.descriptor[key] += '\n'; signEnvelope(input); });
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
test('package delivery verifies both signatures using the same trusted server key', async () => {
|
|
356
|
+
const other = generateKeyPairSync('ed25519');
|
|
357
|
+
for (const which of ['parent', 'envelope', 'trusted-key']) {
|
|
358
|
+
const input = fixture();
|
|
359
|
+
if (which === 'parent') {
|
|
360
|
+
const { signature: _signature, ...unsigned } = input.bundle;
|
|
361
|
+
input.bundle.signature = signCanonicalObject(unsigned, other.privateKey);
|
|
362
|
+
}
|
|
363
|
+
else if (which === 'envelope') {
|
|
364
|
+
const { signature: _signature, ...unsigned } = input.envelope;
|
|
365
|
+
input.envelope.signature = signCanonicalObject(unsigned, other.privateKey);
|
|
366
|
+
}
|
|
367
|
+
const { request, calls } = harness(input);
|
|
368
|
+
if (which === 'trusted-key')
|
|
369
|
+
request.serverPublicKey = other.publicKey;
|
|
370
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
371
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
test('package delivery rejects an invalid injected clock before fetching', async () => {
|
|
375
|
+
const { request, calls, clock } = harness(fixture());
|
|
376
|
+
clock.milliseconds = NaN;
|
|
377
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
378
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
379
|
+
});
|
|
380
|
+
test('package delivery permits a parent without expiry and wildcard non-repository selectors', async () => {
|
|
381
|
+
const input = fixture();
|
|
382
|
+
input.bundle.expiresAt = null;
|
|
383
|
+
input.bundle.targetSelectors.deviceIds = [];
|
|
384
|
+
input.bundle.targetSelectors.workspaceIds = [];
|
|
385
|
+
input.bundle.targetSelectors.providers = [];
|
|
386
|
+
signBundle(input);
|
|
387
|
+
const { request } = harness(input);
|
|
388
|
+
assert.deepEqual((await receiveRepositoryPackageDelivery(request)).files, input.files);
|
|
389
|
+
});
|
|
390
|
+
test('package delivery snapshots envelope, parent and local scope before the first await', async () => {
|
|
391
|
+
const input = fixture();
|
|
392
|
+
const originalFiles = structuredClone(input.files);
|
|
393
|
+
const originalBundle = structuredClone(input.bundle);
|
|
394
|
+
const { request } = harness(input);
|
|
395
|
+
const pending = receiveRepositoryPackageDelivery(request);
|
|
396
|
+
input.envelope.descriptor.organizationId = 'org_mutated';
|
|
397
|
+
input.envelope.descriptor.expiresAt = CREATED;
|
|
398
|
+
input.envelope.signature = 'invalid';
|
|
399
|
+
input.bundle.targetSelectors.organizationAgentIds.length = 0;
|
|
400
|
+
input.bundle.createdAt = 'invalid';
|
|
401
|
+
input.bundle.expiresAt = CREATED;
|
|
402
|
+
input.bundle.signature = 'invalid';
|
|
403
|
+
input.scope.repositoryBindingId = 'invalid';
|
|
404
|
+
const result = await pending;
|
|
405
|
+
assert.deepEqual(result.files, originalFiles);
|
|
406
|
+
assert.deepEqual(result.bundle, originalBundle);
|
|
407
|
+
assert.doesNotThrow(() => result.assertCurrent());
|
|
408
|
+
});
|
|
409
|
+
test('package delivery currentness guard stays bound to snapshots but observes the live clock', async () => {
|
|
410
|
+
const input = fixture();
|
|
411
|
+
const { request, calls, clock } = harness(input);
|
|
412
|
+
const result = await receiveRepositoryPackageDelivery(request);
|
|
413
|
+
input.envelope.descriptor.expiresAt = '2099-01-01T00:00:00.000Z';
|
|
414
|
+
input.bundle.expiresAt = '2099-01-01T00:00:00.000Z';
|
|
415
|
+
clock.milliseconds = Date.parse(EXPIRES) - 1;
|
|
416
|
+
assert.doesNotThrow(() => result.assertCurrent());
|
|
417
|
+
clock.milliseconds++;
|
|
418
|
+
assert.throws(() => result.assertCurrent());
|
|
419
|
+
clock.milliseconds = Date.parse(CREATED) - 1;
|
|
420
|
+
assert.throws(() => result.assertCurrent());
|
|
421
|
+
clock.milliseconds = NaN;
|
|
422
|
+
assert.throws(() => result.assertCurrent());
|
|
423
|
+
assert.equal(calls.index, 1);
|
|
424
|
+
});
|
|
425
|
+
test('package delivery checks expiry after index await before requesting any chunks', async () => {
|
|
426
|
+
const input = fixture();
|
|
427
|
+
const { request, calls, clock } = harness(input);
|
|
428
|
+
request.fetchIndex = async () => { calls.index++; clock.milliseconds = Date.parse(EXPIRES); return input.plan.index; };
|
|
429
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
430
|
+
assert.deepEqual(calls, { index: 1, chunks: [] });
|
|
431
|
+
});
|
|
432
|
+
test('package delivery checks expiry before each subsequent chunk read', async () => {
|
|
433
|
+
const input = fixture();
|
|
434
|
+
const { request, calls, clock } = harness(input);
|
|
435
|
+
const original = request.fetchChunk;
|
|
436
|
+
request.fetchChunk = async (fileIndex, chunkIndex) => {
|
|
437
|
+
const chunk = await original(fileIndex, chunkIndex);
|
|
438
|
+
clock.milliseconds = Date.parse(EXPIRES);
|
|
439
|
+
return chunk;
|
|
440
|
+
};
|
|
441
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
442
|
+
assert.equal(calls.chunks.length, 1);
|
|
443
|
+
});
|
|
444
|
+
test('package delivery checks invalid time after network awaits', async () => {
|
|
445
|
+
for (const stage of ['index', 'chunk']) {
|
|
446
|
+
const input = fixture();
|
|
447
|
+
const { request, clock } = harness(input);
|
|
448
|
+
const originalIndex = request.fetchIndex;
|
|
449
|
+
const originalChunk = request.fetchChunk;
|
|
450
|
+
if (stage === 'index')
|
|
451
|
+
request.fetchIndex = async () => {
|
|
452
|
+
const value = await originalIndex();
|
|
453
|
+
clock.milliseconds = NaN;
|
|
454
|
+
return value;
|
|
455
|
+
};
|
|
456
|
+
else
|
|
457
|
+
request.fetchChunk = async (fileIndex, chunkIndex) => {
|
|
458
|
+
const value = await originalChunk(fileIndex, chunkIndex);
|
|
459
|
+
clock.milliseconds = NaN;
|
|
460
|
+
return value;
|
|
461
|
+
};
|
|
462
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
test('package delivery checks expiry after the final chunk await rather than returning stale files', async () => {
|
|
466
|
+
const input = fixture();
|
|
467
|
+
const { request, calls, clock } = harness(input);
|
|
468
|
+
const original = request.fetchChunk;
|
|
469
|
+
request.fetchChunk = async (fileIndex, chunkIndex) => {
|
|
470
|
+
const chunk = await original(fileIndex, chunkIndex);
|
|
471
|
+
if (calls.chunks.length === input.plan.chunks.length)
|
|
472
|
+
clock.milliseconds = Date.parse(EXPIRES);
|
|
473
|
+
return chunk;
|
|
474
|
+
};
|
|
475
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
476
|
+
assert.equal(calls.chunks.length, input.plan.chunks.length);
|
|
477
|
+
});
|
|
478
|
+
test('package delivery propagates current authorization read failures without returning partial files', async () => {
|
|
479
|
+
for (const stage of ['index', 'chunk']) {
|
|
480
|
+
const input = fixture();
|
|
481
|
+
const { request, calls } = harness(input);
|
|
482
|
+
const revoked = new Error('Endpoint authorization revoked.');
|
|
483
|
+
if (stage === 'index')
|
|
484
|
+
request.fetchIndex = async () => { calls.index++; throw revoked; };
|
|
485
|
+
else
|
|
486
|
+
request.fetchChunk = async (fileIndex, chunkIndex) => {
|
|
487
|
+
calls.chunks.push([fileIndex, chunkIndex]);
|
|
488
|
+
throw revoked;
|
|
489
|
+
};
|
|
490
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request), error => error === revoked);
|
|
491
|
+
assert.equal(calls.index, 1);
|
|
492
|
+
assert.equal(calls.chunks.length, stage === 'index' ? 0 : 1);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
for (const key of ['organizationId', 'repositoryBindingId', 'repositoryAgentId', 'releaseId', 'generation', 'gitCommit']) {
|
|
496
|
+
test('package delivery enforces signed transfer scope against a repinned foreign index: ' + key, async () => {
|
|
497
|
+
const input = fixture();
|
|
498
|
+
const foreign = { ...input.plan.index, [key]: key === 'generation' ? 2
|
|
499
|
+
: key === 'gitCommit' ? 'f'.repeat(40) : key === 'organizationId' ? 'org_foreign'
|
|
500
|
+
: 'e24b5a90-3b7a-4a81-9503-c9f49be790c3' };
|
|
501
|
+
input.envelope.descriptor.transferIndexHash = hash(canonicalize(foreign));
|
|
502
|
+
signEnvelope(input);
|
|
503
|
+
const { request, calls } = harness(input);
|
|
504
|
+
request.fetchIndex = async () => { calls.index++; return foreign; };
|
|
505
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
506
|
+
assert.equal(calls.chunks.length, 0);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
test('package delivery does not trust an index hash supplied by the network', async () => {
|
|
510
|
+
const input = fixture();
|
|
511
|
+
const { request, calls } = harness(input);
|
|
512
|
+
const changed = structuredClone(input.plan.index);
|
|
513
|
+
changed.files[0].path = ROOT + 'renamed.md';
|
|
514
|
+
request.fetchIndex = async () => { calls.index++; return changed; };
|
|
515
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
516
|
+
assert.equal(calls.chunks.length, 0);
|
|
517
|
+
});
|
|
518
|
+
test('package delivery rejects transfer schema downgrade and signed inventory count mismatches', async () => {
|
|
519
|
+
for (const key of ['fileCount', 'totalBytes', 'schema']) {
|
|
520
|
+
const input = fixture();
|
|
521
|
+
const { request, calls } = harness(input);
|
|
522
|
+
if (key === 'schema') {
|
|
523
|
+
const index = { ...input.plan.index, schema: 'dharma.repository-package-transfer/v1' };
|
|
524
|
+
input.envelope.descriptor.transferIndexHash = hash(canonicalize(index));
|
|
525
|
+
signEnvelope(input);
|
|
526
|
+
request.fetchIndex = async () => { calls.index++; return index; };
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
const countKey = key;
|
|
530
|
+
input.envelope.descriptor[countKey]++;
|
|
531
|
+
signEnvelope(input);
|
|
532
|
+
}
|
|
533
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
534
|
+
assert.equal(calls.chunks.length, 0);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
test('package delivery rejects chunk corruption, foreign pins and cross-schema chunks', async () => {
|
|
538
|
+
for (const change of [
|
|
539
|
+
{ contentBase64: Buffer.from('corrupt').toString('base64') },
|
|
540
|
+
{ indexHash: pin('f') }, { schema: 'dharma.repository-package-chunk/v1' },
|
|
541
|
+
]) {
|
|
542
|
+
const input = fixture();
|
|
543
|
+
const { request } = harness(input);
|
|
544
|
+
const original = request.fetchChunk;
|
|
545
|
+
request.fetchChunk = async (fileIndex, chunkIndex) => {
|
|
546
|
+
const chunk = await original(fileIndex, chunkIndex);
|
|
547
|
+
return { ...chunk, ...change };
|
|
548
|
+
};
|
|
549
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
for (const key of ['policyHash', 'sourceSnapshotHash', 'sourceManifestHash', 'sourceLocalCatalogHash',
|
|
553
|
+
'catalogHash', 'manifestHash']) {
|
|
554
|
+
test('package delivery enforces whole metadata lineage from the trusted descriptor: ' + key, async () => {
|
|
555
|
+
const input = fixture();
|
|
556
|
+
input.envelope.descriptor[key] = pin('f');
|
|
557
|
+
signEnvelope(input);
|
|
558
|
+
const { request } = harness(input);
|
|
559
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
test('package delivery validates complete metadata even when transfer hashes and signatures are consistent', async () => {
|
|
563
|
+
const input = fixture();
|
|
564
|
+
const manifest = readDocument(input, MANIFEST);
|
|
565
|
+
manifest.sourceSkills = [];
|
|
566
|
+
replaceDocument(input, MANIFEST, manifest);
|
|
567
|
+
repackage(input);
|
|
568
|
+
const { request } = harness(input);
|
|
569
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
570
|
+
});
|
|
571
|
+
test('package delivery rejects noncanonical duplicate-key metadata despite trusted byte pins', async () => {
|
|
572
|
+
const input = fixture();
|
|
573
|
+
const catalog = readDocument(input, CATALOG);
|
|
574
|
+
const duplicate = canonicalize(catalog).replace('{', '{"generation":1,') + '\n';
|
|
575
|
+
replaceDocument(input, CATALOG, catalog, duplicate);
|
|
576
|
+
repackage(input);
|
|
577
|
+
const { request } = harness(input);
|
|
578
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
579
|
+
});
|
|
580
|
+
test('package delivery rejects missing canonical package files rather than returning a partial package', async () => {
|
|
581
|
+
const input = fixture();
|
|
582
|
+
input.files = input.files.filter(row => row.path !== PROMPT);
|
|
583
|
+
const manifest = readDocument(input, MANIFEST);
|
|
584
|
+
manifest.files = manifest.files.filter(row => row.path !== PROMPT);
|
|
585
|
+
replaceDocument(input, MANIFEST, manifest);
|
|
586
|
+
repackage(input);
|
|
587
|
+
const { request } = harness(input);
|
|
588
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
589
|
+
});
|
|
590
|
+
test('package delivery does not replace the existing installer treehash check with metadata integrity', async () => {
|
|
591
|
+
const input = fixture();
|
|
592
|
+
input.bundle.skills[0].contentHash = pin('f');
|
|
593
|
+
signBundle(input);
|
|
594
|
+
const { request } = harness(input);
|
|
595
|
+
// This byte receiver leaves the independently signed skill treehash to the
|
|
596
|
+
// unchanged installer. Metadata success is not installation success.
|
|
597
|
+
const result = await receiveRepositoryPackageDelivery(request);
|
|
598
|
+
assert.deepEqual(result.files, input.files);
|
|
599
|
+
assert.notEqual(treeHash(result.files), input.bundle.skills[0].contentHash);
|
|
600
|
+
});
|
|
601
|
+
test('package delivery rejects scope accessors and proxies without invoking hooks', async () => {
|
|
602
|
+
let calls = 0;
|
|
603
|
+
const input = fixture();
|
|
604
|
+
const accessor = { ...input.scope };
|
|
605
|
+
Object.defineProperty(accessor, 'repositoryBindingId', {
|
|
606
|
+
enumerable: true, get() { calls++; return input.scope.repositoryBindingId; },
|
|
607
|
+
});
|
|
608
|
+
const proxy = new Proxy(input.scope, {
|
|
609
|
+
ownKeys() { calls++; return Reflect.ownKeys(input.scope); },
|
|
610
|
+
get() { calls++; throw new Error('Proxy hook executed.'); },
|
|
611
|
+
});
|
|
612
|
+
for (const scope of [accessor, proxy]) {
|
|
613
|
+
const { request, calls: network } = harness(input);
|
|
614
|
+
request.scope = scope;
|
|
615
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
616
|
+
assert.deepEqual(network, { index: 0, chunks: [] });
|
|
617
|
+
}
|
|
618
|
+
assert.equal(calls, 0);
|
|
619
|
+
});
|
|
620
|
+
test('package delivery rejects envelope and parent accessors or proxies before asynchronous validation', async () => {
|
|
621
|
+
let calls = 0;
|
|
622
|
+
const input = fixture();
|
|
623
|
+
const envelope = { ...input.envelope };
|
|
624
|
+
Object.defineProperty(envelope, 'descriptor', { enumerable: true,
|
|
625
|
+
get() { calls++; return input.envelope.descriptor; } });
|
|
626
|
+
const bundle = { ...input.bundle };
|
|
627
|
+
Object.defineProperty(bundle, 'skills', { enumerable: true, get() { calls++; return input.bundle.skills; } });
|
|
628
|
+
const proxy = new Proxy(input.envelope, {
|
|
629
|
+
ownKeys() { calls++; return Reflect.ownKeys(input.envelope); },
|
|
630
|
+
});
|
|
631
|
+
for (const which of ['envelope', 'bundle', 'proxy']) {
|
|
632
|
+
const { request, calls: network } = harness(input);
|
|
633
|
+
if (which === 'bundle')
|
|
634
|
+
request.bundle = bundle;
|
|
635
|
+
else
|
|
636
|
+
request.envelope = which === 'proxy' ? proxy : envelope;
|
|
637
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
638
|
+
assert.deepEqual(network, { index: 0, chunks: [] });
|
|
639
|
+
}
|
|
640
|
+
assert.equal(calls, 0);
|
|
641
|
+
});
|
|
642
|
+
test('package delivery bounds local scope and descriptor JSON snapshots before network reads', async () => {
|
|
643
|
+
const input = fixture();
|
|
644
|
+
for (const which of ['scope', 'descriptor']) {
|
|
645
|
+
const { request, calls } = harness(input);
|
|
646
|
+
const padding = Array.from({ length: 64 }, () => 'x'.repeat(349_529));
|
|
647
|
+
if (which === 'scope')
|
|
648
|
+
request.scope = { ...input.scope, padding };
|
|
649
|
+
else
|
|
650
|
+
request.envelope = { ...input.envelope, descriptor: { ...input.envelope.descriptor, padding } };
|
|
651
|
+
await assert.rejects(receiveRepositoryPackageDelivery(request));
|
|
652
|
+
assert.deepEqual(calls, { index: 0, chunks: [] });
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
function currentFilesystemFixture() {
|
|
656
|
+
const input = fixture();
|
|
657
|
+
const milliseconds = Date.now();
|
|
658
|
+
const createdAt = new Date(milliseconds - 1000).toISOString();
|
|
659
|
+
const expiresAt = new Date(milliseconds + 120_000).toISOString();
|
|
660
|
+
input.bundle.createdAt = createdAt;
|
|
661
|
+
input.bundle.expiresAt = expiresAt;
|
|
662
|
+
input.envelope.descriptor.createdAt = createdAt;
|
|
663
|
+
input.envelope.descriptor.expiresAt = expiresAt;
|
|
664
|
+
signBundle(input);
|
|
665
|
+
return input;
|
|
666
|
+
}
|
|
667
|
+
const filesystemPolicy = {
|
|
668
|
+
schema: 'dharma.organization-policy/v1', organizationId: localScope.organizationId, revision: '1',
|
|
669
|
+
evidence: {
|
|
670
|
+
defaultMode: 'structured', registeredWorkspaceOnly: true, excludePaths: [],
|
|
671
|
+
maximumCapsuleBytes: 1, maximumDailyUploadBytes: 1, maximumExpansionBytes: 1,
|
|
672
|
+
},
|
|
673
|
+
tasks: {
|
|
674
|
+
defaultNetwork: 'deny', defaultGit: 'read_only', allowedCommands: {},
|
|
675
|
+
writePaths: [], requireLocalConfirmationFor: [],
|
|
676
|
+
},
|
|
677
|
+
skills: { automaticInstall: true, automaticPromotionMaxRisk: 'R0', canaryPercent: 100 },
|
|
678
|
+
retention: {}, budgets: {},
|
|
679
|
+
};
|
|
680
|
+
async function materializeReceived(delivery, source) {
|
|
681
|
+
for (const row of delivery.files) {
|
|
682
|
+
const target = resolve(source, row.path);
|
|
683
|
+
const route = relative(source, target);
|
|
684
|
+
assert.ok(route && !isAbsolute(route) && route.split(sep)[0] !== '..');
|
|
685
|
+
delivery.assertCurrent();
|
|
686
|
+
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
|
|
687
|
+
delivery.assertCurrent();
|
|
688
|
+
await writeFile(target, Buffer.from(row.contentBase64, 'base64'), { flag: 'wx', mode: 0o600 });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
test('filesystem integration: delivered five-file package installs through the public v2 installer and active authorization', async () => {
|
|
692
|
+
const input = currentFilesystemFixture();
|
|
693
|
+
const { request } = harness(input);
|
|
694
|
+
request.now = () => new Date();
|
|
695
|
+
const delivery = await receiveRepositoryPackageDelivery(request);
|
|
696
|
+
assert.equal(delivery.files.length, 5);
|
|
697
|
+
const owned = await mkdtemp(resolve(tmpdir(), 'fabric-package-delivery-integration-'));
|
|
698
|
+
try {
|
|
699
|
+
const source = resolve(owned, 'source');
|
|
700
|
+
const native = resolve(owned, 'native');
|
|
701
|
+
const device = generateKeyPairSync('ed25519');
|
|
702
|
+
await materializeReceived(delivery, source);
|
|
703
|
+
assert.equal(await contentHash(resolve(source, ROOT)), delivery.bundle.skills[0].contentHash);
|
|
704
|
+
delivery.assertCurrent();
|
|
705
|
+
const receipt = await installSkillBundle({
|
|
706
|
+
bundle: delivery.bundle, sourceDirectory: source, nativeSkillDirectory: native,
|
|
707
|
+
policy: filesystemPolicy, serverPublicKey: keys.publicKey, devicePrivateKey: device.privateKey,
|
|
708
|
+
deviceId: input.scope.deviceId, organizationAgentId: input.scope.repositoryAgentId,
|
|
709
|
+
workspaceId: input.scope.workspaceId, provider: input.scope.provider,
|
|
710
|
+
});
|
|
711
|
+
assert.equal(receipt.status, 'active');
|
|
712
|
+
assert.ok(receipt.checks.some(check => check.name === 'content:dharma-agent-fabric' && check.status === 'pass'));
|
|
713
|
+
for (const row of delivery.files.filter(row => row.path.startsWith(ROOT))) {
|
|
714
|
+
const copied = resolve(native, 'dharma-agent-fabric', row.path.slice(ROOT.length));
|
|
715
|
+
assert.equal(hash(await readFile(copied)), row.sha256, row.path);
|
|
716
|
+
}
|
|
717
|
+
assert.equal(hash(await readFile(resolve(source, PROMPT))), delivery.files.find(row => row.path === PROMPT).sha256);
|
|
718
|
+
assert.equal(await contentHash(resolve(native, '.dharma-managed', 'workspaces', input.scope.workspaceId, 'releases', delivery.bundle.bundleId, 'dharma-agent-fabric')), delivery.bundle.skills[0].contentHash);
|
|
719
|
+
const ownership = JSON.parse(await readFile(resolve(native, 'dharma-agent-fabric', '.dharma-agent-fabric.json'), 'utf8'));
|
|
720
|
+
assert.equal(ownership.bundleId, delivery.bundle.bundleId);
|
|
721
|
+
assert.equal(ownership.workspaceId, input.scope.workspaceId);
|
|
722
|
+
const authorization = await getActiveSkillBundleAuthorization({
|
|
723
|
+
nativeSkillDirectory: native, workspaceId: input.scope.workspaceId, provider: input.scope.provider,
|
|
724
|
+
organizationId: input.scope.organizationId, organizationAgentId: input.scope.repositoryAgentId,
|
|
725
|
+
deviceId: input.scope.deviceId, serverPublicKey: keys.publicKey, devicePublicKey: device.publicKey,
|
|
726
|
+
expectedReceiptHash: receipt.receiptHash,
|
|
727
|
+
});
|
|
728
|
+
assert.equal(authorization?.bundleId, delivery.bundle.bundleId);
|
|
729
|
+
assert.equal(authorization?.bundleHash, delivery.bundle.bundleHash);
|
|
730
|
+
}
|
|
731
|
+
finally {
|
|
732
|
+
await rm(owned, { recursive: true, force: true });
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
test('filesystem integration: tampering after delivery fails the installer treehash before replacing the prior accepted package', async () => {
|
|
736
|
+
const input = currentFilesystemFixture();
|
|
737
|
+
const { request } = harness(input);
|
|
738
|
+
request.now = () => new Date();
|
|
739
|
+
const prior = await receiveRepositoryPackageDelivery(request);
|
|
740
|
+
const owned = await mkdtemp(resolve(tmpdir(), 'fabric-package-delivery-tamper-'));
|
|
741
|
+
try {
|
|
742
|
+
const source = resolve(owned, 'prior-source');
|
|
743
|
+
const native = resolve(owned, 'native');
|
|
744
|
+
const device = generateKeyPairSync('ed25519');
|
|
745
|
+
const endpoint = {
|
|
746
|
+
deviceId: input.scope.deviceId, organizationAgentId: input.scope.repositoryAgentId,
|
|
747
|
+
workspaceId: input.scope.workspaceId, provider: input.scope.provider,
|
|
748
|
+
};
|
|
749
|
+
await materializeReceived(prior, source);
|
|
750
|
+
prior.assertCurrent();
|
|
751
|
+
const receipt = await installSkillBundle({
|
|
752
|
+
bundle: prior.bundle, sourceDirectory: source, nativeSkillDirectory: native,
|
|
753
|
+
policy: filesystemPolicy, serverPublicKey: keys.publicKey, devicePrivateKey: device.privateKey, ...endpoint,
|
|
754
|
+
});
|
|
755
|
+
assert.equal(receipt.status, 'active');
|
|
756
|
+
const candidateInput = currentFilesystemFixture();
|
|
757
|
+
candidateInput.bundle.bundleId = 'e24b5a90-3b7a-4a81-9503-c9f49be790c3';
|
|
758
|
+
candidateInput.bundle.rollbackBundleId = prior.bundle.bundleId;
|
|
759
|
+
signBundle(candidateInput);
|
|
760
|
+
const candidateHarness = harness(candidateInput);
|
|
761
|
+
candidateHarness.request.now = () => new Date();
|
|
762
|
+
const candidate = await receiveRepositoryPackageDelivery(candidateHarness.request);
|
|
763
|
+
const candidateSource = resolve(owned, 'candidate-source');
|
|
764
|
+
await materializeReceived(candidate, candidateSource);
|
|
765
|
+
candidate.assertCurrent();
|
|
766
|
+
await writeFile(resolve(candidateSource, ROOT, 'SKILL.md'), '# Tampered after verified delivery\n', { mode: 0o600 });
|
|
767
|
+
candidate.assertCurrent();
|
|
768
|
+
await assert.rejects(installSkillBundle({
|
|
769
|
+
bundle: candidate.bundle, sourceDirectory: candidateSource, nativeSkillDirectory: native,
|
|
770
|
+
policy: filesystemPolicy, serverPublicKey: keys.publicKey, devicePrivateKey: device.privateKey, ...endpoint,
|
|
771
|
+
}), /Skill content hash mismatch: dharma-agent-fabric/);
|
|
772
|
+
for (const row of prior.files.filter(row => row.path.startsWith(ROOT))) {
|
|
773
|
+
assert.equal(hash(await readFile(resolve(native, 'dharma-agent-fabric', row.path.slice(ROOT.length)))), row.sha256, row.path);
|
|
774
|
+
}
|
|
775
|
+
const authorization = await getActiveSkillBundleAuthorization({
|
|
776
|
+
nativeSkillDirectory: native, organizationId: input.scope.organizationId, ...endpoint,
|
|
777
|
+
serverPublicKey: keys.publicKey, devicePublicKey: device.publicKey,
|
|
778
|
+
expectedReceiptHash: receipt.receiptHash,
|
|
779
|
+
});
|
|
780
|
+
assert.equal(authorization?.bundleId, prior.bundle.bundleId);
|
|
781
|
+
assert.equal(authorization?.bundleHash, prior.bundle.bundleHash);
|
|
782
|
+
}
|
|
783
|
+
finally {
|
|
784
|
+
await rm(owned, { recursive: true, force: true });
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
//# sourceMappingURL=repositoryPackageDelivery.test.js.map
|