akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4

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 (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -1,105 +0,0 @@
1
- // This Source Code Form is subject to the terms of the Mozilla Public
2
- // License, v. 2.0. If a copy of the MPL was not distributed with this
3
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { createHash } from "node:crypto";
5
- import fs from "node:fs";
6
- import path from "node:path";
7
- import { isRecord } from "../core/common.js";
8
- import { UsageError } from "../core/errors.js";
9
- export function freezeExecutableIdentity(requested, options = {}) {
10
- const absolutePath = resolveExecutable(requested, options.cwd ?? process.cwd(), options.path ?? process.env.PATH ?? "");
11
- let realPath;
12
- let stat;
13
- let bytes;
14
- try {
15
- fs.accessSync(absolutePath, fs.constants.X_OK);
16
- realPath = fs.realpathSync(absolutePath);
17
- stat = fs.lstatSync(realPath, { bigint: true });
18
- if (!stat.isFile() || stat.isSymbolicLink())
19
- throw new Error("not a regular executable file");
20
- bytes = fs.readFileSync(realPath);
21
- }
22
- catch {
23
- throw new UsageError(`Executable ${JSON.stringify(requested)} could not be frozen.`, "INVALID_FLAG_VALUE");
24
- }
25
- return Object.freeze({
26
- requested,
27
- absolutePath,
28
- realPath,
29
- device: stat.dev.toString(),
30
- inode: stat.ino.toString(),
31
- size: bytes.byteLength,
32
- sha256: createHash("sha256").update(bytes).digest("hex"),
33
- });
34
- }
35
- export function decodeFrozenExecutableIdentity(value, label = "executable") {
36
- if (!isRecord(value))
37
- throw invalid(label);
38
- const allowed = new Set(["requested", "absolutePath", "realPath", "device", "inode", "size", "sha256"]);
39
- for (const key of Object.keys(value))
40
- if (!allowed.has(key))
41
- throw invalid(label);
42
- if (typeof value.requested !== "string" ||
43
- typeof value.absolutePath !== "string" ||
44
- !path.isAbsolute(value.absolutePath) ||
45
- typeof value.realPath !== "string" ||
46
- !path.isAbsolute(value.realPath) ||
47
- typeof value.device !== "string" ||
48
- typeof value.inode !== "string" ||
49
- !Number.isSafeInteger(value.size) ||
50
- value.size < 0 ||
51
- typeof value.sha256 !== "string" ||
52
- !/^[0-9a-f]{64}$/.test(value.sha256)) {
53
- throw invalid(label);
54
- }
55
- const decoded = Object.freeze({
56
- requested: value.requested,
57
- absolutePath: value.absolutePath,
58
- realPath: value.realPath,
59
- device: value.device,
60
- inode: value.inode,
61
- size: value.size,
62
- sha256: value.sha256,
63
- });
64
- assertFrozenExecutableIdentity(decoded, label);
65
- return decoded;
66
- }
67
- export function assertFrozenExecutableIdentity(identity, label = "executable") {
68
- let actual;
69
- try {
70
- actual = freezeExecutableIdentity(identity.absolutePath, { cwd: path.dirname(identity.absolutePath) });
71
- }
72
- catch {
73
- throw changed(label);
74
- }
75
- for (const key of ["absolutePath", "realPath", "device", "inode", "size", "sha256"]) {
76
- if (actual[key] !== identity[key])
77
- throw changed(label);
78
- }
79
- }
80
- function resolveExecutable(requested, cwd, pathValue) {
81
- if (requested.includes("/") || requested.includes("\\"))
82
- return path.resolve(cwd, requested);
83
- const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
84
- for (const directory of pathValue.split(path.delimiter)) {
85
- if (!directory)
86
- continue;
87
- for (const extension of extensions) {
88
- const candidate = path.resolve(directory, `${requested}${extension}`);
89
- try {
90
- fs.accessSync(candidate, fs.constants.X_OK);
91
- return candidate;
92
- }
93
- catch {
94
- // Continue through the frozen search path.
95
- }
96
- }
97
- }
98
- throw new UsageError(`Executable ${JSON.stringify(requested)} was not found.`, "INVALID_FLAG_VALUE");
99
- }
100
- function invalid(label) {
101
- return new UsageError(`Frozen ${label} identity is invalid.`, "INVALID_FLAG_VALUE");
102
- }
103
- function changed(label) {
104
- return new UsageError(`Frozen ${label} changed after publication.`, "RESOURCE_ALREADY_EXISTS");
105
- }
@@ -1,427 +0,0 @@
1
- // This Source Code Form is subject to the terms of the Mozilla Public
2
- // License, v. 2.0. If a copy of the MPL was not distributed with this
3
- // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { createHash } from "node:crypto";
5
- import fs from "node:fs";
6
- import path from "node:path";
7
- import { parseFrontmatter } from "../core/asset/frontmatter.js";
8
- import { compareCodePoints, toPosix } from "../core/common.js";
9
- import { UsageError } from "../core/errors.js";
10
- import { createExecutionSourceIdentity } from "./source.js";
11
- function errorMessage(cause) {
12
- return cause instanceof Error ? cause.message : String(cause);
13
- }
14
- function physicalIdentity(realPath, stat) {
15
- return stat.ino === 0n ? `path:${realPath}` : `inode:${stat.dev}:${stat.ino}`;
16
- }
17
- function statVersion(stat) {
18
- return `${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
19
- }
20
- function sameBigIntStat(left, right) {
21
- return (left.dev === right.dev &&
22
- left.ino === right.ino &&
23
- left.mode === right.mode &&
24
- left.size === right.size &&
25
- left.mtimeNs === right.mtimeNs &&
26
- left.ctimeNs === right.ctimeNs);
27
- }
28
- function containedRelative(root, candidate, allowRoot) {
29
- const relative = path.relative(root, candidate);
30
- if ((!allowRoot && relative === "") || relative.startsWith("..") || path.isAbsolute(relative)) {
31
- throw new UsageError(`${candidate} resolves outside its guarded containment root.`, "PATH_ESCAPE_VIOLATION");
32
- }
33
- return relative;
34
- }
35
- function requireContainmentRoot(containmentRootInput) {
36
- const containmentRoot = path.resolve(containmentRootInput);
37
- const containmentRealPath = fs.realpathSync(containmentRoot);
38
- const containmentStat = fs.statSync(containmentRealPath, { bigint: true });
39
- if (!containmentStat.isDirectory()) {
40
- throw new UsageError(`${containmentRootInput} is not a guarded source directory.`, "INVALID_FLAG_VALUE");
41
- }
42
- return { containmentRoot, containmentRealPath, containmentStat };
43
- }
44
- function numberStat(stat) {
45
- return new Proxy(stat, {
46
- get(target, property, receiver) {
47
- const value = Reflect.get(target, property, receiver);
48
- return typeof value === "bigint" ? Number(value) : value;
49
- },
50
- });
51
- }
52
- function decodeUtf8(bytes, sourcePath) {
53
- try {
54
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
55
- }
56
- catch {
57
- throw new UsageError(`${sourcePath} contains invalid UTF-8 bytes.`, "INVALID_FLAG_VALUE");
58
- }
59
- }
60
- function freezeSource(source) {
61
- const identity = source.identity ? createExecutionSourceIdentity(source.identity) : undefined;
62
- return Object.freeze({ ...source, ...(identity ? { identity } : {}) });
63
- }
64
- function captureRecord(sourcePathInput, containmentRootInput, options = {}) {
65
- const sourcePath = path.resolve(sourcePathInput);
66
- const { containmentRoot, containmentRealPath, containmentStat } = requireContainmentRoot(containmentRootInput);
67
- const lexicalRelative = containedRelative(containmentRoot, sourcePath, false);
68
- const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
69
- let descriptor;
70
- try {
71
- descriptor = fs.openSync(sourcePath, fs.constants.O_RDONLY | noFollow);
72
- const before = fs.fstatSync(descriptor, { bigint: true });
73
- if (!before.isFile()) {
74
- throw new UsageError(`${sourcePath} is not a regular guarded source file.`, "INVALID_FLAG_VALUE");
75
- }
76
- const bytes = fs.readFileSync(descriptor);
77
- const after = fs.fstatSync(descriptor, { bigint: true });
78
- if (!sameBigIntStat(before, after) || BigInt(bytes.byteLength) !== before.size) {
79
- throw new UsageError(`${sourcePath} changed while its guarded bytes were read.`, "RESOURCE_ALREADY_EXISTS");
80
- }
81
- const pathnameStat = fs.lstatSync(sourcePath, { bigint: true });
82
- if (pathnameStat.isSymbolicLink()) {
83
- throw new UsageError(`${sourcePath} is a symbolic source; guarded reads require a regular no-follow owner.`, "RESOURCE_ALREADY_EXISTS");
84
- }
85
- if (!pathnameStat.isFile() || pathnameStat.dev !== before.dev || pathnameStat.ino !== before.ino) {
86
- throw new UsageError(`${sourcePath} changed physical identity during its guarded read.`, "RESOURCE_ALREADY_EXISTS");
87
- }
88
- const realPath = fs.realpathSync(sourcePath);
89
- containedRelative(containmentRealPath, realPath, false);
90
- const content = decodeUtf8(bytes, sourcePath);
91
- const sha256 = createHash("sha256").update(bytes).digest("hex");
92
- if (options.identity && options.identity.hash !== sha256) {
93
- throw new UsageError(`${sourcePath} adapter identity hash does not match its guarded bytes.`, "RESOURCE_ALREADY_EXISTS");
94
- }
95
- if (options.identity && options.identity.file !== toPosix(lexicalRelative)) {
96
- throw new UsageError(`${sourcePath} adapter identity file does not match its guarded relative path.`, "RESOURCE_ALREADY_EXISTS");
97
- }
98
- return {
99
- source: freezeSource({
100
- sourcePath,
101
- relativePath: toPosix(lexicalRelative),
102
- realPath,
103
- containmentRoot,
104
- containmentRealPath,
105
- containmentPhysicalIdentity: physicalIdentity(containmentRealPath, containmentStat),
106
- physicalIdentity: physicalIdentity(realPath, before),
107
- size: bytes.byteLength,
108
- mtimeNs: String(before.mtimeNs),
109
- ctimeNs: String(before.ctimeNs),
110
- sha256,
111
- bytesBase64: bytes.toString("base64"),
112
- content,
113
- authored: options.authored ?? false,
114
- ...(options.identity ? { identity: options.identity } : {}),
115
- }),
116
- stat: numberStat(before),
117
- };
118
- }
119
- catch (cause) {
120
- if (cause instanceof UsageError)
121
- throw cause;
122
- const code = cause.code;
123
- if (code === "ELOOP") {
124
- let linked;
125
- try {
126
- linked = fs.realpathSync(sourcePath);
127
- }
128
- catch {
129
- // The no-follow failure itself is sufficient when the link is broken.
130
- }
131
- if (linked) {
132
- const relative = path.relative(containmentRealPath, linked);
133
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
134
- throw new UsageError(`${sourcePath} resolves outside its containment root through a symbolic source.`, "PATH_ESCAPE_VIOLATION");
135
- }
136
- }
137
- throw new UsageError(`${sourcePath} is a symbolic source; guarded reads require a regular no-follow owner.`, "RESOURCE_ALREADY_EXISTS");
138
- }
139
- throw new UsageError(`${sourcePath} could not be guarded as a contained regular source: ${errorMessage(cause)}`, code === "ENOENT" ? "RESOURCE_ALREADY_EXISTS" : "PATH_ESCAPE_VIOLATION");
140
- }
141
- finally {
142
- if (descriptor !== undefined)
143
- fs.closeSync(descriptor);
144
- }
145
- }
146
- export function captureGuardedExecutionSource(sourcePath, containmentRoot, options = {}) {
147
- return captureRecord(sourcePath, containmentRoot, options).source;
148
- }
149
- export function captureGuardedDirectoryManifest(directoryPathInput, containmentRootInput) {
150
- const directoryPath = path.resolve(directoryPathInput);
151
- const { containmentRoot, containmentRealPath, containmentStat } = requireContainmentRoot(containmentRootInput);
152
- const lexicalRelative = containedRelative(containmentRoot, directoryPath, true);
153
- try {
154
- const before = fs.lstatSync(directoryPath, { bigint: true });
155
- if (before.isSymbolicLink()) {
156
- throw new UsageError(`${directoryPath} is a symbolic directory with ambiguous guarded identity.`, "RESOURCE_ALREADY_EXISTS");
157
- }
158
- if (!before.isDirectory()) {
159
- throw new UsageError(`${directoryPath} is not a guarded directory.`, "INVALID_FLAG_VALUE");
160
- }
161
- const realPath = fs.realpathSync(directoryPath);
162
- containedRelative(containmentRealPath, realPath, true);
163
- const entries = fs
164
- .readdirSync(directoryPath, { withFileTypes: true })
165
- .sort((left, right) => compareCodePoints(left.name, right.name))
166
- .map((entry) => {
167
- const entryPath = path.join(directoryPath, entry.name);
168
- const entryStat = fs.lstatSync(entryPath, { bigint: true });
169
- if (entry.isSymbolicLink() || entryStat.isSymbolicLink()) {
170
- let linked;
171
- try {
172
- linked = fs.realpathSync(entryPath);
173
- }
174
- catch {
175
- // Broken symbolic entries still have ambiguous ownership.
176
- }
177
- if (linked) {
178
- const relative = path.relative(containmentRealPath, linked);
179
- if (relative.startsWith("..") || path.isAbsolute(relative)) {
180
- throw new UsageError(`${entryPath} resolves outside the bundle root through a symbolic source.`, "PATH_ESCAPE_VIOLATION");
181
- }
182
- }
183
- throw new UsageError(`${entryPath} is a symbolic source with a physical source identity collision; guarded reads require one no-follow owner.`, "RESOURCE_ALREADY_EXISTS");
184
- }
185
- const kind = entry.isDirectory() && entryStat.isDirectory()
186
- ? "directory"
187
- : entry.isFile() && entryStat.isFile()
188
- ? "file"
189
- : undefined;
190
- if (!kind) {
191
- throw new UsageError(`${entryPath} is neither a regular file nor directory.`, "INVALID_FLAG_VALUE");
192
- }
193
- const entryRealPath = fs.realpathSync(entryPath);
194
- containedRelative(containmentRealPath, entryRealPath, false);
195
- return Object.freeze({
196
- name: entry.name,
197
- kind,
198
- physicalIdentity: physicalIdentity(entryRealPath, entryStat),
199
- version: statVersion(entryStat),
200
- });
201
- });
202
- const after = fs.lstatSync(directoryPath, { bigint: true });
203
- if (!sameBigIntStat(before, after)) {
204
- throw new UsageError(`${directoryPath} changed while its guarded directory manifest was read.`, "RESOURCE_ALREADY_EXISTS");
205
- }
206
- return Object.freeze({
207
- directoryPath,
208
- relativePath: lexicalRelative === "" ? "." : toPosix(lexicalRelative),
209
- realPath,
210
- containmentRoot,
211
- containmentRealPath,
212
- containmentPhysicalIdentity: physicalIdentity(containmentRealPath, containmentStat),
213
- physicalIdentity: physicalIdentity(realPath, before),
214
- version: statVersion(before),
215
- entries: Object.freeze(entries),
216
- });
217
- }
218
- catch (cause) {
219
- if (cause instanceof UsageError)
220
- throw cause;
221
- throw new UsageError(`${directoryPath} could not be captured as a guarded directory manifest: ${errorMessage(cause)}`, cause.code === "ENOENT" ? "RESOURCE_ALREADY_EXISTS" : "PATH_ESCAPE_VIOLATION");
222
- }
223
- }
224
- function sameSnapshotValue(left, right) {
225
- return JSON.stringify(left) === JSON.stringify(right);
226
- }
227
- function sourceSortKey(source) {
228
- const identity = source.identity;
229
- return identity
230
- ? `${identity.ref}\0${identity.adapter}\0${identity.file}`
231
- : `${source.containmentRealPath}\0${source.relativePath}`;
232
- }
233
- function directorySortKey(manifest) {
234
- return `${manifest.containmentRealPath}\0${manifest.relativePath}`;
235
- }
236
- export class GuardedExecutionSourceCollector {
237
- #sources = new Map();
238
- #directories = new Map();
239
- capture(sourcePath, containmentRoot, options = {}) {
240
- const key = path.resolve(sourcePath);
241
- const existing = this.#sources.get(key);
242
- if (existing) {
243
- if (options.identity)
244
- return this.bindIdentity(sourcePath, containmentRoot, options.identity);
245
- return existing.source;
246
- }
247
- const record = captureRecord(sourcePath, containmentRoot, options);
248
- this.#assertNoPhysicalOwnerAlias(record.source);
249
- this.#sources.set(key, record);
250
- return record.source;
251
- }
252
- readBytes(sourcePath, containmentRoot, options = {}) {
253
- return Buffer.from(this.capture(sourcePath, containmentRoot, options).bytesBase64, "base64");
254
- }
255
- fileContext(root, file, options = {}) {
256
- const source = this.capture(file, root, options);
257
- const record = this.#sources.get(path.resolve(file));
258
- if (!record)
259
- throw new Error("guarded source collector lost a captured file");
260
- const relPath = source.relativePath;
261
- const ext = path.extname(file).toLowerCase();
262
- const fileName = path.basename(file);
263
- const parentDirAbs = path.dirname(path.resolve(file));
264
- const relDir = path.posix.dirname(relPath);
265
- const ancestorDirs = relDir === "." ? [] : relDir.split("/").filter(Boolean);
266
- let frontmatter;
267
- let parsed = false;
268
- return {
269
- absPath: path.resolve(file),
270
- relPath,
271
- ext,
272
- fileName,
273
- parentDir: path.basename(parentDirAbs),
274
- parentDirAbs,
275
- ancestorDirs,
276
- stashRoot: path.resolve(root),
277
- content: () => source.content,
278
- frontmatter: () => {
279
- if (!parsed) {
280
- const result = parseFrontmatter(source.content);
281
- frontmatter = Object.keys(result.data).length > 0 ? result.data : null;
282
- parsed = true;
283
- }
284
- return frontmatter ?? null;
285
- },
286
- stat: () => record.stat,
287
- };
288
- }
289
- bindIdentity(sourcePath, containmentRoot, identityInput) {
290
- const key = path.resolve(sourcePath);
291
- const existing = this.#sources.get(key) ?? captureRecord(sourcePath, containmentRoot);
292
- const identity = createExecutionSourceIdentity(identityInput);
293
- if (identity.file !== existing.source.relativePath || identity.hash !== existing.source.sha256) {
294
- throw new UsageError(`${sourcePath} adapter identity does not match its guarded file owner or bytes.`, "RESOURCE_ALREADY_EXISTS");
295
- }
296
- if (existing.source.identity) {
297
- if (!sameSnapshotValue(existing.source.identity, identity)) {
298
- throw new UsageError(`${sourcePath} already has a different guarded logical owner identity.`, "RESOURCE_ALREADY_EXISTS");
299
- }
300
- return existing.source;
301
- }
302
- const source = freezeSource({ ...existing.source, identity });
303
- this.#assertNoPhysicalOwnerAlias(source, key);
304
- this.#sources.set(key, { ...existing, source });
305
- return source;
306
- }
307
- trackDirectory(directoryPath, containmentRoot) {
308
- const manifest = captureGuardedDirectoryManifest(directoryPath, containmentRoot);
309
- const key = path.resolve(directoryPath);
310
- const existing = this.#directories.get(key);
311
- if (existing && !sameSnapshotValue(existing, manifest)) {
312
- throw new UsageError(`${directoryPath} changed between guarded directory reads.`, "RESOURCE_ALREADY_EXISTS");
313
- }
314
- if (!existing)
315
- this.#directories.set(key, manifest);
316
- return existing ?? manifest;
317
- }
318
- enumerateTree(directoryPath, containmentRoot) {
319
- const files = [];
320
- const visit = (directory) => {
321
- const manifest = this.trackDirectory(directory, containmentRoot);
322
- for (const entry of manifest.entries) {
323
- const candidate = path.join(directory, entry.name);
324
- if (entry.kind === "directory")
325
- visit(candidate);
326
- else
327
- files.push(candidate);
328
- }
329
- };
330
- visit(path.resolve(directoryPath));
331
- return Object.freeze(files.sort(compareCodePoints));
332
- }
333
- snapshot() {
334
- return Object.freeze({
335
- sources: Object.freeze([...this.#sources.values()]
336
- .map((record) => record.source)
337
- .sort((left, right) => compareCodePoints(sourceSortKey(left), sourceSortKey(right)))),
338
- directoryManifests: Object.freeze([...this.#directories.values()].sort((left, right) => compareCodePoints(directorySortKey(left), directorySortKey(right)))),
339
- });
340
- }
341
- /**
342
- * Merge another collector's captured sources and directory manifests into
343
- * this one (A-N7, spec docs/plans/specs/p3a-plan-v5-child-freeze.md §4.2
344
- * step 6). The recursive child-workflow freeze gives each child its OWN
345
- * fresh collector, so a child's plan (and therefore its `planHash`) is a
346
- * pure function of its own source, independent of its position in the
347
- * parent (A-N7's rejected alternative: sharing the parent's collector,
348
- * which would make an identical child hash differently depending on what
349
- * the parent had already captured). The parent then absorbs the child's
350
- * records so its own final pre-publication CAS ({@link revalidate}) covers
351
- * every child file too.
352
- *
353
- * Re-runs `#assertNoPhysicalOwnerAlias` for each newly-absorbed source
354
- * record, so the shared-physical-owner authority holds ACROSS the
355
- * composition, not just within one workflow's own freeze. A record whose
356
- * key (resolved source path) is already present is required to be
357
- * byte-identical to the one already held — two DISJOINT branches
358
- * composing the SAME child (a diamond, not a cycle) absorb the same file
359
- * twice with identical content and pass silently; two branches that
360
- * somehow captured the same path with different content fail closed.
361
- */
362
- absorb(other) {
363
- for (const [key, record] of other.#sources) {
364
- const existing = this.#sources.get(key);
365
- if (existing) {
366
- if (!sameSnapshotValue(existing.source, record.source)) {
367
- throw new UsageError(`${record.source.sourcePath} was captured with conflicting content across a composed workflow freeze.`, "RESOURCE_ALREADY_EXISTS");
368
- }
369
- continue;
370
- }
371
- this.#assertNoPhysicalOwnerAlias(record.source);
372
- this.#sources.set(key, record);
373
- }
374
- for (const [key, manifest] of other.#directories) {
375
- const existing = this.#directories.get(key);
376
- if (existing) {
377
- if (!sameSnapshotValue(existing, manifest)) {
378
- throw new UsageError(`${manifest.directoryPath} changed between guarded directory reads across a composed workflow freeze.`, "RESOURCE_ALREADY_EXISTS");
379
- }
380
- continue;
381
- }
382
- this.#directories.set(key, manifest);
383
- }
384
- }
385
- revalidate() {
386
- for (const record of this.#sources.values()) {
387
- let current;
388
- try {
389
- current = captureRecord(record.source.sourcePath, record.source.containmentRoot, {
390
- authored: record.source.authored,
391
- ...(record.source.identity ? { identity: record.source.identity } : {}),
392
- }).source;
393
- }
394
- catch (cause) {
395
- throw new UsageError(`Guarded execution source read set changed before publication: ${errorMessage(cause)}`, "RESOURCE_ALREADY_EXISTS");
396
- }
397
- if (!sameSnapshotValue(record.source, current)) {
398
- throw new UsageError("Guarded execution source read set changed before publication.", "RESOURCE_ALREADY_EXISTS");
399
- }
400
- }
401
- for (const manifest of this.#directories.values()) {
402
- let current;
403
- try {
404
- current = captureGuardedDirectoryManifest(manifest.directoryPath, manifest.containmentRoot);
405
- }
406
- catch (cause) {
407
- throw new UsageError(`Guarded execution source directory manifest changed before publication: ${errorMessage(cause)}`, "RESOURCE_ALREADY_EXISTS");
408
- }
409
- if (!sameSnapshotValue(manifest, current)) {
410
- throw new UsageError("Guarded execution source directory manifest changed before publication.", "RESOURCE_ALREADY_EXISTS");
411
- }
412
- }
413
- }
414
- #assertNoPhysicalOwnerAlias(candidate, selfKey) {
415
- if (!candidate.identity)
416
- return;
417
- for (const [key, record] of this.#sources) {
418
- if (key === selfKey || !record.source.identity)
419
- continue;
420
- if (record.source.containmentPhysicalIdentity === candidate.containmentPhysicalIdentity &&
421
- record.source.physicalIdentity === candidate.physicalIdentity &&
422
- !sameSnapshotValue(record.source.identity, candidate.identity)) {
423
- throw new UsageError(`${candidate.sourcePath} aliases the same physical source and root under a different logical owner.`, "RESOURCE_ALREADY_EXISTS");
424
- }
425
- }
426
- }
427
- }