@aopslabs/aops 0.3.30 → 0.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +11 -3
  2. package/aops-assets-release/aops-assets.json.gz +0 -0
  3. package/aops-assets-release/disciplines/build-review-chat/SKILL.md +33 -0
  4. package/aops-assets-release/disciplines/coordinator-loop/SKILL.md +33 -0
  5. package/aops-assets-release/disciplines/design-first-consensus/SKILL.md +30 -0
  6. package/aops-assets-release/disciplines/solo-pm-loop/SKILL.md +31 -0
  7. package/aops-assets-release/docs/user-guides/agentspace-user-guide.md +384 -0
  8. package/aops-assets-release/docs/user-guides/aops-cli-user-guide.md +2067 -0
  9. package/aops-assets-release/docs/user-guides/chatv3-user-guide.md +500 -0
  10. package/aops-assets-release/docs/user-guides/docman-user-guide.md +1009 -0
  11. package/aops-assets-release/docs/user-guides/projectman-user-guide.md +763 -0
  12. package/aops-assets-release/docs/user-guides/tasker-user-guide.md +244 -0
  13. package/aops-assets-release/release.json +6 -0
  14. package/aops-assets-release/roles/coordinator/ROLE.md +28 -0
  15. package/aops-assets-release/roles/implementer/ROLE.md +27 -0
  16. package/aops-assets-release/roles/reviewer/ROLE.md +27 -0
  17. package/aops-assets-release/skills/aops/SKILL.md +112 -0
  18. package/aops-assets-release/skills/aops-bootstrapper-authoring/SKILL.md +245 -0
  19. package/aops-assets-release/skills/aops-cli-agentspace/SKILL.md +186 -0
  20. package/aops-assets-release/skills/aops-cli-board-lifecycle/SKILL.md +64 -0
  21. package/aops-assets-release/skills/aops-cli-chat/SKILL.md +204 -0
  22. package/aops-assets-release/skills/aops-cli-collab/SKILL.md +42 -0
  23. package/aops-assets-release/skills/aops-cli-core/SKILL.md +219 -0
  24. package/aops-assets-release/skills/aops-cli-discuss/SKILL.md +172 -0
  25. package/aops-assets-release/skills/aops-cli-docman/SKILL.md +192 -0
  26. package/aops-assets-release/skills/aops-cli-fileman/SKILL.md +169 -0
  27. package/aops-assets-release/skills/aops-cli-mission/SKILL.md +128 -0
  28. package/aops-assets-release/skills/aops-cli-operator-brief/SKILL.md +67 -0
  29. package/aops-assets-release/skills/aops-cli-projectman/SKILL.md +235 -0
  30. package/aops-assets-release/skills/aops-cli-sugar-authoring/SKILL.md +188 -0
  31. package/aops-assets-release/skills/aops-cli-tasker/SKILL.md +108 -0
  32. package/aops-assets-release/skills/aops-cli-tooling-agent/SKILL.md +112 -0
  33. package/aops-assets-release/skills/aops-cli-view/SKILL.md +210 -0
  34. package/aops-assets-release/skills/aops-collaborative-work/SKILL.md +304 -0
  35. package/aops-assets-release/skills/aops-interactive/SKILL.md +70 -0
  36. package/aops-assets-release/skills/aops-loop-interactive/SKILL.md +314 -0
  37. package/aops-assets-release/skills/aops-working-disciplines/SKILL.md +310 -0
  38. package/aops-assets-release/skills/feature-retirement-flow/SKILL.md +126 -0
  39. package/assets/skills/aops-install/SKILL.md +18 -17
  40. package/dist/commands/assets.js +93 -1264
  41. package/dist/commands/community-server.js +27 -8
  42. package/dist/commands/community-setup.js +1 -2
  43. package/dist/commands/discuss.js +1 -1
  44. package/dist/commands/doc.js +88 -27
  45. package/dist/commands/docs.js +68 -0
  46. package/dist/commands/global-update.js +28 -0
  47. package/dist/commands/init.js +3 -1
  48. package/dist/commands/loop.js +6379 -0
  49. package/dist/commands/memory.js +2 -1
  50. package/dist/commands/pm/index.js +11 -9
  51. package/dist/commands/pm/projectman.js +33 -2
  52. package/dist/commands/project.js +6 -6
  53. package/dist/commands/prompt.js +1 -1
  54. package/dist/commands/repo-sync.js +92 -27
  55. package/dist/commands/runner.js +257 -0
  56. package/dist/commands/skill.js +1 -1
  57. package/dist/commands/start.js +1 -1
  58. package/dist/commands/tasker.js +361 -0
  59. package/dist/commands/view.js +53 -9
  60. package/dist/lib/commercial-setup-readiness-adapter.js +0 -1
  61. package/dist/lib/community-migration-snapshot.js +7 -9
  62. package/dist/lib/community-native-database-recovery.js +5 -14
  63. package/dist/lib/community-native-lifecycle.js +13 -0
  64. package/dist/lib/setup-agent-assets-bridge.js +28 -148
  65. package/dist/lib/setup-agent-assets-release.js +16 -44
  66. package/dist/lib/setup-init-orchestrator.js +11 -15
  67. package/dist/lib/simple-agent-assets.js +447 -0
  68. package/dist/lib/user-guide-sections.js +488 -0
  69. package/dist/main.js +10 -2
  70. package/dist/seeds/agents-md.js +3 -3
  71. package/dist/utils/agents-md.js +1 -1
  72. package/dist/utils/guide-paths.js +7 -7
  73. package/dist/utils/hosted-workspace.js +2 -1
  74. package/dist/utils/repo-first-storage.js +26 -1
  75. package/dist/utils/session-state.js +2 -7
  76. package/launchers/aops-cockpit.sh +0 -0
  77. package/launchers/aops-server.sh +0 -0
  78. package/package.json +67 -35
  79. package/THIRD_PARTY_NOTICES +0 -16169
  80. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/SKILL.md +0 -47
  81. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/agentspace/SKILL.md +0 -72
  82. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/aops-cli-core/SKILL.md +0 -110
  83. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/chatv3/SKILL.md +0 -59
  84. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/collaborative-work/SKILL.md +0 -81
  85. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/discuss/SKILL.md +0 -66
  86. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/docman/SKILL.md +0 -75
  87. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/projectman/SKILL.md +0 -92
  88. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/sys/SKILL.md +0 -36
  89. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/view/SKILL.md +0 -58
  90. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/references/working-disciplines/SKILL.md +0 -66
  91. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/agent-assets.md +0 -81
  92. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/agentspace.md +0 -183
  93. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/aops-cli.md +0 -1108
  94. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/aops-system.md +0 -102
  95. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/chatv3.md +0 -113
  96. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/docman.md +0 -223
  97. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/projectman.md +0 -233
  98. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/sys.md +0 -94
  99. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/files/user-guides/working-disciplines.md +0 -482
  100. package/agent-assets-release/agent-assets/core/bc42dd7792dbe66040e99a7ef3d80b3bc788c72b369b5a065cf936aef29f53fb/manifest.json +0 -117
  101. package/agent-assets-release/agent-assets/gateway/aops/SKILL.md +0 -14
  102. package/agent-assets-release/agent-assets/inventory.json +0 -29
  103. package/agent-assets-release/agent-assets/projection.json +0 -8
  104. package/agent-assets-release/agent-assets-release.json +0 -171
  105. package/agent-assets-release/agent-assets-release.sigstore.json +0 -67
  106. package/assets/agent-assets/core/SKILL.md +0 -47
  107. package/assets/agent-assets/core/references/agentspace/SKILL.md +0 -72
  108. package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +0 -110
  109. package/assets/agent-assets/core/references/chatv3/SKILL.md +0 -59
  110. package/assets/agent-assets/core/references/collaborative-work/SKILL.md +0 -81
  111. package/assets/agent-assets/core/references/discuss/SKILL.md +0 -66
  112. package/assets/agent-assets/core/references/docman/SKILL.md +0 -75
  113. package/assets/agent-assets/core/references/projectman/SKILL.md +0 -92
  114. package/assets/agent-assets/core/references/sys/SKILL.md +0 -36
  115. package/assets/agent-assets/core/references/view/SKILL.md +0 -58
  116. package/assets/agent-assets/core/references/working-disciplines/SKILL.md +0 -66
  117. package/assets/agent-assets/core/user-guides/agent-assets.md +0 -81
  118. package/assets/agent-assets/core/user-guides/agentspace.md +0 -183
  119. package/assets/agent-assets/core/user-guides/aops-cli.md +0 -1108
  120. package/assets/agent-assets/core/user-guides/aops-system.md +0 -102
  121. package/assets/agent-assets/core/user-guides/chatv3.md +0 -113
  122. package/assets/agent-assets/core/user-guides/docman.md +0 -223
  123. package/assets/agent-assets/core/user-guides/projectman.md +0 -233
  124. package/assets/agent-assets/core/user-guides/sys.md +0 -94
  125. package/assets/agent-assets/core/user-guides/working-disciplines.md +0 -482
  126. package/assets/agent-assets/gateway/aops/SKILL.md +0 -14
  127. package/dist/lib/agent-assets/gateway.js +0 -15
  128. package/dist/lib/agent-assets/guards.js +0 -23
  129. package/dist/lib/agent-assets/hosted-discovery.js +0 -148
  130. package/dist/lib/agent-assets/hosted-package-input.js +0 -154
  131. package/dist/lib/agent-assets/legacy-pointer-migration.js +0 -677
  132. package/dist/lib/agent-assets/native-fs.js +0 -589
  133. package/dist/lib/agent-assets/roots.js +0 -45
  134. package/dist/lib/agent-assets/runtime-binding-reader.js +0 -545
  135. package/dist/lib/agent-assets/runtime-targets.js +0 -50
  136. package/dist/lib/agent-assets/store-reader.js +0 -1212
  137. package/dist/lib/agent-assets/store-writer.js +0 -1484
  138. package/native/bin/darwin-arm64/aops-agent-assets-fs +0 -0
  139. package/native/bin/darwin-x64/aops-agent-assets-fs +0 -0
  140. package/native/bin/linux-arm64/aops-agent-assets-fs +0 -0
  141. package/native/bin/linux-x64/aops-agent-assets-fs +0 -0
  142. package/native/bin/win32-x64/aops-agent-assets-fs.exe +0 -0
  143. package/native/manifest.json +0 -49
  144. package/native/qualifications/win32-x64.json +0 -23
  145. package/native/tui/darwin-arm64/aops-tui +0 -0
  146. package/native/tui/linux-x64/aops-tui +0 -0
  147. package/native/tui/win32-x64/aops-tui.exe +0 -0
