@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,1212 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, } from 'node:fs';
3
- import path from 'node:path';
4
- import { AgentAssetsError } from './envelope.js';
5
- import { canonicalPackageSha256V1, sha256Bytes, validatePackageManifestStructureV1, validatePortablePackageV1, } from './package-manifest.js';
6
- import { portablePackageCaseKeyV1, validatePortablePackagePath } from './portable-path.js';
7
- const SHA256_HEX = /^[a-f0-9]{64}$/;
8
- const MAX_STATE_BYTES = 1024 * 1024;
9
- const MAX_STORE_DIRECTORY_ENTRIES = 10_000;
10
- const MAX_STAGING_TREE_ENTRIES = 10_000;
11
- const MAX_STAGING_TREE_DEPTH = 64;
12
- const STAGING_OPERATION_ID = /^(?:[a-f0-9]{40}(?:\.[a-f0-9]{40})?|[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})$/;
13
- const AUTHORITY_KEYS = [
14
- 'schemaVersion',
15
- 'storeId',
16
- 'authorityRevision',
17
- 'boundMachineId',
18
- 'rootIdentitySha256',
19
- 'publicationCapability',
20
- 'capabilityEvidenceSha256',
21
- 'lastIssuedFenceEpoch',
22
- 'previousAuthoritySha256',
23
- 'createdAt',
24
- 'updatedAt',
25
- ];
26
- const ACTIVE_KEYS = [
27
- 'schemaVersion',
28
- 'storeId',
29
- 'generation',
30
- 'receiptId',
31
- 'receiptSha256',
32
- 'writerFenceEpoch',
33
- 'authorityRevision',
34
- 'updatedAt',
35
- ];
36
- const RECEIPT_KEYS = [
37
- 'schemaVersion',
38
- 'storeId',
39
- 'receiptId',
40
- 'operationId',
41
- 'operation',
42
- 'generation',
43
- 'createdAt',
44
- 'writerFenceEpoch',
45
- 'authorityRevision',
46
- 'previousReceiptId',
47
- 'previousReceiptSha256',
48
- 'core',
49
- 'assets',
50
- ];
51
- const PACKAGE_REF_KEYS = [
52
- 'name',
53
- 'version',
54
- 'versionId',
55
- 'packageSha256',
56
- 'entryFile',
57
- 'origin',
58
- 'trustClass',
59
- ];
60
- const PIN_KEYS = [
61
- 'schemaVersion',
62
- 'storeId',
63
- 'leaseId',
64
- 'packageSha256',
65
- 'owner',
66
- 'createdAt',
67
- 'expiresAt',
68
- 'writerFenceEpoch',
69
- 'authorityRevision',
70
- ];
71
- const MAINTENANCE_POINTER_KEYS = [
72
- 'schemaVersion',
73
- 'storeId',
74
- 'receiptId',
75
- 'receiptSha256',
76
- 'writerFenceEpoch',
77
- 'authorityRevision',
78
- 'updatedAt',
79
- ];
80
- const MAINTENANCE_RECEIPT_KEYS = [
81
- 'schemaVersion',
82
- 'storeId',
83
- 'receiptId',
84
- 'operationId',
85
- 'operation',
86
- 'createdAt',
87
- 'writerFenceEpoch',
88
- 'authorityRevision',
89
- 'previousReceiptId',
90
- 'previousReceiptSha256',
91
- 'protectedPackageSha256s',
92
- 'removedPackageSha256s',
93
- 'affectedManagedPaths',
94
- ];
95
- function storeError(code, message, details) {
96
- return new AgentAssetsError(code, message, {
97
- nextActions: ['Run `aops assets status --verify full --json` before retrying.'],
98
- ...(details === undefined ? {} : { details }),
99
- });
100
- }
101
- function isRecord(value) {
102
- return typeof value === 'object' && value !== null && !Array.isArray(value);
103
- }
104
- function exactRecord(value, keys, label) {
105
- if (!isRecord(value))
106
- throw storeError('schema_incompatible', `${label} must be an object.`);
107
- const actual = Object.keys(value).sort();
108
- const expected = [...keys].sort();
109
- if (JSON.stringify(actual) !== JSON.stringify(expected)) {
110
- throw storeError('schema_incompatible', `${label} has missing or unknown fields.`, { actual, expected });
111
- }
112
- return value;
113
- }
114
- function stringField(record, key, label, minimumLength = 1) {
115
- const value = record[key];
116
- if (typeof value !== 'string' || value.length < minimumLength) {
117
- throw storeError('schema_incompatible', `${label}.${key} must be a non-empty string.`);
118
- }
119
- return value;
120
- }
121
- function integerField(record, key, label, minimum) {
122
- const value = record[key];
123
- if (!Number.isSafeInteger(value) || value < minimum) {
124
- throw storeError('schema_incompatible', `${label}.${key} must be an integer greater than or equal to ${minimum}.`);
125
- }
126
- return value;
127
- }
128
- function shaField(record, key, label) {
129
- const value = stringField(record, key, label);
130
- if (!SHA256_HEX.test(value))
131
- throw storeError('schema_incompatible', `${label}.${key} must be lowercase SHA-256.`);
132
- return value;
133
- }
134
- function nullableShaField(record, key, label) {
135
- if (record[key] === null)
136
- return null;
137
- return shaField(record, key, label);
138
- }
139
- function dateField(record, key, label) {
140
- const value = stringField(record, key, label);
141
- if (!Number.isFinite(Date.parse(value)))
142
- throw storeError('schema_incompatible', `${label}.${key} must be an ISO date-time.`);
143
- return value;
144
- }
145
- function stringArrayField(record, key, label, validate) {
146
- const value = record[key];
147
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string' || !entry || (validate && !validate(entry)))) {
148
- throw storeError('schema_incompatible', `${label}.${key} must be an array of valid non-empty strings.`);
149
- }
150
- if (new Set(value).size !== value.length) {
151
- throw storeError('schema_incompatible', `${label}.${key} must not contain duplicates.`);
152
- }
153
- return value;
154
- }
155
- function enumField(record, key, values, label) {
156
- const value = record[key];
157
- if (typeof value !== 'string' || !values.includes(value)) {
158
- throw storeError('schema_incompatible', `${label}.${key} is not a supported value.`);
159
- }
160
- return value;
161
- }
162
- function assertSchemaV1(record, label) {
163
- if (record.schemaVersion !== 1)
164
- throw storeError('schema_incompatible', `${label}.schemaVersion must equal 1.`);
165
- }
166
- export function canonicalJsonV1(value) {
167
- if (value === null || typeof value === 'boolean' || typeof value === 'string')
168
- return JSON.stringify(value);
169
- if (typeof value === 'number') {
170
- if (!Number.isFinite(value))
171
- throw storeError('schema_incompatible', 'Canonical JSON cannot contain non-finite numbers.');
172
- return JSON.stringify(value);
173
- }
174
- if (Array.isArray(value))
175
- return `[${value.map((entry) => canonicalJsonV1(entry)).join(',')}]`;
176
- if (!isRecord(value))
177
- throw storeError('schema_incompatible', 'Canonical JSON contains a non-JSON value.');
178
- return `{${Object.keys(value)
179
- .sort()
180
- .map((key) => `${JSON.stringify(key)}:${canonicalJsonV1(value[key])}`)
181
- .join(',')}}`;
182
- }
183
- export function canonicalJsonSha256V1(value) {
184
- return createHash('sha256').update(canonicalJsonV1(value), 'utf8').digest('hex');
185
- }
186
- function assertRoot(assetRoot) {
187
- if (!path.isAbsolute(assetRoot))
188
- throw storeError('store_identity_mismatch', 'The agent-assets root must be absolute.');
189
- const normalized = path.normalize(assetRoot);
190
- if (!existsSync(normalized))
191
- return null;
192
- const stat = lstatSync(normalized);
193
- if (stat.isSymbolicLink() || !stat.isDirectory()) {
194
- throw storeError('link_unsafe_path', 'The agent-assets root must be a real directory, not a link or special file.');
195
- }
196
- return { assetRoot: normalized, realRoot: realpathSync.native(normalized) };
197
- }
198
- function safeChild(root, segments) {
199
- for (const segment of segments) {
200
- if (!segment || segment === '.' || segment === '..' || segment.includes('/') || segment.includes('\\')) {
201
- throw storeError('link_unsafe_path', 'A managed store path contains an unsafe segment.');
202
- }
203
- }
204
- const candidate = path.join(root.assetRoot, ...segments);
205
- const relative = path.relative(root.assetRoot, candidate);
206
- if (relative.startsWith('..') || path.isAbsolute(relative))
207
- throw storeError('link_unsafe_path', 'Managed path escaped the store root.');
208
- let cursor = root.assetRoot;
209
- for (const segment of segments) {
210
- cursor = path.join(cursor, segment);
211
- if (!existsSync(cursor))
212
- break;
213
- const stat = lstatSync(cursor);
214
- if (stat.isSymbolicLink())
215
- throw storeError('link_unsafe_path', 'Managed store paths may not traverse links.', { path: cursor });
216
- }
217
- if (existsSync(candidate)) {
218
- const realCandidate = realpathSync.native(candidate);
219
- const realRelative = path.relative(root.realRoot, realCandidate);
220
- if (realRelative.startsWith('..') || path.isAbsolute(realRelative)) {
221
- throw storeError('link_unsafe_path', 'Managed path resolves outside the store root.');
222
- }
223
- }
224
- return candidate;
225
- }
226
- function requiredFile(root, segments) {
227
- const candidate = safeChild(root, segments);
228
- if (!existsSync(candidate))
229
- throw storeError('not_found', 'Required agent-assets state is missing.', { relativePath: segments.join('/') });
230
- const stat = lstatSync(candidate);
231
- if (!stat.isFile() || stat.size < 1 || stat.size > MAX_STATE_BYTES) {
232
- throw storeError('schema_incompatible', 'Managed state file has an invalid type or size.', {
233
- relativePath: segments.join('/'),
234
- byteLength: stat.size,
235
- });
236
- }
237
- return candidate;
238
- }
239
- function readJsonFile(root, segments) {
240
- const filePath = requiredFile(root, segments);
241
- try {
242
- return JSON.parse(readFileSync(filePath, 'utf8'));
243
- }
244
- catch (error) {
245
- throw storeError('schema_incompatible', 'Managed state file is not valid JSON.', {
246
- relativePath: segments.join('/'),
247
- cause: error instanceof Error ? error.message : String(error),
248
- });
249
- }
250
- }
251
- export function parseStoreAuthorityV1(value) {
252
- const label = 'StoreAuthorityV1';
253
- const record = exactRecord(value, AUTHORITY_KEYS, label);
254
- assertSchemaV1(record, label);
255
- const authorityRevision = integerField(record, 'authorityRevision', label, 1);
256
- const previousAuthoritySha256 = nullableShaField(record, 'previousAuthoritySha256', label);
257
- if ((authorityRevision === 1) !== (previousAuthoritySha256 === null)) {
258
- throw storeError('schema_incompatible', 'StoreAuthorityV1 previous-authority lineage is inconsistent.');
259
- }
260
- return {
261
- schemaVersion: 1,
262
- storeId: stringField(record, 'storeId', label, 16),
263
- authorityRevision,
264
- boundMachineId: stringField(record, 'boundMachineId', label),
265
- rootIdentitySha256: shaField(record, 'rootIdentitySha256', label),
266
- publicationCapability: enumField(record, 'publicationCapability', [
267
- 'posix-durable-v1',
268
- 'macos-durable-v1',
269
- 'windows-ntfs-crash-recoverable-v1',
270
- ], label),
271
- capabilityEvidenceSha256: shaField(record, 'capabilityEvidenceSha256', label),
272
- lastIssuedFenceEpoch: integerField(record, 'lastIssuedFenceEpoch', label, 0),
273
- previousAuthoritySha256,
274
- createdAt: dateField(record, 'createdAt', label),
275
- updatedAt: dateField(record, 'updatedAt', label),
276
- };
277
- }
278
- export function parseActivePointerV1(value) {
279
- const label = 'ActivePointerV1';
280
- const record = exactRecord(value, ACTIVE_KEYS, label);
281
- assertSchemaV1(record, label);
282
- return {
283
- schemaVersion: 1,
284
- storeId: stringField(record, 'storeId', label, 16),
285
- generation: integerField(record, 'generation', label, 1),
286
- receiptId: stringField(record, 'receiptId', label),
287
- receiptSha256: shaField(record, 'receiptSha256', label),
288
- writerFenceEpoch: integerField(record, 'writerFenceEpoch', label, 1),
289
- authorityRevision: integerField(record, 'authorityRevision', label, 1),
290
- updatedAt: dateField(record, 'updatedAt', label),
291
- };
292
- }
293
- export function parsePackageRefV1(value) {
294
- const label = 'PackageRefV1';
295
- const record = exactRecord(value, PACKAGE_REF_KEYS, label);
296
- return {
297
- name: stringField(record, 'name', label),
298
- version: stringField(record, 'version', label),
299
- versionId: stringField(record, 'versionId', label),
300
- packageSha256: shaField(record, 'packageSha256', label),
301
- entryFile: stringField(record, 'entryFile', label),
302
- origin: enumField(record, 'origin', ['bundled', 'hosted-cache', 'reserved-catalog'], label),
303
- trustClass: enumField(record, 'trustClass', [
304
- 'signed-community-release',
305
- 'verified-hosted-package',
306
- ], label),
307
- };
308
- }
309
- export function parseActivationReceiptV1(value) {
310
- const label = 'ActivationReceiptV1';
311
- const record = exactRecord(value, RECEIPT_KEYS, label);
312
- assertSchemaV1(record, label);
313
- const generation = integerField(record, 'generation', label, 1);
314
- const previousReceiptId = record.previousReceiptId === null
315
- ? null
316
- : stringField(record, 'previousReceiptId', label);
317
- const previousReceiptSha256 = nullableShaField(record, 'previousReceiptSha256', label);
318
- if ((generation === 1) !== (previousReceiptId === null && previousReceiptSha256 === null)) {
319
- throw storeError('schema_incompatible', 'ActivationReceiptV1 previous-receipt lineage is inconsistent.');
320
- }
321
- if (!Array.isArray(record.assets))
322
- throw storeError('schema_incompatible', 'ActivationReceiptV1.assets must be an array.');
323
- const assets = record.assets.map(parsePackageRefV1);
324
- const packageIdentities = assets.map((entry) => canonicalJsonV1(entry));
325
- if (new Set(packageIdentities).size !== packageIdentities.length) {
326
- throw storeError('schema_incompatible', 'ActivationReceiptV1.assets contains duplicate package references.');
327
- }
328
- return {
329
- schemaVersion: 1,
330
- storeId: stringField(record, 'storeId', label, 16),
331
- receiptId: stringField(record, 'receiptId', label),
332
- operationId: stringField(record, 'operationId', label),
333
- operation: enumField(record, 'operation', [
334
- 'install',
335
- 'update',
336
- 'rollback',
337
- 'repair',
338
- 'migrate',
339
- ], label),
340
- generation,
341
- createdAt: dateField(record, 'createdAt', label),
342
- writerFenceEpoch: integerField(record, 'writerFenceEpoch', label, 1),
343
- authorityRevision: integerField(record, 'authorityRevision', label, 1),
344
- previousReceiptId,
345
- previousReceiptSha256,
346
- core: parsePackageRefV1(record.core),
347
- assets,
348
- };
349
- }
350
- export function parseExactVersionPinV1(value) {
351
- const label = 'ExactVersionPinV1';
352
- const record = exactRecord(value, PIN_KEYS, label);
353
- assertSchemaV1(record, label);
354
- return {
355
- schemaVersion: 1,
356
- storeId: stringField(record, 'storeId', label, 16),
357
- leaseId: stringField(record, 'leaseId', label),
358
- packageSha256: shaField(record, 'packageSha256', label),
359
- owner: stringField(record, 'owner', label),
360
- createdAt: dateField(record, 'createdAt', label),
361
- expiresAt: dateField(record, 'expiresAt', label),
362
- writerFenceEpoch: integerField(record, 'writerFenceEpoch', label, 1),
363
- authorityRevision: integerField(record, 'authorityRevision', label, 1),
364
- };
365
- }
366
- export function parseMaintenancePointerV1(value) {
367
- const label = 'MaintenancePointerV1';
368
- const record = exactRecord(value, MAINTENANCE_POINTER_KEYS, label);
369
- assertSchemaV1(record, label);
370
- return {
371
- schemaVersion: 1,
372
- storeId: stringField(record, 'storeId', label, 16),
373
- receiptId: stringField(record, 'receiptId', label),
374
- receiptSha256: shaField(record, 'receiptSha256', label),
375
- writerFenceEpoch: integerField(record, 'writerFenceEpoch', label, 1),
376
- authorityRevision: integerField(record, 'authorityRevision', label, 1),
377
- updatedAt: dateField(record, 'updatedAt', label),
378
- };
379
- }
380
- export function parseMaintenanceReceiptV1(value) {
381
- const label = 'MaintenanceReceiptV1';
382
- const record = exactRecord(value, MAINTENANCE_RECEIPT_KEYS, label);
383
- assertSchemaV1(record, label);
384
- const previousReceiptId = record.previousReceiptId === null
385
- ? null
386
- : stringField(record, 'previousReceiptId', label);
387
- const previousReceiptSha256 = nullableShaField(record, 'previousReceiptSha256', label);
388
- if ((previousReceiptId === null) !== (previousReceiptSha256 === null)) {
389
- throw storeError('schema_incompatible', 'MaintenanceReceiptV1 previous-receipt lineage is inconsistent.');
390
- }
391
- return {
392
- schemaVersion: 1,
393
- storeId: stringField(record, 'storeId', label, 16),
394
- receiptId: stringField(record, 'receiptId', label),
395
- operationId: stringField(record, 'operationId', label),
396
- operation: enumField(record, 'operation', ['pin', 'prune'], label),
397
- createdAt: dateField(record, 'createdAt', label),
398
- writerFenceEpoch: integerField(record, 'writerFenceEpoch', label, 1),
399
- authorityRevision: integerField(record, 'authorityRevision', label, 1),
400
- previousReceiptId,
401
- previousReceiptSha256,
402
- protectedPackageSha256s: stringArrayField(record, 'protectedPackageSha256s', label, (entry) => SHA256_HEX.test(entry)),
403
- removedPackageSha256s: stringArrayField(record, 'removedPackageSha256s', label, (entry) => SHA256_HEX.test(entry)),
404
- affectedManagedPaths: stringArrayField(record, 'affectedManagedPaths', label, (entry) => {
405
- const normalized = entry.replaceAll('\\', '/');
406
- return normalized === entry
407
- && !entry.startsWith('/')
408
- && entry.split('/').every((segment) => Boolean(segment) && segment !== '.' && segment !== '..');
409
- }),
410
- };
411
- }
412
- function safeDirectoryEntries(root, segments) {
413
- const directory = safeChild(root, segments);
414
- if (!existsSync(directory))
415
- return [];
416
- const stat = lstatSync(directory);
417
- if (!stat.isDirectory())
418
- throw storeError('link_unsafe_path', 'Managed store directory is not a real directory.');
419
- const entries = readdirSync(directory);
420
- if (entries.length > MAX_STORE_DIRECTORY_ENTRIES) {
421
- throw storeError('schema_incompatible', 'Managed store directory exceeds the bounded entry count.');
422
- }
423
- return entries.sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
424
- }
425
- function stagingTreeEntry(root, segments, kind) {
426
- const candidate = safeChild(root, segments);
427
- const stat = lstatSync(candidate);
428
- if (stat.isSymbolicLink()) {
429
- throw storeError('link_unsafe_path', 'Managed staging cleanup may not traverse links.', {
430
- relativePath: segments.join('/'),
431
- });
432
- }
433
- if ((kind === 'directory' && !stat.isDirectory()) || (kind === 'file' && !stat.isFile())) {
434
- throw storeError('link_unsafe_path', 'Managed staging contains a special or unexpected node type.', {
435
- relativePath: segments.join('/'),
436
- });
437
- }
438
- return {
439
- relativePath: segments.join('/'),
440
- kind,
441
- size: stat.size,
442
- modifiedAtMs: stat.mtimeMs,
443
- changedAtMs: stat.ctimeMs,
444
- device: stat.dev,
445
- inode: stat.ino,
446
- };
447
- }
448
- function inspectStagingFilesTree(root, operationId, entries, portableCaseKeys, relativeSegments = []) {
449
- if (relativeSegments.length > MAX_STAGING_TREE_DEPTH) {
450
- throw storeError('schema_incompatible', 'Managed staging exceeds the bounded directory depth.');
451
- }
452
- const directorySegments = ['staging', operationId, 'files', ...relativeSegments];
453
- for (const name of safeDirectoryEntries(root, directorySegments)) {
454
- if (entries.length >= MAX_STAGING_TREE_ENTRIES) {
455
- throw storeError('schema_incompatible', 'Managed staging exceeds the bounded total entry count.');
456
- }
457
- const packagePath = [...relativeSegments, name].join('/');
458
- const portable = validatePortablePackagePath(packagePath);
459
- if (!portable.ok) {
460
- throw storeError('invalid_package_path', 'Managed staging contains a non-portable package path.', {
461
- relativePath: packagePath,
462
- issues: portable.issues.map((entry) => entry.code),
463
- });
464
- }
465
- const caseKey = portablePackageCaseKeyV1(portable.normalizedPath);
466
- if (portableCaseKeys.has(caseKey)) {
467
- throw storeError('invalid_package_path', 'Managed staging contains a portable case-fold collision.', {
468
- relativePath: packagePath,
469
- });
470
- }
471
- portableCaseKeys.add(caseKey);
472
- const segments = [...directorySegments, name];
473
- const candidate = safeChild(root, segments);
474
- const stat = lstatSync(candidate);
475
- if (stat.isSymbolicLink()) {
476
- throw storeError('link_unsafe_path', 'Managed staging cleanup may not traverse links.', {
477
- relativePath: segments.join('/'),
478
- });
479
- }
480
- if (stat.isDirectory()) {
481
- entries.push(stagingTreeEntry(root, segments, 'directory'));
482
- inspectStagingFilesTree(root, operationId, entries, portableCaseKeys, [...relativeSegments, name]);
483
- continue;
484
- }
485
- if (!stat.isFile()) {
486
- throw storeError('link_unsafe_path', 'Managed staging contains a special node.', {
487
- relativePath: segments.join('/'),
488
- });
489
- }
490
- entries.push(stagingTreeEntry(root, segments, 'file'));
491
- }
492
- }
493
- export function readAgentAssetsStagingCleanupPlan(options) {
494
- const snapshot = readAgentAssetsStoreSnapshot(options);
495
- if (!snapshot)
496
- throw storeError('not_found', 'Agent assets are not installed; staging ownership cannot be proven.');
497
- const root = assertRoot(options.assetRoot);
498
- if (!root)
499
- throw storeError('not_found', 'Agent assets disappeared during staging inspection.');
500
- const removableManagedPaths = [];
501
- const identityEntries = [];
502
- for (const operationId of safeDirectoryEntries(root, ['staging'])) {
503
- if (!STAGING_OPERATION_ID.test(operationId)) {
504
- throw storeError('schema_incompatible', 'Staging contains an entry outside the managed operation namespace.', {
505
- entryName: operationId,
506
- });
507
- }
508
- if (identityEntries.length >= MAX_STAGING_TREE_ENTRIES) {
509
- throw storeError('schema_incompatible', 'Managed staging exceeds the bounded total entry count.');
510
- }
511
- const operationSegments = ['staging', operationId];
512
- identityEntries.push(stagingTreeEntry(root, operationSegments, 'directory'));
513
- const topLevelEntries = safeDirectoryEntries(root, operationSegments);
514
- if (topLevelEntries.some((entry) => entry !== 'files' && entry !== 'manifest.json')) {
515
- throw storeError('schema_incompatible', 'Managed staging has an unexpected top-level entry.', {
516
- operationId,
517
- entries: topLevelEntries,
518
- });
519
- }
520
- if (topLevelEntries.includes('manifest.json')) {
521
- const manifestValue = readJsonFile(root, [...operationSegments, 'manifest.json']);
522
- const manifest = validatePackageManifestStructureV1(manifestValue);
523
- if (!manifest.ok) {
524
- throw storeError('schema_incompatible', 'Managed staging manifest is not a valid PackageManifestV1.', {
525
- operationId,
526
- issues: manifest.issues,
527
- });
528
- }
529
- identityEntries.push(stagingTreeEntry(root, [...operationSegments, 'manifest.json'], 'file'));
530
- }
531
- if (topLevelEntries.includes('files')) {
532
- identityEntries.push(stagingTreeEntry(root, [...operationSegments, 'files'], 'directory'));
533
- inspectStagingFilesTree(root, operationId, identityEntries, new Set());
534
- }
535
- removableManagedPaths.push(`staging/${operationId}`);
536
- }
537
- return Object.freeze({
538
- authority: structuredClone(snapshot.authority),
539
- active: snapshot.active === null ? null : structuredClone(snapshot.active),
540
- receipt: snapshot.receipt === null ? null : structuredClone(snapshot.receipt),
541
- removableManagedPaths: Object.freeze(removableManagedPaths),
542
- treeIdentitySha256: canonicalJsonSha256V1(identityEntries),
543
- });
544
- }
545
- function findReceipt(root, generation, receiptId, expectedSha256) {
546
- const names = safeDirectoryEntries(root, ['state', 'receipts'])
547
- .filter((name) => name.startsWith(`${generation}-`) && name.endsWith('.json'));
548
- let found;
549
- for (const name of names) {
550
- const value = readJsonFile(root, ['state', 'receipts', name]);
551
- const receipt = parseActivationReceiptV1(value);
552
- if (receipt.generation !== generation || receipt.receiptId !== receiptId)
553
- continue;
554
- if (found)
555
- throw storeError('schema_incompatible', 'Multiple receipt files claim the same activation identity.');
556
- if (canonicalJsonSha256V1(value) !== expectedSha256) {
557
- throw storeError('hash_mismatch', 'Activation receipt digest does not match its pointer.');
558
- }
559
- found = receipt;
560
- }
561
- if (!found)
562
- throw storeError('not_found', 'The activation receipt referenced by state/active.json was not found.');
563
- return found;
564
- }
565
- function assertAuthorityCoherence(authority, active, receipt) {
566
- if (active.storeId !== authority.storeId || receipt.storeId !== authority.storeId) {
567
- throw storeError('store_identity_mismatch', 'Store records disagree on storeId.');
568
- }
569
- if (active.generation !== receipt.generation || active.receiptId !== receipt.receiptId) {
570
- throw storeError('schema_incompatible', 'The active pointer does not identify the loaded receipt.');
571
- }
572
- if (active.writerFenceEpoch !== receipt.writerFenceEpoch || active.authorityRevision !== receipt.authorityRevision) {
573
- throw storeError('schema_incompatible', 'The active pointer and receipt disagree on fencing authority.');
574
- }
575
- if (active.authorityRevision > authority.authorityRevision ||
576
- active.writerFenceEpoch > authority.lastIssuedFenceEpoch ||
577
- receipt.authorityRevision > authority.authorityRevision ||
578
- receipt.writerFenceEpoch > authority.lastIssuedFenceEpoch) {
579
- throw storeError('concurrent_writer', 'Active state claims an authority or fence epoch newer than store authority.');
580
- }
581
- }
582
- function openStore(options) {
583
- const root = assertRoot(options.assetRoot);
584
- if (!root)
585
- return null;
586
- const authority = parseStoreAuthorityV1(readJsonFile(root, ['state', 'store-authority.json']));
587
- if (options.expectedMachineId !== undefined && authority.boundMachineId !== options.expectedMachineId) {
588
- throw storeError('different_machine_store', 'The store is bound to another machine identity.');
589
- }
590
- if (options.expectedRootIdentitySha256 !== undefined &&
591
- authority.rootIdentitySha256 !== options.expectedRootIdentitySha256) {
592
- throw storeError('store_identity_mismatch', 'The store root identity does not match the current root.');
593
- }
594
- const active = parseActivePointerV1(readJsonFile(root, ['state', 'active.json']));
595
- const receipt = findReceipt(root, active.generation, active.receiptId, active.receiptSha256);
596
- assertAuthorityCoherence(authority, active, receipt);
597
- return { ...root, authority, active, receipt };
598
- }
599
- export function readAgentAssetsStoreSnapshot(options) {
600
- const root = assertRoot(options.assetRoot);
601
- if (!root)
602
- return null;
603
- const authorityPath = safeChild(root, ['state', 'store-authority.json']);
604
- if (!existsSync(authorityPath))
605
- return null;
606
- const authority = parseStoreAuthorityV1(readJsonFile(root, ['state', 'store-authority.json']));
607
- if (options.expectedMachineId !== undefined && authority.boundMachineId !== options.expectedMachineId) {
608
- throw storeError('different_machine_store', 'The store is bound to another machine identity.');
609
- }
610
- if (options.expectedRootIdentitySha256 !== undefined
611
- && authority.rootIdentitySha256 !== options.expectedRootIdentitySha256) {
612
- throw storeError('store_identity_mismatch', 'The store root identity does not match the current root.');
613
- }
614
- const activePath = safeChild(root, ['state', 'active.json']);
615
- if (!existsSync(activePath))
616
- return { authority: structuredClone(authority), active: null, receipt: null };
617
- const active = parseActivePointerV1(readJsonFile(root, ['state', 'active.json']));
618
- const receipt = findReceipt(root, active.generation, active.receiptId, active.receiptSha256);
619
- assertAuthorityCoherence(authority, active, receipt);
620
- return {
621
- authority: structuredClone(authority),
622
- active: structuredClone(active),
623
- receipt: structuredClone(receipt),
624
- };
625
- }
626
- function maintenanceReceiptFileName(receiptId) {
627
- return `${createHash('sha256').update(`aops-agent-assets-maintenance-receipt-v1\0${receiptId}`, 'utf8').digest('hex')}.json`;
628
- }
629
- export function readAgentAssetsMaintenanceHead(options) {
630
- const root = assertRoot(options.assetRoot);
631
- if (!root)
632
- return null;
633
- const authority = parseStoreAuthorityV1(readJsonFile(root, ['state', 'store-authority.json']));
634
- if (options.expectedMachineId !== undefined && authority.boundMachineId !== options.expectedMachineId) {
635
- throw storeError('different_machine_store', 'The store is bound to another machine identity.');
636
- }
637
- if (options.expectedRootIdentitySha256 !== undefined
638
- && authority.rootIdentitySha256 !== options.expectedRootIdentitySha256) {
639
- throw storeError('store_identity_mismatch', 'The store root identity does not match the current root.');
640
- }
641
- const pointerPath = safeChild(root, ['state', 'maintenance.json']);
642
- if (!existsSync(pointerPath))
643
- return null;
644
- const pointer = parseMaintenancePointerV1(readJsonFile(root, ['state', 'maintenance.json']));
645
- if (pointer.storeId !== authority.storeId) {
646
- throw storeError('store_identity_mismatch', 'Maintenance pointer belongs to another store.');
647
- }
648
- if (pointer.authorityRevision > authority.authorityRevision
649
- || pointer.writerFenceEpoch > authority.lastIssuedFenceEpoch) {
650
- throw storeError('concurrent_writer', 'Maintenance pointer claims a future authority or fence epoch.');
651
- }
652
- const value = readJsonFile(root, [
653
- 'state',
654
- 'maintenance-receipts',
655
- maintenanceReceiptFileName(pointer.receiptId),
656
- ]);
657
- if (canonicalJsonSha256V1(value) !== pointer.receiptSha256) {
658
- throw storeError('hash_mismatch', 'Maintenance receipt digest does not match its pointer.');
659
- }
660
- const receipt = parseMaintenanceReceiptV1(value);
661
- if (receipt.storeId !== authority.storeId
662
- || receipt.receiptId !== pointer.receiptId
663
- || receipt.authorityRevision !== pointer.authorityRevision
664
- || receipt.writerFenceEpoch !== pointer.writerFenceEpoch) {
665
- throw storeError('schema_incompatible', 'Maintenance pointer and receipt disagree on identity or fencing authority.');
666
- }
667
- return { pointer, receipt };
668
- }
669
- function assertPackageRefMatchesManifest(packageRef, manifest) {
670
- if (packageRef.name !== manifest.name ||
671
- packageRef.version !== manifest.version ||
672
- packageRef.versionId !== manifest.versionId ||
673
- packageRef.packageSha256 !== manifest.packageSha256 ||
674
- packageRef.entryFile !== manifest.entryFile ||
675
- packageRef.trustClass !== manifest.provenance.trustClass) {
676
- throw storeError('hash_mismatch', 'Activation package reference does not match the installed manifest.', {
677
- packageSha256: packageRef.packageSha256,
678
- });
679
- }
680
- if ((packageRef.origin === 'bundled' && manifest.assetKind !== 'community-core') ||
681
- (packageRef.origin !== 'bundled' && manifest.assetKind !== 'skill-package')) {
682
- throw storeError('untrusted_origin', 'Package origin and signed manifest kind disagree.');
683
- }
684
- }
685
- function readInstalledManifest(root, packageSha256) {
686
- if (!SHA256_HEX.test(packageSha256))
687
- throw storeError('schema_incompatible', 'Package digest is invalid.');
688
- const value = readJsonFile(root, ['core', packageSha256, 'manifest.json']);
689
- const structural = validatePackageManifestStructureV1(value);
690
- if (!structural.ok) {
691
- throw storeError('schema_incompatible', 'Installed package manifest is invalid.', { issues: structural.issues });
692
- }
693
- if (structural.value.packageSha256 !== packageSha256) {
694
- throw storeError('hash_mismatch', 'Installed package directory does not match the manifest package digest.');
695
- }
696
- if (canonicalPackageSha256V1(structural.value.files) !== packageSha256) {
697
- throw storeError('hash_mismatch', 'Installed manifest file records do not match packageSha256.');
698
- }
699
- return { manifest: structural.value, value };
700
- }
701
- function readPackageFile(root, packageSha256, relativePath, expectedStats) {
702
- const segments = relativePath.split('/');
703
- const filePath = requiredFile(root, ['core', packageSha256, 'files', ...segments]);
704
- const stat = lstatSync(filePath);
705
- if (expectedStats !== undefined && stat.size !== expectedStats.size) {
706
- throw storeError('hash_mismatch', 'Installed package file byte length does not match its manifest.');
707
- }
708
- return readFileSync(filePath);
709
- }
710
- function verifyPackage(root, packageRef, verify) {
711
- const { manifest } = readInstalledManifest(root, packageRef.packageSha256);
712
- assertPackageRefMatchesManifest(packageRef, manifest);
713
- if (packageRef.origin === 'reserved-catalog') {
714
- throw storeError('untrusted_origin', 'Reserved catalog packages are inert and cannot be active resolver sources.');
715
- }
716
- const entryRow = manifest.files.find((row) => row.path === manifest.entryFile);
717
- if (!entryRow)
718
- throw storeError('schema_incompatible', 'Installed package entry file is not declared.');
719
- const entryBytes = readPackageFile(root, packageRef.packageSha256, manifest.entryFile, { size: entryRow.byteLength });
720
- const entrySha256 = sha256Bytes(entryBytes);
721
- if (entrySha256 !== entryRow.sha256)
722
- throw storeError('hash_mismatch', 'Installed package entry digest does not match.');
723
- if (verify === 'full') {
724
- const transferFiles = manifest.files.map((row) => ({
725
- path: row.path,
726
- bytes: readPackageFile(root, packageRef.packageSha256, row.path, { size: row.byteLength }),
727
- }));
728
- const portable = validatePortablePackageV1(manifest, transferFiles);
729
- if (!portable.ok)
730
- throw storeError('hash_mismatch', 'Installed package failed full validation.', { issues: portable.issues });
731
- }
732
- return {
733
- manifest,
734
- entryPath: safeChild(root, ['core', packageRef.packageSha256, 'files', ...manifest.entryFile.split('/')]),
735
- entrySha256,
736
- };
737
- }
738
- export function verifyAgentAssetsPackageAtRoot(options) {
739
- const root = assertRoot(options.assetRoot);
740
- if (!root)
741
- throw storeError('not_found', 'Agent assets are not installed.');
742
- return verifyPackage(root, options.packageRef, options.verify ?? 'full');
743
- }
744
- export function readAgentAssetsRollbackTarget(options) {
745
- const root = openStore(options);
746
- if (!root)
747
- throw storeError('not_found', 'Agent assets are not installed.');
748
- const requested = options.receiptId?.trim();
749
- if (requested && requested === root.receipt.receiptId) {
750
- throw storeError('rollback_unavailable', 'The requested receipt is already active.');
751
- }
752
- let cursor = root.receipt;
753
- while (cursor.previousReceiptId !== null && cursor.previousReceiptSha256 !== null) {
754
- const previous = findReceipt(root, cursor.generation - 1, cursor.previousReceiptId, cursor.previousReceiptSha256);
755
- if (!requested || previous.receiptId === requested) {
756
- for (const packageRef of packageRefs(previous))
757
- verifyPackage(root, packageRef, 'full');
758
- return {
759
- authority: structuredClone(root.authority),
760
- active: structuredClone(root.active),
761
- current: structuredClone(root.receipt),
762
- target: structuredClone(previous),
763
- };
764
- }
765
- cursor = previous;
766
- }
767
- throw storeError('rollback_unavailable', requested
768
- ? 'The requested receipt is not in the active verified lineage.'
769
- : 'No previous activation receipt is available for rollback.');
770
- }
771
- function loadPreviousReceipt(root) {
772
- const { receipt } = root;
773
- if (receipt.generation === 1 || receipt.previousReceiptId === null || receipt.previousReceiptSha256 === null)
774
- return null;
775
- return findReceipt(root, receipt.generation - 1, receipt.previousReceiptId, receipt.previousReceiptSha256);
776
- }
777
- function loadUnexpiredPins(root, now) {
778
- const pins = [];
779
- for (const name of safeDirectoryEntries(root, ['state', 'pins'])) {
780
- if (!name.endsWith('.json'))
781
- throw storeError('schema_incompatible', 'Unknown entry exists in the managed pins directory.');
782
- const pin = parseExactVersionPinV1(readJsonFile(root, ['state', 'pins', name]));
783
- if (pin.storeId !== root.authority.storeId)
784
- throw storeError('store_identity_mismatch', 'Pin belongs to another store.');
785
- if (pin.authorityRevision > root.authority.authorityRevision || pin.writerFenceEpoch > root.authority.lastIssuedFenceEpoch) {
786
- throw storeError('concurrent_writer', 'Pin claims a future authority or fence epoch.');
787
- }
788
- if (Date.parse(pin.expiresAt) > now.getTime())
789
- pins.push(pin);
790
- }
791
- return pins;
792
- }
793
- function packageRefs(receipt) {
794
- return [receipt.core, ...receipt.assets];
795
- }
796
- function refsByDigest(...receipts) {
797
- const result = new Map();
798
- for (const receipt of receipts) {
799
- if (!receipt)
800
- continue;
801
- for (const packageRef of packageRefs(receipt))
802
- result.set(packageRef.packageSha256, packageRef);
803
- }
804
- return result;
805
- }
806
- function readAuthorityHistoryEvidence(root, current) {
807
- if (current.authorityRevision === 1) {
808
- return Object.freeze({ state: 'genesis', verifiedRevisionCount: 1 });
809
- }
810
- if (current.authorityRevision > MAX_STORE_DIRECTORY_ENTRIES + 1) {
811
- throw storeError('schema_incompatible', 'Store authority history exceeds the bounded v1 revision count.');
812
- }
813
- let cursor = current;
814
- let verifiedRevisionCount = 1;
815
- while (cursor.authorityRevision > 1) {
816
- const previousRevision = cursor.authorityRevision - 1;
817
- const expectedSha256 = cursor.previousAuthoritySha256;
818
- if (!expectedSha256) {
819
- throw storeError('schema_incompatible', 'Store authority history lost its previous revision digest.');
820
- }
821
- const segments = ['state', 'authorities', `${previousRevision}-${expectedSha256}.json`];
822
- const previousPath = safeChild(root, segments);
823
- if (!existsSync(previousPath)) {
824
- return Object.freeze({
825
- state: 'incomplete',
826
- verifiedRevisionCount,
827
- missingRevision: previousRevision,
828
- });
829
- }
830
- const value = readJsonFile(root, segments);
831
- if (canonicalJsonSha256V1(value) !== expectedSha256) {
832
- throw storeError('hash_mismatch', 'Store authority history digest does not match its immutable reference.', {
833
- authorityRevision: previousRevision,
834
- });
835
- }
836
- const previous = parseStoreAuthorityV1(value);
837
- if (previous.authorityRevision !== previousRevision
838
- || previous.storeId !== current.storeId
839
- || previous.boundMachineId !== current.boundMachineId
840
- || previous.rootIdentitySha256 !== current.rootIdentitySha256
841
- || previous.publicationCapability !== current.publicationCapability
842
- || previous.capabilityEvidenceSha256 !== current.capabilityEvidenceSha256
843
- || previous.createdAt !== current.createdAt
844
- || previous.lastIssuedFenceEpoch + 1 !== cursor.lastIssuedFenceEpoch) {
845
- throw storeError('schema_incompatible', 'Store authority history breaks immutable identity or fence lineage.', {
846
- authorityRevision: previousRevision,
847
- });
848
- }
849
- cursor = previous;
850
- verifiedRevisionCount += 1;
851
- }
852
- return Object.freeze({ state: 'verified', verifiedRevisionCount });
853
- }
854
- function recordedNativeIdentityEvidence(authority) {
855
- return Object.freeze({
856
- state: 'recorded-not-live-verified',
857
- boundMachineId: authority.boundMachineId,
858
- rootIdentitySha256: authority.rootIdentitySha256,
859
- liveProbe: 'unavailable-in-read-only-status-v1',
860
- });
861
- }
862
- function readActivationReceiptOrphanCount(root) {
863
- const receipts = [];
864
- const byIdentity = new Map();
865
- for (const name of safeDirectoryEntries(root, ['state', 'receipts'])) {
866
- if (!name.endsWith('.json')) {
867
- throw storeError('schema_incompatible', 'Unknown entry exists in the managed activation receipt directory.', {
868
- name,
869
- });
870
- }
871
- const receipt = parseActivationReceiptV1(readJsonFile(root, ['state', 'receipts', name]));
872
- if (receipt.storeId !== root.authority.storeId) {
873
- throw storeError('store_identity_mismatch', 'An activation receipt belongs to another store.');
874
- }
875
- const identity = `${receipt.generation}\0${receipt.receiptId}`;
876
- if (byIdentity.has(identity)) {
877
- throw storeError('schema_incompatible', 'Multiple receipt files claim the same activation identity.');
878
- }
879
- byIdentity.set(identity, receipt);
880
- receipts.push(receipt);
881
- }
882
- const lineage = new Set();
883
- let cursor = root.receipt;
884
- while (cursor) {
885
- const identity = `${cursor.generation}\0${cursor.receiptId}`;
886
- if (lineage.has(identity)) {
887
- throw storeError('schema_incompatible', 'The activation receipt lineage contains a cycle.');
888
- }
889
- const stored = byIdentity.get(identity);
890
- if (!stored || canonicalJsonSha256V1(stored) !== canonicalJsonSha256V1(cursor)) {
891
- throw storeError('hash_mismatch', 'The active receipt lineage is not present exactly in the immutable ledger.');
892
- }
893
- lineage.add(identity);
894
- if (cursor.previousReceiptId === null || cursor.previousReceiptSha256 === null) {
895
- cursor = null;
896
- continue;
897
- }
898
- const previousIdentity = `${cursor.generation - 1}\0${cursor.previousReceiptId}`;
899
- const previous = byIdentity.get(previousIdentity);
900
- if (!previous || canonicalJsonSha256V1(previous) !== cursor.previousReceiptSha256) {
901
- throw storeError('hash_mismatch', 'The activation receipt lineage does not match its previous receipt digest.');
902
- }
903
- cursor = previous;
904
- }
905
- return receipts.length - lineage.size;
906
- }
907
- function readMaintenanceReceiptOrphanCount(root, maintenance) {
908
- const receipts = [];
909
- const byId = new Map();
910
- for (const name of safeDirectoryEntries(root, ['state', 'maintenance-receipts'])) {
911
- if (!SHA256_HEX.test(name.slice(0, -5)) || !name.endsWith('.json')) {
912
- throw storeError('schema_incompatible', 'Unknown entry exists in the managed maintenance receipt directory.', {
913
- name,
914
- });
915
- }
916
- const value = readJsonFile(root, ['state', 'maintenance-receipts', name]);
917
- const receipt = parseMaintenanceReceiptV1(value);
918
- if (name !== maintenanceReceiptFileName(receipt.receiptId)) {
919
- throw storeError('hash_mismatch', 'A maintenance receipt is stored under the wrong immutable identity.', {
920
- name,
921
- receiptId: receipt.receiptId,
922
- });
923
- }
924
- if (receipt.storeId !== root.authority.storeId) {
925
- throw storeError('store_identity_mismatch', 'A maintenance receipt belongs to another store.');
926
- }
927
- if (byId.has(receipt.receiptId)) {
928
- throw storeError('schema_incompatible', 'Multiple maintenance receipts claim the same immutable identity.');
929
- }
930
- byId.set(receipt.receiptId, receipt);
931
- receipts.push(receipt);
932
- }
933
- const lineage = new Set();
934
- let cursor = maintenance?.receipt ?? null;
935
- while (cursor) {
936
- if (lineage.has(cursor.receiptId)) {
937
- throw storeError('schema_incompatible', 'The maintenance receipt lineage contains a cycle.');
938
- }
939
- const stored = byId.get(cursor.receiptId);
940
- if (!stored || canonicalJsonSha256V1(stored) !== canonicalJsonSha256V1(cursor)) {
941
- throw storeError('hash_mismatch', 'The maintenance head is not present exactly in the immutable ledger.');
942
- }
943
- lineage.add(cursor.receiptId);
944
- if (cursor.previousReceiptId === null || cursor.previousReceiptSha256 === null) {
945
- cursor = null;
946
- continue;
947
- }
948
- const previous = byId.get(cursor.previousReceiptId);
949
- if (!previous || canonicalJsonSha256V1(previous) !== cursor.previousReceiptSha256) {
950
- throw storeError('hash_mismatch', 'The maintenance receipt lineage does not match its previous receipt digest.');
951
- }
952
- if (previous.authorityRevision >= cursor.authorityRevision
953
- || previous.writerFenceEpoch >= cursor.writerFenceEpoch) {
954
- throw storeError('schema_incompatible', 'The maintenance receipt lineage does not advance authority and fence epochs.');
955
- }
956
- cursor = previous;
957
- }
958
- return receipts.length - lineage.size;
959
- }
960
- export function readAgentAssetsStoreStatus(options) {
961
- const verify = options.verify ?? 'quick';
962
- const existingRoot = assertRoot(options.assetRoot);
963
- if (!existingRoot) {
964
- return {
965
- state: 'not-installed',
966
- verify,
967
- assetRoot: path.normalize(options.assetRoot),
968
- verifiedPackageCount: 0,
969
- protectedPackageCount: 0,
970
- };
971
- }
972
- const authorityPath = safeChild(existingRoot, ['state', 'store-authority.json']);
973
- if (!existsSync(authorityPath)) {
974
- return {
975
- state: 'partial-genesis',
976
- verify,
977
- assetRoot: existingRoot.assetRoot,
978
- verifiedPackageCount: 0,
979
- protectedPackageCount: 0,
980
- recoveryReasons: ['store-root-exists-without-authority'],
981
- };
982
- }
983
- const preflightAuthority = parseStoreAuthorityV1(readJsonFile(existingRoot, ['state', 'store-authority.json']));
984
- const authorityHistory = readAuthorityHistoryEvidence(existingRoot, preflightAuthority);
985
- const nativeIdentityEvidence = recordedNativeIdentityEvidence(preflightAuthority);
986
- const activePath = safeChild(existingRoot, ['state', 'active.json']);
987
- if (!existsSync(activePath)) {
988
- const stagingEntries = safeDirectoryEntries(existingRoot, ['staging']).length;
989
- const receiptEntries = safeDirectoryEntries(existingRoot, ['state', 'receipts']).length;
990
- return {
991
- state: 'activation-incomplete',
992
- verify,
993
- assetRoot: existingRoot.assetRoot,
994
- storeId: preflightAuthority.storeId,
995
- authorityRevision: preflightAuthority.authorityRevision,
996
- publicationCapability: preflightAuthority.publicationCapability,
997
- capabilityEvidenceSha256: preflightAuthority.capabilityEvidenceSha256,
998
- authorityHistory,
999
- nativeIdentityEvidence,
1000
- verifiedPackageCount: 0,
1001
- protectedPackageCount: 0,
1002
- recoveryReasons: [
1003
- 'authority-exists-without-active-pointer',
1004
- ...(stagingEntries > 0 ? [`staging-entry-count:${stagingEntries}`] : []),
1005
- ...(receiptEntries > 0 ? [`orphan-receipt-count:${receiptEntries}`] : []),
1006
- ],
1007
- };
1008
- }
1009
- const root = openStore(options);
1010
- if (!root)
1011
- throw storeError('not_found', 'Agent assets store disappeared during status inspection.');
1012
- const activeRefs = packageRefs(root.receipt);
1013
- const maintenance = readAgentAssetsMaintenanceHead(options);
1014
- const stagingEntryCount = readAgentAssetsStagingCleanupPlan(options).removableManagedPaths.length;
1015
- const orphanActivationReceiptCount = readActivationReceiptOrphanCount(root);
1016
- const orphanMaintenanceReceiptCount = readMaintenanceReceiptOrphanCount(root, maintenance);
1017
- const recoveryReasons = [
1018
- ...(stagingEntryCount > 0 ? [`staging-entry-count:${stagingEntryCount}`] : []),
1019
- ...(orphanActivationReceiptCount > 0
1020
- ? [`orphan-activation-receipt-count:${orphanActivationReceiptCount}`]
1021
- : []),
1022
- ...(orphanMaintenanceReceiptCount > 0
1023
- ? [`orphan-maintenance-receipt-count:${orphanMaintenanceReceiptCount}`]
1024
- : []),
1025
- ];
1026
- for (const packageRef of activeRefs)
1027
- verifyPackage(root, packageRef, verify);
1028
- let protectedDigests = new Set(activeRefs.map((entry) => entry.packageSha256));
1029
- let verifiedDigests = new Set(protectedDigests);
1030
- if (verify === 'full') {
1031
- const previous = loadPreviousReceipt(root);
1032
- for (const packageRef of packageRefs(previous ?? root.receipt)) {
1033
- protectedDigests.add(packageRef.packageSha256);
1034
- if (!verifiedDigests.has(packageRef.packageSha256))
1035
- verifyPackage(root, packageRef, 'full');
1036
- verifiedDigests.add(packageRef.packageSha256);
1037
- }
1038
- const knownRefs = refsByDigest(root.receipt, previous);
1039
- for (const pin of loadUnexpiredPins(root, options.now ?? new Date())) {
1040
- protectedDigests.add(pin.packageSha256);
1041
- if (verifiedDigests.has(pin.packageSha256))
1042
- continue;
1043
- const packageRef = knownRefs.get(pin.packageSha256);
1044
- if (packageRef) {
1045
- verifyPackage(root, packageRef, 'full');
1046
- }
1047
- else {
1048
- const { manifest } = readInstalledManifest(root, pin.packageSha256);
1049
- const inferred = {
1050
- name: manifest.name,
1051
- version: manifest.version,
1052
- versionId: manifest.versionId,
1053
- packageSha256: manifest.packageSha256,
1054
- entryFile: manifest.entryFile,
1055
- origin: manifest.assetKind === 'community-core' ? 'bundled' : 'hosted-cache',
1056
- trustClass: manifest.provenance.trustClass,
1057
- };
1058
- verifyPackage(root, inferred, 'full');
1059
- }
1060
- verifiedDigests.add(pin.packageSha256);
1061
- }
1062
- }
1063
- return {
1064
- state: 'ready',
1065
- verify,
1066
- assetRoot: root.assetRoot,
1067
- storeId: root.authority.storeId,
1068
- authorityRevision: root.authority.authorityRevision,
1069
- generation: root.active.generation,
1070
- activeReceiptId: root.active.receiptId,
1071
- activePackageCount: activeRefs.length,
1072
- verifiedPackageCount: verifiedDigests.size,
1073
- protectedPackageCount: protectedDigests.size,
1074
- publicationCapability: root.authority.publicationCapability,
1075
- capabilityEvidenceSha256: root.authority.capabilityEvidenceSha256,
1076
- authorityHistory,
1077
- nativeIdentityEvidence,
1078
- ...(maintenance ? { maintenanceReceiptId: maintenance.receipt.receiptId } : {}),
1079
- ...(recoveryReasons.length > 0 ? { recoveryReasons } : {}),
1080
- };
1081
- }
1082
- export function readAgentAssetsPrunePlan(options) {
1083
- const root = openStore(options);
1084
- if (!root)
1085
- throw storeError('not_found', 'Agent assets are not installed.');
1086
- const previous = loadPreviousReceipt(root);
1087
- const protectedDigests = new Set([...packageRefs(root.receipt), ...(previous ? packageRefs(previous) : [])]
1088
- .map((entry) => entry.packageSha256));
1089
- for (const pin of loadUnexpiredPins(root, options.now ?? new Date())) {
1090
- protectedDigests.add(pin.packageSha256);
1091
- }
1092
- const installedDigests = safeDirectoryEntries(root, ['core']);
1093
- const removable = [];
1094
- for (const digest of installedDigests) {
1095
- if (!SHA256_HEX.test(digest)) {
1096
- throw storeError('schema_incompatible', 'Unknown entry exists in the managed immutable core directory.');
1097
- }
1098
- const { manifest } = readInstalledManifest(root, digest);
1099
- const inferred = {
1100
- name: manifest.name,
1101
- version: manifest.version,
1102
- versionId: manifest.versionId,
1103
- packageSha256: manifest.packageSha256,
1104
- entryFile: manifest.entryFile,
1105
- origin: manifest.assetKind === 'community-core' ? 'bundled' : 'hosted-cache',
1106
- trustClass: manifest.provenance.trustClass,
1107
- };
1108
- verifyPackage(root, inferred, 'full');
1109
- if (!protectedDigests.has(digest))
1110
- removable.push(digest);
1111
- }
1112
- for (const digest of protectedDigests) {
1113
- if (!installedDigests.includes(digest)) {
1114
- throw storeError('not_found', 'A protected package is missing from the immutable core directory.', { packageSha256: digest });
1115
- }
1116
- }
1117
- const protectedPackageSha256s = [...protectedDigests].sort();
1118
- const removablePackageSha256s = removable.sort();
1119
- return {
1120
- authority: structuredClone(root.authority),
1121
- protectedPackageSha256s,
1122
- removablePackageSha256s,
1123
- removableManagedPaths: removablePackageSha256s.map((digest) => `core/${digest}`),
1124
- };
1125
- }
1126
- function pinnedCandidates(root, now) {
1127
- return loadUnexpiredPins(root, now).map((pin) => {
1128
- const { manifest } = readInstalledManifest(root, pin.packageSha256);
1129
- return {
1130
- name: manifest.name,
1131
- version: manifest.version,
1132
- versionId: manifest.versionId,
1133
- packageSha256: manifest.packageSha256,
1134
- entryFile: manifest.entryFile,
1135
- origin: manifest.assetKind === 'community-core' ? 'bundled' : 'hosted-cache',
1136
- trustClass: manifest.provenance.trustClass,
1137
- };
1138
- });
1139
- }
1140
- function historicalCandidates(root, now) {
1141
- const previous = loadPreviousReceipt(root);
1142
- return [
1143
- ...(previous ? packageRefs(previous) : []),
1144
- ...pinnedCandidates(root, now),
1145
- ];
1146
- }
1147
- export function resolveAgentAsset(options) {
1148
- const selectors = [options.gateway !== undefined, options.name !== undefined, options.versionId !== undefined];
1149
- if (selectors.filter(Boolean).length !== 1) {
1150
- throw storeError('schema_incompatible', 'Resolve requires exactly one of gateway, name, or versionId.');
1151
- }
1152
- const root = openStore(options);
1153
- if (!root)
1154
- throw storeError('not_found', 'Agent assets are not installed.');
1155
- let matchedBy;
1156
- let selected;
1157
- if (options.gateway !== undefined) {
1158
- matchedBy = 'gateway';
1159
- selected = root.receipt.core.name === 'aops' ? root.receipt.core : undefined;
1160
- }
1161
- else if (options.versionId !== undefined) {
1162
- matchedBy = 'versionId';
1163
- const candidates = [...packageRefs(root.receipt), ...historicalCandidates(root, options.now ?? new Date())]
1164
- .filter((entry) => entry.versionId === options.versionId);
1165
- const unique = new Map(candidates.map((entry) => [entry.packageSha256, entry]));
1166
- if (unique.size > 1)
1167
- throw storeError('ambiguous', 'versionId matches more than one package digest.');
1168
- selected = [...unique.values()][0];
1169
- }
1170
- else {
1171
- matchedBy = 'name';
1172
- const candidates = packageRefs(root.receipt).filter((entry) => entry.name === options.name);
1173
- if (candidates.length > 1)
1174
- throw storeError('ambiguous', 'Active package name is ambiguous.');
1175
- selected = candidates[0];
1176
- }
1177
- if (!selected)
1178
- throw storeError('not_found', 'No verified installed package matches the selector.');
1179
- if (matchedBy === 'gateway' && (selected.origin !== 'bundled' || selected.trustClass !== 'signed-community-release')) {
1180
- throw storeError('untrusted_origin', 'Gateway resolution only accepts the active signed Community core.');
1181
- }
1182
- const verified = verifyPackage(root, selected, 'quick');
1183
- return {
1184
- entryPath: verified.entryPath,
1185
- name: selected.name,
1186
- version: selected.version,
1187
- versionId: selected.versionId,
1188
- contentSha256: verified.entrySha256,
1189
- packageSha256: selected.packageSha256,
1190
- origin: selected.origin,
1191
- computedTrustClass: selected.trustClass,
1192
- matchedBy,
1193
- };
1194
- }
1195
- export function readResolvedAgentAssetPackage(options) {
1196
- const resolved = resolveAgentAsset(options);
1197
- const root = openStore(options);
1198
- if (!root)
1199
- throw storeError('not_found', 'Agent assets are not installed.');
1200
- const { manifest } = readInstalledManifest(root, resolved.packageSha256);
1201
- const packageRef = {
1202
- name: resolved.name,
1203
- version: resolved.version,
1204
- versionId: resolved.versionId,
1205
- packageSha256: resolved.packageSha256,
1206
- entryFile: manifest.entryFile,
1207
- origin: resolved.origin,
1208
- trustClass: resolved.computedTrustClass,
1209
- };
1210
- verifyPackage(root, packageRef, 'full');
1211
- return { resolved, manifest };
1212
- }