@@ -1,1484 +0,0 @@
1
- import { createHash, randomUUID } from 'node:crypto';
2
- import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
3
- import path from 'node:path';
4
- import { compareCanonicalText } from '@aopslabs/artifact-trust-contracts';
5
- import { AgentAssetsError } from './envelope.js';
6
- import { openAgentAssetsNativePublicationSession, } from './native-fs.js';
7
- import { agentAssetsRuntimeHomeId, inspectRuntimeGatewayBinding, parseRuntimeBindingV1, } from './runtime-binding-reader.js';
8
- import { AOPS_AGENT_ASSETS_GATEWAY, AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH, AOPS_AGENT_ASSETS_GATEWAY_SHA256, } from './gateway.js';
9
- import { validatePortablePackageV1 } from './package-manifest.js';
10
- import { canonicalJsonSha256V1, canonicalJsonV1, parseExactVersionPinV1, parseMaintenanceReceiptV1, readAgentAssetsRollbackTarget, readAgentAssetsMaintenanceHead, readAgentAssetsPrunePlan, readAgentAssetsStagingCleanupPlan, readResolvedAgentAssetPackage, readAgentAssetsStoreStatus, readAgentAssetsStoreSnapshot, verifyAgentAssetsPackageAtRoot, } from './store-reader.js';
11
- const MAX_MANAGED_READ_BYTES = 1024 * 1024;
12
- const MAX_MANAGED_DIRECTORY_ENTRIES = 10_000;
13
- const SHA256_HEX = /^[a-f0-9]{64}$/;
14
- function writerError(code, message, details) {
15
- return new AgentAssetsError(code, message, {
16
- nextActions: ['Run `aops assets status --verify full --json` before retrying the mutation.'],
17
- ...(details === undefined ? {} : { details }),
18
- });
19
- }
20
- function sha256(bytes) {
21
- return createHash('sha256').update(bytes).digest('hex');
22
- }
23
- function jsonBytes(value) {
24
- return Buffer.from(canonicalJsonV1(value), 'utf8');
25
- }
26
- function publicationCapability(capability) {
27
- switch (capability.capabilityClass) {
28
- case 'linux-posix-durable-v1': return 'posix-durable-v1';
29
- case 'macos-exclusive-durable-v1': return 'macos-durable-v1';
30
- case 'windows-ntfs-crash-recoverable-v1': return 'windows-ntfs-crash-recoverable-v1';
31
- }
32
- }
33
- function operationIdentity(options) {
34
- if (!options.idempotencyKey?.trim())
35
- return (options.randomId ?? randomUUID)();
36
- const keyIdentity = operationKeyIdentity(options);
37
- const runtimeIntent = Object.entries(options.runtimeHomes ?? {})
38
- .sort(([left], [right]) => compareCanonicalText(left, right))
39
- .map(([runtime, runtimeHome]) => `${runtime}:${sha256(path.resolve(runtimeHome).toLowerCase())}`)
40
- .join(',');
41
- const intentIdentity = sha256(`aops-agent-assets-core-intent-v1\0${options.requestedOperation}\0${options.release.packageRef.packageSha256}\0${runtimeIntent}`).slice(0, 40);
42
- return `${keyIdentity}.${intentIdentity}`;
43
- }
44
- function operationKeyIdentity(options) {
45
- return options.idempotencyKey?.trim()
46
- ? sha256(`aops-agent-assets-core-key-v1\0${options.idempotencyKey.trim()}`).slice(0, 40)
47
- : null;
48
- }
49
- function nearestExistingAnchor(assetRoot) {
50
- let cursor = path.resolve(assetRoot);
51
- while (!existsSync(cursor)) {
52
- const parent = path.dirname(cursor);
53
- if (parent === cursor)
54
- throw writerError('atomic_primitive_unavailable', 'No trusted bootstrap anchor exists.');
55
- cursor = parent;
56
- }
57
- const stat = lstatSync(cursor);
58
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
59
- throw writerError('link_unsafe_path', 'The nearest bootstrap anchor is not a real directory.');
60
- }
61
- return realpathSync.native(cursor);
62
- }
63
- function readManagedBytes(assetRoot, relativePath) {
64
- const root = path.resolve(assetRoot);
65
- const rootStat = lstatSync(root);
66
- if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
67
- throw writerError('link_unsafe_path', 'The opened agent-assets root is unsafe.');
68
- }
69
- const realRoot = realpathSync.native(root);
70
- const segments = relativePath.split('/');
71
- if (segments.some((segment) => !segment || segment === '.' || segment === '..' || segment.includes('\\'))) {
72
- throw writerError('invalid_package_path', 'Managed state read contains an unsafe path segment.');
73
- }
74
- let cursor = root;
75
- for (const segment of segments) {
76
- cursor = path.join(cursor, segment);
77
- const stat = lstatSync(cursor);
78
- if (stat.isSymbolicLink())
79
- throw writerError('link_unsafe_path', 'Managed state read traverses a link.');
80
- }
81
- const stat = lstatSync(cursor);
82
- if (!stat.isFile() || stat.size < 1 || stat.size > MAX_MANAGED_READ_BYTES) {
83
- throw writerError('schema_incompatible', 'Managed state read has an invalid file type or size.');
84
- }
85
- const realFile = realpathSync.native(cursor);
86
- const relative = path.relative(realRoot, realFile);
87
- if (relative.startsWith('..') || path.isAbsolute(relative)) {
88
- throw writerError('link_unsafe_path', 'Managed state read escaped the store root.');
89
- }
90
- return readFileSync(cursor);
91
- }
92
- async function publishNoReplaceOrVerify(session, assetRoot, relativePath, content) {
93
- try {
94
- await session.publishFileNoReplace(relativePath, content);
95
- }
96
- catch (error) {
97
- if (!(error instanceof AgentAssetsError) || error.code !== 'publication_conflict')
98
- throw error;
99
- const existing = readManagedBytes(assetRoot, relativePath);
100
- if (!Buffer.from(existing).equals(Buffer.from(content)))
101
- throw error;
102
- }
103
- }
104
- async function ensureStoreDirectories(session, assetRoot) {
105
- for (const relativePath of [
106
- 'core',
107
- 'staging',
108
- 'state',
109
- 'state/authorities',
110
- 'state/receipts',
111
- 'state/maintenance-receipts',
112
- 'state/pins',
113
- 'state/bindings',
114
- 'state/bindings/receipts',
115
- ]) {
116
- await ensureNativeDirectory(assetRoot, relativePath, (next) => session.createDirectory(next));
117
- }
118
- }
119
- async function ensureNativeDirectory(root, relativePath, create) {
120
- let cursor = path.resolve(root);
121
- let current = '';
122
- for (const segment of relativePath.split('/')) {
123
- current = current ? `${current}/${segment}` : segment;
124
- cursor = path.join(cursor, segment);
125
- if (!existsSync(cursor)) {
126
- await create(current);
127
- continue;
128
- }
129
- const stat = lstatSync(cursor);
130
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
131
- throw writerError('link_unsafe_path', 'Managed directory path is not a plain directory.', {
132
- relativePath: current,
133
- });
134
- }
135
- }
136
- }
137
- async function prepareRuntimeBindings(session, assetRoot, runtimeHomes, options = {}) {
138
- const prepared = [];
139
- for (const runtime of ['codex', 'claude']) {
140
- const selected = runtimeHomes?.[runtime];
141
- if (!selected)
142
- continue;
143
- const runtimeHome = path.resolve(selected);
144
- const nativeRoot = await session.registerRuntimeRoot(runtimeHome);
145
- const inspection = inspectRuntimeGatewayBinding({
146
- assetRoot,
147
- runtime,
148
- runtimeHome,
149
- expectedRuntimeRootIdentitySha256: nativeRoot.rootIdentitySha256,
150
- });
151
- if (inspection.state === 'ownership-conflict' || inspection.state === 'unsafe-path') {
152
- throw writerError(inspection.state === 'unsafe-path' ? 'link_unsafe_path' : 'publication_conflict', `${runtime} gateway ownership could not be established safely.`, { runtime, state: inspection.state, reasons: inspection.reasons });
153
- }
154
- const interruptedPublication = inspection.bindingProof === 'verified'
155
- && inspection.gateway === 'absent'
156
- && (inspection.ownerMarkerProof === 'absent' || inspection.ownerMarkerProof === 'verified');
157
- const explicitManagedRepair = options.repairManagedContent === true
158
- && inspection.bindingProof === 'verified'
159
- && ((inspection.ownerMarkerProof === 'verified'
160
- && (inspection.gateway === 'absent' || inspection.gateway === 'tampered'))
161
- || (inspection.ownerMarkerProof === 'absent'
162
- && (inspection.gateway === 'absent' || inspection.gateway === 'canonical')));
163
- if (inspection.state === 'managed-drift' && !interruptedPublication && !explicitManagedRepair) {
164
- throw writerError('schema_incompatible', `${runtime} gateway has drift that install/update cannot adopt.`, {
165
- runtime,
166
- state: inspection.state,
167
- reasons: inspection.reasons,
168
- });
169
- }
170
- prepared.push(Object.freeze({ runtime, runtimeHome, nativeRoot, inspection }));
171
- }
172
- return Object.freeze(prepared);
173
- }
174
- function readCurrentRuntimeBinding(assetRoot, runtime) {
175
- const bytes = readManagedBytes(assetRoot, `state/bindings/${runtime}.json`);
176
- try {
177
- return parseRuntimeBindingV1(JSON.parse(Buffer.from(bytes).toString('utf8')));
178
- }
179
- catch (error) {
180
- if (error instanceof AgentAssetsError)
181
- throw error;
182
- throw writerError('schema_incompatible', `${runtime} binding is not valid JSON.`, { runtime });
183
- }
184
- }
185
- function ownerMarker(binding) {
186
- return {
187
- schemaVersion: 1,
188
- owner: 'aops-cli-agent-assets',
189
- storeId: binding.storeId,
190
- runtime: binding.runtime,
191
- bindingId: binding.bindingId,
192
- bindingGeneration: binding.bindingGeneration,
193
- relativePath: binding.relativePath,
194
- contentSha256: binding.contentSha256,
195
- };
196
- }
197
- async function completeRuntimeFiles(prepared, binding) {
198
- if (prepared.inspection.bindingProof !== 'verified'
199
- || binding.runtimeHomeId !== agentAssetsRuntimeHomeId(prepared.runtime, prepared.runtimeHome)
200
- || binding.runtimeRootIdentitySha256 !== prepared.nativeRoot.rootIdentitySha256
201
- || binding.contentSha256 !== AOPS_AGENT_ASSETS_GATEWAY_SHA256) {
202
- throw writerError('store_identity_mismatch', `${prepared.runtime} binding does not match the qualified runtime root.`);
203
- }
204
- await ensureNativeDirectory(prepared.runtimeHome, 'skills/aops', (relativePath) => prepared.nativeRoot.createDirectory(relativePath));
205
- if (prepared.inspection.ownerMarker === 'absent') {
206
- await prepared.nativeRoot.publishFileNoReplace('skills/aops/.aops-gateway-owner.json', jsonBytes(ownerMarker(binding)));
207
- }
208
- if (prepared.inspection.gateway === 'absent') {
209
- await prepared.nativeRoot.publishFileNoReplace(AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH, Buffer.from(AOPS_AGENT_ASSETS_GATEWAY, 'utf8'));
210
- }
211
- if (prepared.inspection.gateway === 'tampered') {
212
- if (prepared.inspection.ownerMarkerProof !== 'verified'
213
- || !prepared.inspection.gatewayContentSha256) {
214
- throw writerError('binding_conflict', `${prepared.runtime} tampered gateway has no immutable ownership proof.`);
215
- }
216
- await prepared.nativeRoot.publishFileReplace(AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH, prepared.inspection.gatewayContentSha256, Buffer.from(AOPS_AGENT_ASSETS_GATEWAY, 'utf8'));
217
- }
218
- }
219
- async function createRuntimeBinding(session, assetRoot, prepared, authority, active, activation, installedAt, randomId) {
220
- const bindingGeneration = 1;
221
- const bindingId = `binding-${prepared.runtime}-${randomId()}`;
222
- const bindingReceiptId = `binding-receipt-${prepared.runtime}-${bindingGeneration}-${authority.lastIssuedFenceEpoch}-${randomId()}`;
223
- const marker = {
224
- schemaVersion: 1,
225
- owner: 'aops-cli-agent-assets',
226
- storeId: authority.storeId,
227
- runtime: prepared.runtime,
228
- bindingId,
229
- bindingGeneration,
230
- relativePath: AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH,
231
- contentSha256: AOPS_AGENT_ASSETS_GATEWAY_SHA256,
232
- };
233
- const receipt = {
234
- schemaVersion: 1,
235
- storeId: authority.storeId,
236
- bindingId,
237
- bindingGeneration,
238
- runtime: prepared.runtime,
239
- runtimeHomeId: agentAssetsRuntimeHomeId(prepared.runtime, prepared.runtimeHome),
240
- runtimeRootIdentitySha256: prepared.nativeRoot.rootIdentitySha256,
241
- gatewayName: 'aops',
242
- relativePath: AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH,
243
- ownerMarkerRelativePath: 'skills/aops/.aops-gateway-owner.json',
244
- contentSha256: AOPS_AGENT_ASSETS_GATEWAY_SHA256,
245
- ownerMarkerSha256: canonicalJsonSha256V1(marker),
246
- activationReceiptId: activation.receiptId,
247
- activationReceiptSha256: active.receiptSha256,
248
- bindingReceiptId,
249
- installedAt,
250
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
251
- authorityRevision: authority.authorityRevision,
252
- };
253
- const binding = {
254
- ...receipt,
255
- bindingReceiptSha256: canonicalJsonSha256V1(receipt),
256
- };
257
- await ensureNativeDirectory(assetRoot, `state/bindings/receipts/${prepared.runtime}`, (relativePath) => session.createDirectory(relativePath));
258
- await session.publishFileNoReplace(`state/bindings/receipts/${prepared.runtime}/${bindingGeneration}-${bindingReceiptId}.json`, jsonBytes(receipt));
259
- await session.publishFileNoReplace(`state/bindings/${prepared.runtime}.json`, jsonBytes(binding));
260
- await ensureNativeDirectory(prepared.runtimeHome, 'skills/aops', (relativePath) => prepared.nativeRoot.createDirectory(relativePath));
261
- await prepared.nativeRoot.publishFileNoReplace('skills/aops/.aops-gateway-owner.json', jsonBytes(marker));
262
- await prepared.nativeRoot.publishFileNoReplace(AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH, Buffer.from(AOPS_AGENT_ASSETS_GATEWAY, 'utf8'));
263
- }
264
- async function upgradeRuntimeBindingContent(session, assetRoot, prepared, current, authority, active, activation, installedAt, randomId) {
265
- if (prepared.inspection.bindingProof !== 'verified'
266
- || prepared.inspection.ownerMarkerProof !== 'verified'
267
- || current.runtimeHomeId !== agentAssetsRuntimeHomeId(prepared.runtime, prepared.runtimeHome)
268
- || current.runtimeRootIdentitySha256 !== prepared.nativeRoot.rootIdentitySha256
269
- || current.contentSha256 === AOPS_AGENT_ASSETS_GATEWAY_SHA256) {
270
- throw writerError('store_identity_mismatch', `${prepared.runtime} binding is not eligible for a verified gateway-content upgrade.`);
271
- }
272
- const bindingGeneration = current.bindingGeneration + 1;
273
- const bindingReceiptId = `binding-receipt-${prepared.runtime}-${bindingGeneration}-${authority.lastIssuedFenceEpoch}-${randomId()}`;
274
- const marker = {
275
- schemaVersion: 1,
276
- owner: 'aops-cli-agent-assets',
277
- storeId: authority.storeId,
278
- runtime: prepared.runtime,
279
- bindingId: current.bindingId,
280
- bindingGeneration,
281
- relativePath: AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH,
282
- contentSha256: AOPS_AGENT_ASSETS_GATEWAY_SHA256,
283
- };
284
- const receipt = {
285
- schemaVersion: 1,
286
- storeId: authority.storeId,
287
- bindingId: current.bindingId,
288
- bindingGeneration,
289
- runtime: prepared.runtime,
290
- runtimeHomeId: current.runtimeHomeId,
291
- runtimeRootIdentitySha256: current.runtimeRootIdentitySha256,
292
- gatewayName: 'aops',
293
- relativePath: AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH,
294
- ownerMarkerRelativePath: 'skills/aops/.aops-gateway-owner.json',
295
- contentSha256: AOPS_AGENT_ASSETS_GATEWAY_SHA256,
296
- ownerMarkerSha256: canonicalJsonSha256V1(marker),
297
- activationReceiptId: activation.receiptId,
298
- activationReceiptSha256: active.receiptSha256,
299
- bindingReceiptId,
300
- previousContentSha256: current.contentSha256,
301
- installedAt,
302
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
303
- authorityRevision: authority.authorityRevision,
304
- };
305
- const binding = {
306
- ...receipt,
307
- bindingReceiptSha256: canonicalJsonSha256V1(receipt),
308
- };
309
- await ensureNativeDirectory(assetRoot, `state/bindings/receipts/${prepared.runtime}`, (relativePath) => session.createDirectory(relativePath));
310
- await session.publishFileNoReplace(`state/bindings/receipts/${prepared.runtime}/${bindingGeneration}-${bindingReceiptId}.json`, jsonBytes(receipt));
311
- if (prepared.inspection.gateway === 'absent') {
312
- await prepared.nativeRoot.publishFileNoReplace(AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH, Buffer.from(AOPS_AGENT_ASSETS_GATEWAY, 'utf8'));
313
- }
314
- else if ((prepared.inspection.gateway === 'tampered' || prepared.inspection.gateway === 'canonical')
315
- && prepared.inspection.gatewayContentSha256) {
316
- await prepared.nativeRoot.publishFileReplace(AOPS_AGENT_ASSETS_GATEWAY_RELATIVE_PATH, prepared.inspection.gatewayContentSha256, Buffer.from(AOPS_AGENT_ASSETS_GATEWAY, 'utf8'));
317
- }
318
- else {
319
- throw writerError('binding_conflict', `${prepared.runtime} gateway content cannot be upgraded safely.`);
320
- }
321
- await prepared.nativeRoot.publishFileReplace('skills/aops/.aops-gateway-owner.json', current.ownerMarkerSha256, jsonBytes(marker));
322
- await session.publishFileReplace(`state/bindings/${prepared.runtime}.json`, sha256(readManagedBytes(assetRoot, `state/bindings/${prepared.runtime}.json`)), jsonBytes(binding));
323
- }
324
- function inspectPreparedBindings(assetRoot, prepared) {
325
- const result = {};
326
- for (const item of prepared) {
327
- result[item.runtime] = inspectRuntimeGatewayBinding({
328
- assetRoot,
329
- runtime: item.runtime,
330
- runtimeHome: item.runtimeHome,
331
- expectedRuntimeRootIdentitySha256: item.nativeRoot.rootIdentitySha256,
332
- });
333
- }
334
- return Object.freeze(result);
335
- }
336
- function assertAuthorityMatchesCapability(authority, capability) {
337
- if (authority.boundMachineId !== capability.machineIdentitySha256) {
338
- throw writerError('different_machine_store', 'The agent-assets store is bound to another machine.');
339
- }
340
- if (authority.rootIdentitySha256 !== capability.rootIdentitySha256) {
341
- throw writerError('store_identity_mismatch', 'The agent-assets root identity changed.');
342
- }
343
- if (authority.publicationCapability !== publicationCapability(capability)
344
- || authority.capabilityEvidenceSha256 !== capability.capabilityEvidenceSha256) {
345
- throw writerError('durability_unavailable', 'The current native publication capability does not match store authority.');
346
- }
347
- }
348
- async function ensureGenesisAuthority(session, assetRoot, now, randomId) {
349
- const existing = readAgentAssetsStoreSnapshot({
350
- assetRoot,
351
- expectedMachineId: session.capability.machineIdentitySha256,
352
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
353
- });
354
- if (existing) {
355
- assertAuthorityMatchesCapability(existing.authority, session.capability);
356
- return existing.authority;
357
- }
358
- const authority = {
359
- schemaVersion: 1,
360
- storeId: `store-${randomId()}`,
361
- authorityRevision: 1,
362
- boundMachineId: session.capability.machineIdentitySha256,
363
- rootIdentitySha256: session.capability.rootIdentitySha256,
364
- publicationCapability: publicationCapability(session.capability),
365
- capabilityEvidenceSha256: session.capability.capabilityEvidenceSha256,
366
- lastIssuedFenceEpoch: 0,
367
- previousAuthoritySha256: null,
368
- createdAt: now,
369
- updatedAt: now,
370
- };
371
- await session.publishFileNoReplace('state/store-authority.json', jsonBytes(authority));
372
- return authority;
373
- }
374
- async function materializePackage(session, assetRoot, operationId, packageInput) {
375
- try {
376
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef: packageInput.packageRef, verify: 'full' });
377
- return false;
378
- }
379
- catch (error) {
380
- if (!(error instanceof AgentAssetsError) || error.code !== 'not_found')
381
- throw error;
382
- }
383
- const stage = `staging/${operationId}`;
384
- await session.removeManagedTree(stage);
385
- await session.createDirectory(stage);
386
- await session.createDirectory(`${stage}/files`);
387
- for (const file of packageInput.transferFiles) {
388
- const parent = path.posix.dirname(file.path);
389
- if (parent !== '.') {
390
- await ensureNativeDirectory(assetRoot, `${stage}/files/${parent}`, (relativePath) => session.createDirectory(relativePath));
391
- }
392
- await session.publishFileNoReplace(`${stage}/files/${file.path}`, file.bytes);
393
- }
394
- await session.publishFileNoReplace(`${stage}/manifest.json`, jsonBytes(packageInput.manifest));
395
- try {
396
- await session.promoteDirectoryNoReplace(stage, `core/${packageInput.packageRef.packageSha256}`);
397
- }
398
- catch (error) {
399
- if (!(error instanceof AgentAssetsError) || error.code !== 'publication_conflict')
400
- throw error;
401
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef: packageInput.packageRef, verify: 'full' });
402
- await session.removeManagedTree(stage);
403
- return false;
404
- }
405
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef: packageInput.packageRef, verify: 'full' });
406
- return true;
407
- }
408
- async function issueWriterFence(session, assetRoot, authority, now) {
409
- const authorityBytes = jsonBytes(authority);
410
- const authoritySha256 = canonicalJsonSha256V1(authority);
411
- await publishNoReplaceOrVerify(session, assetRoot, `state/authorities/${authority.authorityRevision}-${authoritySha256}.json`, authorityBytes);
412
- const next = {
413
- ...authority,
414
- authorityRevision: authority.authorityRevision + 1,
415
- lastIssuedFenceEpoch: authority.lastIssuedFenceEpoch + 1,
416
- previousAuthoritySha256: authoritySha256,
417
- updatedAt: now,
418
- };
419
- const rawCurrentSha256 = sha256(readManagedBytes(assetRoot, 'state/store-authority.json'));
420
- await session.publishFileReplace('state/store-authority.json', rawCurrentSha256, jsonBytes(next));
421
- return next;
422
- }
423
- export async function applyVerifiedCommunityCore(options) {
424
- const assetRoot = path.resolve(options.assetRoot);
425
- const preflight = readAgentAssetsStoreSnapshot({ assetRoot });
426
- if (options.requestedOperation === 'update' && !preflight?.active) {
427
- throw writerError('not_found', 'No active core exists; use `assets install` for the first activation.');
428
- }
429
- if (options.requestedOperation === 'install'
430
- && preflight?.active
431
- && preflight.receipt.core.packageSha256 !== options.release.packageRef.packageSha256) {
432
- throw writerError('publication_conflict', 'A different core is already installed; use `assets update` for a new release.');
433
- }
434
- const nowFactory = options.now ?? (() => new Date());
435
- const randomId = options.randomId ?? randomUUID;
436
- const operationId = operationIdentity(options);
437
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
438
- const session = await openNative({
439
- agentAssetsRoot: assetRoot,
440
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
441
- requiredDurability: 'process-crash',
442
- });
443
- try {
444
- await ensureStoreDirectories(session, assetRoot);
445
- const preparedBindings = await prepareRuntimeBindings(session, assetRoot, options.runtimeHomes);
446
- const now = nowFactory().toISOString();
447
- let authority = await ensureGenesisAuthority(session, assetRoot, now, randomId);
448
- const before = readAgentAssetsStoreSnapshot({
449
- assetRoot,
450
- expectedMachineId: session.capability.machineIdentitySha256,
451
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
452
- });
453
- if (!before)
454
- throw writerError('schema_incompatible', 'Store authority publication was not observable.');
455
- const keyIdentity = operationKeyIdentity(options);
456
- if (keyIdentity
457
- && before.receipt?.operationId.startsWith(`${keyIdentity}.`)
458
- && before.receipt.operationId !== operationId) {
459
- throw writerError('publication_conflict', 'The install/update idempotency key is already bound to another release or target intent.');
460
- }
461
- const coreAlreadyActive = before.active !== null
462
- && before.receipt.core.packageSha256 === options.release.packageRef.packageSha256;
463
- if (options.requestedOperation === 'install' && before.active !== null && !coreAlreadyActive) {
464
- throw writerError('publication_conflict', 'A different core is already installed; use `assets update` for a new release.');
465
- }
466
- if (options.requestedOperation === 'update' && before.active === null) {
467
- throw writerError('not_found', 'No active core exists; use `assets install` for the first activation.');
468
- }
469
- if (coreAlreadyActive) {
470
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef: options.release.packageRef, verify: 'full' });
471
- }
472
- const newBindings = preparedBindings.filter((item) => item.inspection.state === 'absent');
473
- const recoverableBindings = preparedBindings.filter((item) => item.inspection.state === 'managed-drift');
474
- if (coreAlreadyActive && newBindings.length === 0 && recoverableBindings.length === 0) {
475
- return {
476
- idempotent: true,
477
- packageInstalled: false,
478
- authority: before.authority,
479
- active: before.active,
480
- receipt: before.receipt,
481
- capability: session.capability,
482
- bindings: inspectPreparedBindings(assetRoot, preparedBindings),
483
- };
484
- }
485
- const packageInstalled = coreAlreadyActive
486
- ? false
487
- : await materializePackage(session, assetRoot, operationId, options.release);
488
- const contentUpgradeBindings = recoverableBindings.map((prepared) => ({
489
- prepared,
490
- current: readCurrentRuntimeBinding(assetRoot, prepared.runtime),
491
- })).filter(({ current }) => current.contentSha256 !== AOPS_AGENT_ASSETS_GATEWAY_SHA256);
492
- if (!coreAlreadyActive || newBindings.length > 0 || contentUpgradeBindings.length > 0) {
493
- authority = await issueWriterFence(session, assetRoot, authority, now);
494
- }
495
- let active = before.active;
496
- let receipt = before.receipt;
497
- if (!coreAlreadyActive) {
498
- const generation = (before.active?.generation ?? 0) + 1;
499
- const receiptId = `receipt-${generation}-${authority.lastIssuedFenceEpoch}-${randomId()}`;
500
- receipt = {
501
- schemaVersion: 1,
502
- storeId: authority.storeId,
503
- receiptId,
504
- operationId,
505
- operation: before.active === null ? 'install' : 'update',
506
- generation,
507
- createdAt: now,
508
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
509
- authorityRevision: authority.authorityRevision,
510
- previousReceiptId: before.receipt?.receiptId ?? null,
511
- previousReceiptSha256: before.receipt === null ? null : canonicalJsonSha256V1(before.receipt),
512
- core: options.release.packageRef,
513
- assets: before.receipt?.assets ?? [],
514
- };
515
- const receiptSha256 = canonicalJsonSha256V1(receipt);
516
- await session.publishFileNoReplace(`state/receipts/${generation}-${receiptId}.json`, jsonBytes(receipt));
517
- active = {
518
- schemaVersion: 1,
519
- storeId: authority.storeId,
520
- generation,
521
- receiptId,
522
- receiptSha256,
523
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
524
- authorityRevision: authority.authorityRevision,
525
- updatedAt: now,
526
- };
527
- if (before.active === null) {
528
- await session.publishFileNoReplace('state/active.json', jsonBytes(active));
529
- }
530
- else {
531
- const priorActiveSha256 = sha256(readManagedBytes(assetRoot, 'state/active.json'));
532
- await session.publishFileReplace('state/active.json', priorActiveSha256, jsonBytes(active));
533
- }
534
- }
535
- if (!active || !receipt)
536
- throw writerError('schema_incompatible', 'Runtime binding requires an active core receipt.');
537
- for (const prepared of recoverableBindings) {
538
- const current = readCurrentRuntimeBinding(assetRoot, prepared.runtime);
539
- if (current.contentSha256 === AOPS_AGENT_ASSETS_GATEWAY_SHA256) {
540
- await completeRuntimeFiles(prepared, current);
541
- }
542
- else {
543
- await upgradeRuntimeBindingContent(session, assetRoot, prepared, current, authority, active, receipt, now, randomId);
544
- }
545
- }
546
- for (const prepared of newBindings) {
547
- await createRuntimeBinding(session, assetRoot, prepared, authority, active, receipt, now, randomId);
548
- }
549
- const after = readAgentAssetsStoreSnapshot({
550
- assetRoot,
551
- expectedMachineId: session.capability.machineIdentitySha256,
552
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
553
- });
554
- if (!after?.active || after.active.receiptSha256 !== active.receiptSha256) {
555
- throw writerError('atomic_replace_blocked', 'Activation pointer did not publish the exact receipt.');
556
- }
557
- const bindings = inspectPreparedBindings(assetRoot, preparedBindings);
558
- for (const [runtime, binding] of Object.entries(bindings)) {
559
- if (binding?.state !== 'ready') {
560
- throw writerError('atomic_replace_blocked', `${runtime} gateway binding did not become ready.`, {
561
- runtime,
562
- state: binding?.state,
563
- reasons: binding?.reasons,
564
- });
565
- }
566
- }
567
- return {
568
- idempotent: false,
569
- packageInstalled,
570
- authority: after.authority,
571
- active: after.active,
572
- receipt: after.receipt,
573
- capability: session.capability,
574
- bindings,
575
- };
576
- }
577
- finally {
578
- await session.close();
579
- }
580
- }
581
- export async function applyVerifiedHostedSkillPackage(options) {
582
- if (options.manifest.assetKind !== 'skill-package'
583
- || options.manifest.provenance.trustClass !== 'verified-hosted-package'
584
- || options.manifest.provenance.expectedDigestSource !== 'immutable-hosted-metadata') {
585
- throw writerError('untrusted_origin', 'Hosted activation requires an immutable verified skill-package manifest.');
586
- }
587
- if (options.transferFiles) {
588
- const validation = validatePortablePackageV1(options.manifest, options.transferFiles);
589
- if (!validation.ok) {
590
- throw writerError('hash_mismatch', 'Hosted package bytes failed portable validation before staging.', {
591
- issues: validation.issues,
592
- });
593
- }
594
- }
595
- const packageRef = {
596
- name: options.manifest.name,
597
- version: options.manifest.version,
598
- versionId: options.manifest.versionId,
599
- packageSha256: options.manifest.packageSha256,
600
- entryFile: options.manifest.entryFile,
601
- origin: 'hosted-cache',
602
- trustClass: 'verified-hosted-package',
603
- };
604
- const assetRoot = path.resolve(options.assetRoot);
605
- const now = (options.now ?? (() => new Date()))().toISOString();
606
- const randomId = options.randomId ?? randomUUID;
607
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
608
- const session = await openNative({
609
- agentAssetsRoot: assetRoot,
610
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
611
- requiredDurability: 'process-crash',
612
- });
613
- try {
614
- await ensureStoreDirectories(session, assetRoot);
615
- let before = readAgentAssetsStoreSnapshot({
616
- assetRoot,
617
- expectedMachineId: session.capability.machineIdentitySha256,
618
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
619
- });
620
- if (!before?.active || !before.receipt) {
621
- throw writerError('not_found', 'Install the signed Community core before activating hosted skills.');
622
- }
623
- assertAuthorityMatchesCapability(before.authority, session.capability);
624
- const activeExact = before.receipt.assets.some((entry) => entry.packageSha256 === packageRef.packageSha256);
625
- if (activeExact) {
626
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef, verify: 'full' });
627
- return {
628
- idempotent: true,
629
- packageInstalled: false,
630
- authority: before.authority,
631
- active: before.active,
632
- receipt: before.receipt,
633
- packageRef,
634
- };
635
- }
636
- const operationId = sha256(`aops-agent-assets-hosted-package-v1\0${packageRef.packageSha256}\0${randomId()}`).slice(0, 40);
637
- let packageInstalled = false;
638
- try {
639
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef, verify: 'full' });
640
- }
641
- catch (error) {
642
- if (!(error instanceof AgentAssetsError) || error.code !== 'not_found')
643
- throw error;
644
- if (!options.transferFiles) {
645
- throw writerError('not_found', 'Exact hosted package bytes are not present in the verified local store.');
646
- }
647
- packageInstalled = await materializePackage(session, assetRoot, operationId, {
648
- packageRef,
649
- manifest: options.manifest,
650
- transferFiles: options.transferFiles,
651
- });
652
- }
653
- let authority = await issueWriterFence(session, assetRoot, before.authority, now);
654
- before = readAgentAssetsStoreSnapshot({
655
- assetRoot,
656
- expectedMachineId: session.capability.machineIdentitySha256,
657
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
658
- });
659
- if (!before?.active || !before.receipt)
660
- throw writerError('not_found', 'The active core disappeared during hosted activation.');
661
- authority = before.authority;
662
- const assets = [
663
- ...before.receipt.assets.filter((entry) => entry.name !== packageRef.name),
664
- packageRef,
665
- ].sort((left, right) => {
666
- return compareCanonicalText(left.name, right.name)
667
- || compareCanonicalText(left.versionId, right.versionId)
668
- || compareCanonicalText(left.packageSha256, right.packageSha256);
669
- });
670
- const generation = before.active.generation + 1;
671
- const receiptId = `receipt-${generation}-${authority.lastIssuedFenceEpoch}-${randomId()}`;
672
- const receipt = {
673
- schemaVersion: 1,
674
- storeId: authority.storeId,
675
- receiptId,
676
- operationId,
677
- operation: 'update',
678
- generation,
679
- createdAt: now,
680
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
681
- authorityRevision: authority.authorityRevision,
682
- previousReceiptId: before.receipt.receiptId,
683
- previousReceiptSha256: canonicalJsonSha256V1(before.receipt),
684
- core: before.receipt.core,
685
- assets,
686
- };
687
- const receiptSha256 = canonicalJsonSha256V1(receipt);
688
- await session.publishFileNoReplace(`state/receipts/${generation}-${receiptId}.json`, jsonBytes(receipt));
689
- const active = {
690
- schemaVersion: 1,
691
- storeId: authority.storeId,
692
- generation,
693
- receiptId,
694
- receiptSha256,
695
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
696
- authorityRevision: authority.authorityRevision,
697
- updatedAt: now,
698
- };
699
- await session.publishFileReplace('state/active.json', sha256(readManagedBytes(assetRoot, 'state/active.json')), jsonBytes(active));
700
- const after = readAgentAssetsStoreSnapshot({
701
- assetRoot,
702
- expectedMachineId: session.capability.machineIdentitySha256,
703
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
704
- });
705
- if (!after?.active || after.active.receiptSha256 !== receiptSha256) {
706
- throw writerError('atomic_replace_blocked', 'Hosted package activation did not publish the exact receipt.');
707
- }
708
- verifyAgentAssetsPackageAtRoot({ assetRoot, packageRef, verify: 'full' });
709
- return {
710
- idempotent: false,
711
- packageInstalled,
712
- authority: after.authority,
713
- active: after.active,
714
- receipt: after.receipt,
715
- packageRef,
716
- };
717
- }
718
- finally {
719
- await session.close();
720
- }
721
- }
722
- export async function repairAgentAssetRuntimeBindings(options) {
723
- const assetRoot = path.resolve(options.assetRoot);
724
- if (!readAgentAssetsStoreSnapshot({ assetRoot })) {
725
- throw writerError('not_found', 'Agent assets are not installed; repair cannot synthesize core trust.');
726
- }
727
- const now = (options.now ?? (() => new Date()))().toISOString();
728
- const randomId = options.randomId ?? randomUUID;
729
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
730
- const session = await openNative({
731
- agentAssetsRoot: assetRoot,
732
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
733
- requiredDurability: 'process-crash',
734
- });
735
- try {
736
- await ensureStoreDirectories(session, assetRoot);
737
- const prepared = await prepareRuntimeBindings(session, assetRoot, options.runtimeHomes, {
738
- repairManagedContent: true,
739
- });
740
- const before = readAgentAssetsStoreSnapshot({
741
- assetRoot,
742
- expectedMachineId: session.capability.machineIdentitySha256,
743
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
744
- });
745
- if (!before?.active || !before.receipt) {
746
- throw writerError('not_found', 'Agent assets have no active verified core to repair.');
747
- }
748
- const recoverable = prepared.filter((item) => item.inspection.state === 'managed-drift');
749
- const missing = prepared.filter((item) => item.inspection.state === 'absent');
750
- if (recoverable.length === 0 && missing.length === 0) {
751
- return {
752
- idempotent: true,
753
- authority: before.authority,
754
- active: before.active,
755
- receipt: before.receipt,
756
- capability: session.capability,
757
- bindings: inspectPreparedBindings(assetRoot, prepared),
758
- };
759
- }
760
- let authority = before.authority;
761
- const contentUpgradeBindings = recoverable.map((item) => ({
762
- item,
763
- current: readCurrentRuntimeBinding(assetRoot, item.runtime),
764
- })).filter(({ current }) => current.contentSha256 !== AOPS_AGENT_ASSETS_GATEWAY_SHA256);
765
- if (missing.length > 0 || contentUpgradeBindings.length > 0) {
766
- authority = await issueWriterFence(session, assetRoot, authority, now);
767
- }
768
- for (const item of recoverable) {
769
- const current = readCurrentRuntimeBinding(assetRoot, item.runtime);
770
- if (current.contentSha256 === AOPS_AGENT_ASSETS_GATEWAY_SHA256) {
771
- await completeRuntimeFiles(item, current);
772
- }
773
- else {
774
- await upgradeRuntimeBindingContent(session, assetRoot, item, current, authority, before.active, before.receipt, now, randomId);
775
- }
776
- }
777
- for (const item of missing) {
778
- await createRuntimeBinding(session, assetRoot, item, authority, before.active, before.receipt, now, randomId);
779
- }
780
- const after = readAgentAssetsStoreSnapshot({
781
- assetRoot,
782
- expectedMachineId: session.capability.machineIdentitySha256,
783
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
784
- });
785
- if (!after?.active || !after.receipt) {
786
- throw writerError('atomic_replace_blocked', 'Runtime repair lost the active core chain.');
787
- }
788
- const bindings = inspectPreparedBindings(assetRoot, prepared);
789
- for (const [runtime, binding] of Object.entries(bindings)) {
790
- if (binding?.state !== 'ready') {
791
- throw writerError('atomic_replace_blocked', `${runtime} gateway repair did not become ready.`, {
792
- runtime,
793
- state: binding?.state,
794
- reasons: binding?.reasons,
795
- });
796
- }
797
- }
798
- return {
799
- idempotent: false,
800
- authority: after.authority,
801
- active: after.active,
802
- receipt: after.receipt,
803
- capability: session.capability,
804
- bindings,
805
- };
806
- }
807
- finally {
808
- await session.close();
809
- }
810
- }
811
- function pinFileName(leaseId) {
812
- return `${sha256(`aops-agent-assets-pin-v1\0${leaseId}`)}.json`;
813
- }
814
- function maintenanceReceiptFileName(receiptId) {
815
- return `${sha256(`aops-agent-assets-maintenance-receipt-v1\0${receiptId}`)}.json`;
816
- }
817
- function sameStrings(left, right) {
818
- return left.length === right.length && left.every((value, index) => value === right[index]);
819
- }
820
- function strictMaintenanceReceiptEntries(assetRoot) {
821
- const root = path.resolve(assetRoot);
822
- const rootStat = lstatSync(root);
823
- if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
824
- throw writerError('link_unsafe_path', 'The opened agent-assets root is unsafe.');
825
- }
826
- const realRoot = realpathSync.native(root);
827
- let directory = root;
828
- for (const segment of ['state', 'maintenance-receipts']) {
829
- directory = path.join(directory, segment);
830
- const stat = lstatSync(directory);
831
- if (!stat.isDirectory() || stat.isSymbolicLink()) {
832
- throw writerError('link_unsafe_path', 'The managed maintenance receipt directory is unsafe.');
833
- }
834
- }
835
- const realDirectory = realpathSync.native(directory);
836
- const relativeDirectory = path.relative(realRoot, realDirectory);
837
- if (relativeDirectory.startsWith('..') || path.isAbsolute(relativeDirectory)) {
838
- throw writerError('link_unsafe_path', 'The managed maintenance receipt directory escaped the store root.');
839
- }
840
- const entries = readdirSync(directory).sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
841
- if (entries.length > MAX_MANAGED_DIRECTORY_ENTRIES) {
842
- throw writerError('schema_incompatible', 'The managed maintenance receipt directory exceeds the bounded entry count.');
843
- }
844
- const receipts = [];
845
- for (const name of entries) {
846
- if (!/^[a-f0-9]{64}\.json$/.test(name)) {
847
- throw writerError('schema_incompatible', 'Unknown entry exists in the managed maintenance receipt directory.', {
848
- name,
849
- });
850
- }
851
- let value;
852
- try {
853
- value = JSON.parse(Buffer.from(readManagedBytes(assetRoot, `state/maintenance-receipts/${name}`)).toString('utf8'));
854
- }
855
- catch (error) {
856
- if (error instanceof AgentAssetsError)
857
- throw error;
858
- throw writerError('schema_incompatible', 'A maintenance receipt is not valid JSON.', { name });
859
- }
860
- const receipt = parseMaintenanceReceiptV1(value);
861
- if (name !== maintenanceReceiptFileName(receipt.receiptId)) {
862
- throw writerError('hash_mismatch', 'A maintenance receipt is stored under the wrong immutable identity.', {
863
- name,
864
- receiptId: receipt.receiptId,
865
- });
866
- }
867
- receipts.push(receipt);
868
- }
869
- return receipts;
870
- }
871
- function readRecoverablePruneReceipt(assetRoot, authority, expectedIdentity = {}) {
872
- const current = readAgentAssetsStoreSnapshot({ assetRoot, ...expectedIdentity });
873
- if (!current || canonicalJsonSha256V1(current.authority) !== canonicalJsonSha256V1(authority)) {
874
- throw writerError('concurrent_writer', 'Writer authority changed during maintenance recovery inspection.');
875
- }
876
- const priorMaintenance = readAgentAssetsMaintenanceHead({ assetRoot, ...expectedIdentity });
877
- const receipts = strictMaintenanceReceiptEntries(assetRoot);
878
- const byId = new Map();
879
- for (const receipt of receipts) {
880
- if (byId.has(receipt.receiptId)) {
881
- throw writerError('schema_incompatible', 'Multiple maintenance receipts claim the same immutable identity.');
882
- }
883
- if (receipt.storeId !== authority.storeId) {
884
- throw writerError('store_identity_mismatch', 'A maintenance receipt belongs to another store.');
885
- }
886
- byId.set(receipt.receiptId, receipt);
887
- }
888
- const lineage = new Set();
889
- let cursor = priorMaintenance?.receipt ?? null;
890
- while (cursor) {
891
- if (lineage.has(cursor.receiptId)) {
892
- throw writerError('schema_incompatible', 'The maintenance receipt lineage contains a cycle.');
893
- }
894
- const stored = byId.get(cursor.receiptId);
895
- if (!stored || canonicalJsonSha256V1(stored) !== canonicalJsonSha256V1(cursor)) {
896
- throw writerError('hash_mismatch', 'The maintenance head is not present exactly in the immutable ledger.');
897
- }
898
- lineage.add(cursor.receiptId);
899
- if (cursor.previousReceiptId === null || cursor.previousReceiptSha256 === null) {
900
- cursor = null;
901
- continue;
902
- }
903
- const previous = byId.get(cursor.previousReceiptId);
904
- if (!previous || canonicalJsonSha256V1(previous) !== cursor.previousReceiptSha256) {
905
- throw writerError('hash_mismatch', 'The maintenance receipt lineage does not match its previous receipt digest.');
906
- }
907
- if (previous.authorityRevision >= cursor.authorityRevision
908
- || previous.writerFenceEpoch >= cursor.writerFenceEpoch) {
909
- throw writerError('schema_incompatible', 'The maintenance receipt lineage does not advance authority and fence epochs.');
910
- }
911
- cursor = previous;
912
- }
913
- const orphans = receipts.filter((receipt) => !lineage.has(receipt.receiptId));
914
- if (orphans.length === 0)
915
- return null;
916
- if (orphans.length !== 1) {
917
- throw writerError('concurrent_writer', 'The maintenance ledger contains ambiguous orphan receipts.', {
918
- orphanReceiptIds: orphans.map((receipt) => receipt.receiptId),
919
- });
920
- }
921
- const receipt = orphans[0];
922
- const expectedPreviousId = priorMaintenance?.receipt.receiptId ?? null;
923
- const expectedPreviousSha256 = priorMaintenance ? canonicalJsonSha256V1(priorMaintenance.receipt) : null;
924
- if (receipt.operation !== 'prune'
925
- || receipt.previousReceiptId !== expectedPreviousId
926
- || receipt.previousReceiptSha256 !== expectedPreviousSha256) {
927
- throw writerError('concurrent_writer', 'The orphan maintenance receipt is not the unique next prune operation.');
928
- }
929
- if (receipt.authorityRevision !== authority.authorityRevision
930
- || receipt.writerFenceEpoch !== authority.lastIssuedFenceEpoch) {
931
- throw writerError('concurrent_writer', 'The orphan prune receipt is stale for the current writer authority.', {
932
- receiptAuthorityRevision: receipt.authorityRevision,
933
- currentAuthorityRevision: authority.authorityRevision,
934
- receiptWriterFenceEpoch: receipt.writerFenceEpoch,
935
- currentWriterFenceEpoch: authority.lastIssuedFenceEpoch,
936
- });
937
- }
938
- const protectedPackageSha256s = [...receipt.protectedPackageSha256s].sort();
939
- const removedPackageSha256s = [...receipt.removedPackageSha256s].sort();
940
- const expectedPaths = removedPackageSha256s.map((digest) => `core/${digest}`);
941
- if (!receipt.receiptId.startsWith(`maintenance-${authority.lastIssuedFenceEpoch}-`)
942
- || receipt.createdAt !== authority.updatedAt
943
- || !sameStrings(receipt.protectedPackageSha256s, protectedPackageSha256s)
944
- || !sameStrings(receipt.removedPackageSha256s, removedPackageSha256s)
945
- || !sameStrings(receipt.affectedManagedPaths, expectedPaths)
946
- || new Set([...protectedPackageSha256s, ...removedPackageSha256s]).size
947
- !== protectedPackageSha256s.length + removedPackageSha256s.length
948
- || protectedPackageSha256s.some((digest) => !SHA256_HEX.test(digest))
949
- || removedPackageSha256s.some((digest) => !SHA256_HEX.test(digest))) {
950
- throw writerError('schema_incompatible', 'The orphan prune receipt does not describe one canonical immutable prune set.');
951
- }
952
- const expectedOperationId = sha256(`aops-agent-assets-prune-operation-v1\0${protectedPackageSha256s.join(',')}\0${removedPackageSha256s.join(',')}`).slice(0, 40);
953
- if (receipt.operationId !== expectedOperationId) {
954
- throw writerError('hash_mismatch', 'The orphan prune receipt operation digest is invalid.');
955
- }
956
- return {
957
- receipt,
958
- receiptSha256: canonicalJsonSha256V1(receipt),
959
- priorMaintenance,
960
- };
961
- }
962
- async function recoverInterruptedPrune(session, assetRoot, nowDate, recovery) {
963
- const expectedMachineId = session.capability.machineIdentitySha256;
964
- const expectedRootIdentitySha256 = session.capability.rootIdentitySha256;
965
- let plan = readAgentAssetsPrunePlan({ assetRoot, now: nowDate, expectedMachineId, expectedRootIdentitySha256 });
966
- const currentRecovery = readRecoverablePruneReceipt(assetRoot, plan.authority, {
967
- expectedMachineId,
968
- expectedRootIdentitySha256,
969
- });
970
- if (!currentRecovery || currentRecovery.receiptSha256 !== recovery.receiptSha256) {
971
- throw writerError('concurrent_writer', 'The interrupted prune receipt changed before native recovery acquired the store.');
972
- }
973
- const receipt = currentRecovery.receipt;
974
- if (plan.protectedPackageSha256s.some((digest) => !receipt.protectedPackageSha256s.includes(digest))) {
975
- throw writerError('concurrent_writer', 'The protected package set changed after the interrupted prune receipt was published.');
976
- }
977
- const receiptTargets = new Set(receipt.removedPackageSha256s);
978
- if (plan.protectedPackageSha256s.some((digest) => receiptTargets.has(digest))) {
979
- throw writerError('concurrent_writer', 'An interrupted prune target is now protected and cannot be removed.');
980
- }
981
- for (const digest of receipt.removedPackageSha256s) {
982
- plan = readAgentAssetsPrunePlan({ assetRoot, now: nowDate, expectedMachineId, expectedRootIdentitySha256 });
983
- if (plan.authority.authorityRevision !== receipt.authorityRevision
984
- || plan.authority.lastIssuedFenceEpoch !== receipt.writerFenceEpoch) {
985
- throw writerError('concurrent_writer', 'Writer authority advanced while recovering an interrupted prune.');
986
- }
987
- if (plan.protectedPackageSha256s.includes(digest)) {
988
- throw writerError('concurrent_writer', 'An interrupted prune target became protected during recovery.', {
989
- packageSha256: digest,
990
- });
991
- }
992
- if (!plan.removablePackageSha256s.includes(digest))
993
- continue;
994
- const relativePath = `core/${digest}`;
995
- const removed = await session.removeManagedTree(relativePath);
996
- if (removed !== 'removed') {
997
- throw writerError('atomic_replace_blocked', 'A verified recovery target disappeared during managed removal.', {
998
- relativePath,
999
- });
1000
- }
1001
- }
1002
- const pointer = {
1003
- schemaVersion: 1,
1004
- storeId: plan.authority.storeId,
1005
- receiptId: receipt.receiptId,
1006
- receiptSha256: currentRecovery.receiptSha256,
1007
- writerFenceEpoch: receipt.writerFenceEpoch,
1008
- authorityRevision: receipt.authorityRevision,
1009
- updatedAt: receipt.createdAt,
1010
- };
1011
- if (currentRecovery.priorMaintenance) {
1012
- await session.publishFileReplace('state/maintenance.json', sha256(readManagedBytes(assetRoot, 'state/maintenance.json')), jsonBytes(pointer));
1013
- }
1014
- else {
1015
- await session.publishFileNoReplace('state/maintenance.json', jsonBytes(pointer));
1016
- }
1017
- const afterPlan = readAgentAssetsPrunePlan({ assetRoot, now: nowDate, expectedMachineId, expectedRootIdentitySha256 });
1018
- const afterMaintenance = readAgentAssetsMaintenanceHead({ assetRoot, expectedMachineId, expectedRootIdentitySha256 });
1019
- if (receipt.removedPackageSha256s.some((digest) => afterPlan.removablePackageSha256s.includes(digest))
1020
- || afterMaintenance?.pointer.receiptSha256 !== currentRecovery.receiptSha256) {
1021
- throw writerError('atomic_replace_blocked', 'Interrupted prune recovery did not publish the exact maintenance result.');
1022
- }
1023
- return {
1024
- idempotent: true,
1025
- authority: afterPlan.authority,
1026
- protectedPackageSha256s: afterPlan.protectedPackageSha256s,
1027
- removedPackageSha256s: receipt.removedPackageSha256s,
1028
- maintenanceReceipt: afterMaintenance.receipt,
1029
- };
1030
- }
1031
- export async function pinAgentAssetsExactVersion(options) {
1032
- const assetRoot = path.resolve(options.assetRoot);
1033
- const versionId = options.versionId.trim();
1034
- const leaseId = options.leaseId.trim();
1035
- const owner = options.owner?.trim() || 'aops-cli';
1036
- const nowDate = (options.now ?? (() => new Date()))();
1037
- const expiresAtMs = Date.parse(options.expiresAt);
1038
- if (!versionId || !leaseId || leaseId.length > 512 || !owner || owner.length > 512) {
1039
- throw writerError('schema_incompatible', 'Pin identity fields are missing or exceed the v1 bound.');
1040
- }
1041
- if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowDate.getTime()) {
1042
- throw writerError('schema_incompatible', 'Pin expiry must be a finite future date-time.');
1043
- }
1044
- const resolved = readResolvedAgentAssetPackage({ assetRoot, versionId, now: nowDate });
1045
- const relativePath = `state/pins/${pinFileName(leaseId)}`;
1046
- const absolutePath = path.join(assetRoot, ...relativePath.split('/'));
1047
- const initial = readAgentAssetsStoreSnapshot({ assetRoot });
1048
- if (!initial)
1049
- throw writerError('not_found', 'Agent assets are not installed.');
1050
- let currentPin = null;
1051
- if (existsSync(absolutePath)) {
1052
- currentPin = parseExactVersionPinV1(JSON.parse(readFileSync(absolutePath, 'utf8')));
1053
- if (currentPin.storeId !== initial.authority.storeId || currentPin.leaseId !== leaseId) {
1054
- throw writerError('store_identity_mismatch', 'The exact-version pin path contains a foreign identity.');
1055
- }
1056
- if (currentPin.packageSha256 !== resolved.resolved.packageSha256) {
1057
- throw writerError('publication_conflict', 'The lease is already bound to another immutable package.');
1058
- }
1059
- if (currentPin.expiresAt === options.expiresAt) {
1060
- return {
1061
- idempotent: true,
1062
- authority: initial.authority,
1063
- pin: currentPin,
1064
- maintenanceReceipt: readAgentAssetsMaintenanceHead({ assetRoot })?.receipt ?? null,
1065
- };
1066
- }
1067
- }
1068
- const now = nowDate.toISOString();
1069
- const randomId = options.randomId ?? randomUUID;
1070
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
1071
- const session = await openNative({
1072
- agentAssetsRoot: assetRoot,
1073
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
1074
- requiredDurability: 'process-crash',
1075
- });
1076
- try {
1077
- await ensureStoreDirectories(session, assetRoot);
1078
- const selected = readResolvedAgentAssetPackage({
1079
- assetRoot,
1080
- versionId,
1081
- now: nowDate,
1082
- expectedMachineId: session.capability.machineIdentitySha256,
1083
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1084
- });
1085
- let snapshot = readAgentAssetsStoreSnapshot({
1086
- assetRoot,
1087
- expectedMachineId: session.capability.machineIdentitySha256,
1088
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1089
- });
1090
- if (!snapshot)
1091
- throw writerError('not_found', 'Agent assets are not installed.');
1092
- let authority = await issueWriterFence(session, assetRoot, snapshot.authority, now);
1093
- snapshot = readAgentAssetsStoreSnapshot({
1094
- assetRoot,
1095
- expectedMachineId: session.capability.machineIdentitySha256,
1096
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1097
- });
1098
- if (!snapshot)
1099
- throw writerError('not_found', 'Agent assets are not installed.');
1100
- authority = snapshot.authority;
1101
- const priorMaintenance = readAgentAssetsMaintenanceHead({
1102
- assetRoot,
1103
- expectedMachineId: session.capability.machineIdentitySha256,
1104
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1105
- });
1106
- const receiptId = `maintenance-${authority.lastIssuedFenceEpoch}-${randomId()}`;
1107
- const operationId = sha256(`aops-agent-assets-pin-operation-v1\0${leaseId}\0${selected.resolved.packageSha256}\0${options.expiresAt}`).slice(0, 40);
1108
- const receipt = {
1109
- schemaVersion: 1,
1110
- storeId: authority.storeId,
1111
- receiptId,
1112
- operationId,
1113
- operation: 'pin',
1114
- createdAt: now,
1115
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1116
- authorityRevision: authority.authorityRevision,
1117
- previousReceiptId: priorMaintenance?.receipt.receiptId ?? null,
1118
- previousReceiptSha256: priorMaintenance ? canonicalJsonSha256V1(priorMaintenance.receipt) : null,
1119
- protectedPackageSha256s: [selected.resolved.packageSha256],
1120
- removedPackageSha256s: [],
1121
- affectedManagedPaths: [relativePath],
1122
- };
1123
- const receiptSha256 = canonicalJsonSha256V1(receipt);
1124
- await session.publishFileNoReplace(`state/maintenance-receipts/${maintenanceReceiptFileName(receiptId)}`, jsonBytes(receipt));
1125
- const pin = {
1126
- schemaVersion: 1,
1127
- storeId: authority.storeId,
1128
- leaseId,
1129
- packageSha256: selected.resolved.packageSha256,
1130
- owner,
1131
- createdAt: currentPin?.createdAt ?? now,
1132
- expiresAt: options.expiresAt,
1133
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1134
- authorityRevision: authority.authorityRevision,
1135
- };
1136
- if (currentPin) {
1137
- await session.publishFileReplace(relativePath, sha256(readManagedBytes(assetRoot, relativePath)), jsonBytes(pin));
1138
- }
1139
- else {
1140
- await session.publishFileNoReplace(relativePath, jsonBytes(pin));
1141
- }
1142
- const pointer = {
1143
- schemaVersion: 1,
1144
- storeId: authority.storeId,
1145
- receiptId,
1146
- receiptSha256,
1147
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1148
- authorityRevision: authority.authorityRevision,
1149
- updatedAt: now,
1150
- };
1151
- if (priorMaintenance) {
1152
- await session.publishFileReplace('state/maintenance.json', sha256(readManagedBytes(assetRoot, 'state/maintenance.json')), jsonBytes(pointer));
1153
- }
1154
- else {
1155
- await session.publishFileNoReplace('state/maintenance.json', jsonBytes(pointer));
1156
- }
1157
- const after = readAgentAssetsStoreSnapshot({
1158
- assetRoot,
1159
- expectedMachineId: session.capability.machineIdentitySha256,
1160
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1161
- });
1162
- const afterMaintenance = readAgentAssetsMaintenanceHead({
1163
- assetRoot,
1164
- expectedMachineId: session.capability.machineIdentitySha256,
1165
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1166
- });
1167
- if (!after || !afterMaintenance || afterMaintenance.pointer.receiptSha256 !== receiptSha256) {
1168
- throw writerError('atomic_replace_blocked', 'Pin maintenance pointer did not publish the exact receipt.');
1169
- }
1170
- return { idempotent: false, authority: after.authority, pin, maintenanceReceipt: afterMaintenance.receipt };
1171
- }
1172
- finally {
1173
- await session.close();
1174
- }
1175
- }
1176
- export async function pruneAgentAssets(options) {
1177
- const assetRoot = path.resolve(options.assetRoot);
1178
- const nowDate = (options.now ?? (() => new Date()))();
1179
- const initialPlan = readAgentAssetsPrunePlan({ assetRoot, now: nowDate });
1180
- const initialRecovery = readRecoverablePruneReceipt(assetRoot, initialPlan.authority);
1181
- if (initialPlan.removablePackageSha256s.length === 0 && !initialRecovery) {
1182
- return {
1183
- idempotent: true,
1184
- authority: initialPlan.authority,
1185
- protectedPackageSha256s: initialPlan.protectedPackageSha256s,
1186
- removedPackageSha256s: [],
1187
- maintenanceReceipt: readAgentAssetsMaintenanceHead({ assetRoot })?.receipt ?? null,
1188
- };
1189
- }
1190
- const now = nowDate.toISOString();
1191
- const randomId = options.randomId ?? randomUUID;
1192
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
1193
- const session = await openNative({
1194
- agentAssetsRoot: assetRoot,
1195
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
1196
- requiredDurability: 'process-crash',
1197
- });
1198
- try {
1199
- await ensureStoreDirectories(session, assetRoot);
1200
- let plan = readAgentAssetsPrunePlan({
1201
- assetRoot,
1202
- now: nowDate,
1203
- expectedMachineId: session.capability.machineIdentitySha256,
1204
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1205
- });
1206
- const recovery = readRecoverablePruneReceipt(assetRoot, plan.authority, {
1207
- expectedMachineId: session.capability.machineIdentitySha256,
1208
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1209
- });
1210
- if (recovery)
1211
- return await recoverInterruptedPrune(session, assetRoot, nowDate, recovery);
1212
- let authority = await issueWriterFence(session, assetRoot, plan.authority, now);
1213
- plan = readAgentAssetsPrunePlan({
1214
- assetRoot,
1215
- now: nowDate,
1216
- expectedMachineId: session.capability.machineIdentitySha256,
1217
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1218
- });
1219
- authority = plan.authority;
1220
- if (plan.removablePackageSha256s.length === 0) {
1221
- return {
1222
- idempotent: true,
1223
- authority,
1224
- protectedPackageSha256s: plan.protectedPackageSha256s,
1225
- removedPackageSha256s: [],
1226
- maintenanceReceipt: readAgentAssetsMaintenanceHead({ assetRoot })?.receipt ?? null,
1227
- };
1228
- }
1229
- const priorMaintenance = readAgentAssetsMaintenanceHead({
1230
- assetRoot,
1231
- expectedMachineId: session.capability.machineIdentitySha256,
1232
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1233
- });
1234
- const receiptId = `maintenance-${authority.lastIssuedFenceEpoch}-${randomId()}`;
1235
- const receipt = {
1236
- schemaVersion: 1,
1237
- storeId: authority.storeId,
1238
- receiptId,
1239
- operationId: sha256(`aops-agent-assets-prune-operation-v1\0${plan.protectedPackageSha256s.join(',')}\0${plan.removablePackageSha256s.join(',')}`).slice(0, 40),
1240
- operation: 'prune',
1241
- createdAt: now,
1242
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1243
- authorityRevision: authority.authorityRevision,
1244
- previousReceiptId: priorMaintenance?.receipt.receiptId ?? null,
1245
- previousReceiptSha256: priorMaintenance ? canonicalJsonSha256V1(priorMaintenance.receipt) : null,
1246
- protectedPackageSha256s: plan.protectedPackageSha256s,
1247
- removedPackageSha256s: plan.removablePackageSha256s,
1248
- affectedManagedPaths: plan.removableManagedPaths,
1249
- };
1250
- const receiptSha256 = canonicalJsonSha256V1(receipt);
1251
- await session.publishFileNoReplace(`state/maintenance-receipts/${maintenanceReceiptFileName(receiptId)}`, jsonBytes(receipt));
1252
- for (const relativePath of plan.removableManagedPaths) {
1253
- const removed = await session.removeManagedTree(relativePath);
1254
- if (removed !== 'removed') {
1255
- throw writerError('atomic_replace_blocked', 'A verified prune target disappeared before managed removal.', { relativePath });
1256
- }
1257
- }
1258
- const pointer = {
1259
- schemaVersion: 1,
1260
- storeId: authority.storeId,
1261
- receiptId,
1262
- receiptSha256,
1263
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1264
- authorityRevision: authority.authorityRevision,
1265
- updatedAt: now,
1266
- };
1267
- if (priorMaintenance) {
1268
- await session.publishFileReplace('state/maintenance.json', sha256(readManagedBytes(assetRoot, 'state/maintenance.json')), jsonBytes(pointer));
1269
- }
1270
- else {
1271
- await session.publishFileNoReplace('state/maintenance.json', jsonBytes(pointer));
1272
- }
1273
- const afterPlan = readAgentAssetsPrunePlan({
1274
- assetRoot,
1275
- now: nowDate,
1276
- expectedMachineId: session.capability.machineIdentitySha256,
1277
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1278
- });
1279
- const afterMaintenance = readAgentAssetsMaintenanceHead({
1280
- assetRoot,
1281
- expectedMachineId: session.capability.machineIdentitySha256,
1282
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1283
- });
1284
- if (afterPlan.removablePackageSha256s.length > 0 || afterMaintenance?.pointer.receiptSha256 !== receiptSha256) {
1285
- throw writerError('atomic_replace_blocked', 'Prune did not publish the exact maintenance result.');
1286
- }
1287
- return {
1288
- idempotent: false,
1289
- authority: afterPlan.authority,
1290
- protectedPackageSha256s: afterPlan.protectedPackageSha256s,
1291
- removedPackageSha256s: plan.removablePackageSha256s,
1292
- maintenanceReceipt: afterMaintenance.receipt,
1293
- };
1294
- }
1295
- finally {
1296
- await session.close();
1297
- }
1298
- }
1299
- export async function cleanupAgentAssetsStaging(options) {
1300
- const assetRoot = path.resolve(options.assetRoot);
1301
- const initialPlan = readAgentAssetsStagingCleanupPlan({ assetRoot });
1302
- if (initialPlan.removableManagedPaths.length === 0) {
1303
- return {
1304
- idempotent: true,
1305
- authority: initialPlan.authority,
1306
- active: initialPlan.active,
1307
- receipt: initialPlan.receipt,
1308
- capability: null,
1309
- removedManagedPaths: [],
1310
- };
1311
- }
1312
- const now = (options.now ?? (() => new Date()))().toISOString();
1313
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
1314
- const session = await openNative({
1315
- agentAssetsRoot: assetRoot,
1316
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
1317
- requiredDurability: 'process-crash',
1318
- });
1319
- try {
1320
- const before = readAgentAssetsStagingCleanupPlan({
1321
- assetRoot,
1322
- expectedMachineId: session.capability.machineIdentitySha256,
1323
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1324
- });
1325
- const activeIdentity = canonicalJsonSha256V1(before.active);
1326
- const receiptIdentity = canonicalJsonSha256V1(before.receipt);
1327
- const fencedAuthority = await issueWriterFence(session, assetRoot, before.authority, now);
1328
- const fenced = readAgentAssetsStagingCleanupPlan({
1329
- assetRoot,
1330
- expectedMachineId: session.capability.machineIdentitySha256,
1331
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1332
- });
1333
- if (fenced.authority.authorityRevision !== fencedAuthority.authorityRevision
1334
- || fenced.authority.lastIssuedFenceEpoch !== fencedAuthority.lastIssuedFenceEpoch
1335
- || fenced.treeIdentitySha256 !== before.treeIdentitySha256
1336
- || canonicalJsonV1(fenced.removableManagedPaths) !== canonicalJsonV1(before.removableManagedPaths)
1337
- || canonicalJsonSha256V1(fenced.active) !== activeIdentity
1338
- || canonicalJsonSha256V1(fenced.receipt) !== receiptIdentity) {
1339
- throw writerError('concurrent_writer', 'Managed staging or activation state changed while cleanup acquired its writer fence.');
1340
- }
1341
- for (const relativePath of fenced.removableManagedPaths) {
1342
- const removed = await session.removeManagedTree(relativePath);
1343
- if (removed !== 'removed') {
1344
- throw writerError('atomic_replace_blocked', 'A preflighted staging tree disappeared before native removal.', {
1345
- relativePath,
1346
- });
1347
- }
1348
- }
1349
- const after = readAgentAssetsStagingCleanupPlan({
1350
- assetRoot,
1351
- expectedMachineId: session.capability.machineIdentitySha256,
1352
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1353
- });
1354
- if (after.removableManagedPaths.length !== 0
1355
- || canonicalJsonSha256V1(after.active) !== activeIdentity
1356
- || canonicalJsonSha256V1(after.receipt) !== receiptIdentity) {
1357
- throw writerError('atomic_replace_blocked', 'Staging cleanup did not preserve the exact activation state.');
1358
- }
1359
- return {
1360
- idempotent: false,
1361
- authority: after.authority,
1362
- active: after.active,
1363
- receipt: after.receipt,
1364
- capability: session.capability,
1365
- removedManagedPaths: fenced.removableManagedPaths,
1366
- };
1367
- }
1368
- finally {
1369
- await session.close();
1370
- }
1371
- }
1372
- export async function rollbackAgentAssets(options) {
1373
- const assetRoot = path.resolve(options.assetRoot);
1374
- const keyIdentity = options.idempotencyKey?.trim()
1375
- ? sha256(`aops-agent-assets-rollback-key-v1\0${options.idempotencyKey.trim()}`).slice(0, 40)
1376
- : null;
1377
- const operationId = keyIdentity
1378
- ? `${keyIdentity}.${sha256(`aops-agent-assets-rollback-intent-v1\0${options.toReceiptId?.trim() || 'previous'}`).slice(0, 40)}`
1379
- : (options.randomId ?? randomUUID)();
1380
- const now = (options.now ?? (() => new Date()))().toISOString();
1381
- const randomId = options.randomId ?? randomUUID;
1382
- const openNative = options.openNative ?? openAgentAssetsNativePublicationSession;
1383
- const session = await openNative({
1384
- agentAssetsRoot: assetRoot,
1385
- bootstrapAnchor: options.bootstrapAnchor ?? nearestExistingAnchor(assetRoot),
1386
- requiredDurability: 'process-crash',
1387
- });
1388
- try {
1389
- const initial = readAgentAssetsStoreSnapshot({
1390
- assetRoot,
1391
- expectedMachineId: session.capability.machineIdentitySha256,
1392
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1393
- });
1394
- if (initial?.active && initial.receipt?.operation === 'rollback' && keyIdentity) {
1395
- if (initial.receipt.operationId.startsWith(`${keyIdentity}.`) && initial.receipt.operationId !== operationId) {
1396
- throw writerError('publication_conflict', 'The rollback idempotency key is already bound to another target intent.');
1397
- }
1398
- if (initial.receipt.operationId === operationId) {
1399
- readAgentAssetsStoreStatus({
1400
- assetRoot,
1401
- verify: 'full',
1402
- expectedMachineId: session.capability.machineIdentitySha256,
1403
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1404
- });
1405
- return {
1406
- idempotent: true,
1407
- authority: initial.authority,
1408
- active: initial.active,
1409
- receipt: initial.receipt,
1410
- rolledBackToReceiptId: options.toReceiptId ?? 'previous-selection',
1411
- };
1412
- }
1413
- }
1414
- const target = readAgentAssetsRollbackTarget({
1415
- assetRoot,
1416
- ...(options.toReceiptId ? { receiptId: options.toReceiptId } : {}),
1417
- expectedMachineId: session.capability.machineIdentitySha256,
1418
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1419
- });
1420
- let authority = readAgentAssetsStoreSnapshot({
1421
- assetRoot,
1422
- expectedMachineId: session.capability.machineIdentitySha256,
1423
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1424
- })?.authority;
1425
- if (!authority)
1426
- throw writerError('not_found', 'Agent assets are not installed.');
1427
- authority = await issueWriterFence(session, assetRoot, authority, now);
1428
- const before = readAgentAssetsStoreSnapshot({
1429
- assetRoot,
1430
- expectedMachineId: session.capability.machineIdentitySha256,
1431
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1432
- });
1433
- if (!before?.active || !before.receipt)
1434
- throw writerError('not_found', 'Agent assets have no active receipt.');
1435
- const generation = before.active.generation + 1;
1436
- const receiptId = `receipt-${generation}-${authority.lastIssuedFenceEpoch}-${randomId()}`;
1437
- const receipt = {
1438
- schemaVersion: 1,
1439
- storeId: authority.storeId,
1440
- receiptId,
1441
- operationId,
1442
- operation: 'rollback',
1443
- generation,
1444
- createdAt: now,
1445
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1446
- authorityRevision: authority.authorityRevision,
1447
- previousReceiptId: before.receipt.receiptId,
1448
- previousReceiptSha256: canonicalJsonSha256V1(before.receipt),
1449
- core: target.target.core,
1450
- assets: target.target.assets,
1451
- };
1452
- const receiptSha256 = canonicalJsonSha256V1(receipt);
1453
- await session.publishFileNoReplace(`state/receipts/${generation}-${receiptId}.json`, jsonBytes(receipt));
1454
- const active = {
1455
- schemaVersion: 1,
1456
- storeId: authority.storeId,
1457
- generation,
1458
- receiptId,
1459
- receiptSha256,
1460
- writerFenceEpoch: authority.lastIssuedFenceEpoch,
1461
- authorityRevision: authority.authorityRevision,
1462
- updatedAt: now,
1463
- };
1464
- await session.publishFileReplace('state/active.json', sha256(readManagedBytes(assetRoot, 'state/active.json')), jsonBytes(active));
1465
- const after = readAgentAssetsStoreSnapshot({
1466
- assetRoot,
1467
- expectedMachineId: session.capability.machineIdentitySha256,
1468
- expectedRootIdentitySha256: session.capability.rootIdentitySha256,
1469
- });
1470
- if (!after?.active || after.active.receiptSha256 !== receiptSha256) {
1471
- throw writerError('atomic_replace_blocked', 'Rollback activation pointer did not publish the exact receipt.');
1472
- }
1473
- return {
1474
- idempotent: false,
1475
- authority: after.authority,
1476
- active: after.active,
1477
- receipt: after.receipt,
1478
- rolledBackToReceiptId: target.target.receiptId,
1479
- };
1480
- }
1481
- finally {
1482
- await session.close();
1483
- }
1484
- }