@msn-control/liftoff 0.11.3 → 0.12.1

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 (332) hide show
  1. package/DEVELOPER.md +26 -35
  2. package/README.md +18 -18
  3. package/assets/governance/single-maintainer-gitflow/activation-v2-graph.json +1583 -0
  4. package/dist/adapters/credentials/github-enrollment.d.ts +51 -0
  5. package/dist/adapters/credentials/github-enrollment.js +226 -0
  6. package/dist/adapters/credentials/github-enrollment.js.map +1 -0
  7. package/dist/adapters/credentials/protected-input.d.ts +7 -0
  8. package/dist/adapters/credentials/protected-input.js +68 -0
  9. package/dist/adapters/credentials/protected-input.js.map +1 -0
  10. package/dist/adapters/filesystem/executables.d.ts +2 -0
  11. package/dist/adapters/filesystem/executables.js +70 -0
  12. package/dist/adapters/filesystem/executables.js.map +1 -0
  13. package/dist/adapters/filesystem/update-previews.d.ts +9 -0
  14. package/dist/adapters/filesystem/update-previews.js +44 -0
  15. package/dist/adapters/filesystem/update-previews.js.map +1 -1
  16. package/dist/adapters/filesystem/workstation-attempts.d.ts +13 -0
  17. package/dist/adapters/filesystem/workstation-attempts.js +78 -0
  18. package/dist/adapters/filesystem/workstation-attempts.js.map +1 -0
  19. package/dist/adapters/github/activation-rest.d.ts +40 -0
  20. package/dist/adapters/github/activation-rest.js +272 -0
  21. package/dist/adapters/github/activation-rest.js.map +1 -0
  22. package/dist/adapters/state/azure-blob.d.ts +43 -0
  23. package/dist/adapters/state/azure-blob.js +335 -0
  24. package/dist/adapters/state/azure-blob.js.map +1 -0
  25. package/dist/adapters/state/darwin-capabilities.d.ts +33 -0
  26. package/dist/adapters/state/darwin-capabilities.js +146 -0
  27. package/dist/adapters/state/darwin-capabilities.js.map +1 -0
  28. package/dist/adapters/state/darwin-native-host.d.ts +32 -0
  29. package/dist/adapters/state/darwin-native-host.js +294 -0
  30. package/dist/adapters/state/darwin-native-host.js.map +1 -0
  31. package/dist/adapters/state/darwin-profile.d.ts +6 -0
  32. package/dist/adapters/state/darwin-profile.js +56 -0
  33. package/dist/adapters/state/darwin-profile.js.map +1 -0
  34. package/dist/adapters/state/darwin-system-program.d.ts +1 -0
  35. package/dist/adapters/state/darwin-system-program.js +94 -0
  36. package/dist/adapters/state/darwin-system-program.js.map +1 -0
  37. package/dist/adapters/state/local-qualification.d.ts +16 -0
  38. package/dist/adapters/state/local-qualification.js +299 -0
  39. package/dist/adapters/state/local-qualification.js.map +1 -0
  40. package/dist/adapters/state/local.d.ts +25 -0
  41. package/dist/adapters/state/local.js +145 -0
  42. package/dist/adapters/state/local.js.map +1 -0
  43. package/dist/adapters/state/native-command.d.ts +21 -0
  44. package/dist/adapters/state/native-command.js +82 -0
  45. package/dist/adapters/state/native-command.js.map +1 -0
  46. package/dist/adapters/state/native-files.d.ts +2 -0
  47. package/dist/adapters/state/native-files.js +47 -0
  48. package/dist/adapters/state/native-files.js.map +1 -0
  49. package/dist/adapters/state/native-system.d.ts +38 -0
  50. package/dist/adapters/state/native-system.js +86 -0
  51. package/dist/adapters/state/native-system.js.map +1 -0
  52. package/dist/adapters/state/opentofu.d.ts +35 -0
  53. package/dist/adapters/state/opentofu.js +350 -0
  54. package/dist/adapters/state/opentofu.js.map +1 -0
  55. package/dist/adapters/state/owned-process.d.ts +43 -0
  56. package/dist/adapters/state/owned-process.js +252 -0
  57. package/dist/adapters/state/owned-process.js.map +1 -0
  58. package/dist/adapters/state/posix-lock-program.d.ts +1 -0
  59. package/dist/adapters/state/posix-lock-program.js +185 -0
  60. package/dist/adapters/state/posix-lock-program.js.map +1 -0
  61. package/dist/adapters/state/posix-native-lock.d.ts +15 -0
  62. package/dist/adapters/state/posix-native-lock.js +145 -0
  63. package/dist/adapters/state/posix-native-lock.js.map +1 -0
  64. package/dist/adapters/state/protected-workspace.d.ts +57 -0
  65. package/dist/adapters/state/protected-workspace.js +412 -0
  66. package/dist/adapters/state/protected-workspace.js.map +1 -0
  67. package/dist/application/context.d.ts +3 -0
  68. package/dist/application/diagnose/doctor.d.ts +5 -0
  69. package/dist/application/diagnose/doctor.js +21 -5
  70. package/dist/application/diagnose/doctor.js.map +1 -1
  71. package/dist/application/diagnose/generated-project.js +2 -7
  72. package/dist/application/diagnose/generated-project.js.map +1 -1
  73. package/dist/application/initialize/fixture.js +8 -1
  74. package/dist/application/initialize/fixture.js.map +1 -1
  75. package/dist/application/initialize/use-case.js +70 -36
  76. package/dist/application/initialize/use-case.js.map +1 -1
  77. package/dist/application/project/manifest.js +5 -4
  78. package/dist/application/project/manifest.js.map +1 -1
  79. package/dist/application/state-migration/execution.d.ts +32 -0
  80. package/dist/application/state-migration/execution.js +440 -0
  81. package/dist/application/state-migration/execution.js.map +1 -0
  82. package/dist/application/state-migration/index.d.ts +22 -0
  83. package/dist/application/state-migration/index.js +17 -0
  84. package/dist/application/state-migration/index.js.map +1 -0
  85. package/dist/application/state-migration/lifecycle.d.ts +3 -0
  86. package/dist/application/state-migration/lifecycle.js +149 -0
  87. package/dist/application/state-migration/lifecycle.js.map +1 -0
  88. package/dist/application/state-migration/planning.d.ts +7 -0
  89. package/dist/application/state-migration/planning.js +150 -0
  90. package/dist/application/state-migration/planning.js.map +1 -0
  91. package/dist/application/state-migration/recovery.d.ts +5 -0
  92. package/dist/application/state-migration/recovery.js +145 -0
  93. package/dist/application/state-migration/recovery.js.map +1 -0
  94. package/dist/application/state-migration/runtime.d.ts +32 -0
  95. package/dist/application/state-migration/runtime.js +188 -0
  96. package/dist/application/state-migration/runtime.js.map +1 -0
  97. package/dist/application/state-migration/service.d.ts +2 -0
  98. package/dist/application/state-migration/service.js +20 -0
  99. package/dist/application/state-migration/service.js.map +1 -0
  100. package/dist/application/update/inspection.d.ts +16 -2
  101. package/dist/application/update/inspection.js +76 -23
  102. package/dist/application/update/inspection.js.map +1 -1
  103. package/dist/application/update/migration-runtime.js +24 -11
  104. package/dist/application/update/migration-runtime.js.map +1 -1
  105. package/dist/application/update/output.d.ts +2 -0
  106. package/dist/application/update/output.js +11 -1
  107. package/dist/application/update/output.js.map +1 -1
  108. package/dist/application/update/revalidation-plan.d.ts +1 -0
  109. package/dist/application/update/revalidation-plan.js +25 -12
  110. package/dist/application/update/revalidation-plan.js.map +1 -1
  111. package/dist/application/update/revalidation.d.ts +4 -1
  112. package/dist/application/update/revalidation.js +62 -21
  113. package/dist/application/update/revalidation.js.map +1 -1
  114. package/dist/application/update/use-case.js +25 -17
  115. package/dist/application/update/use-case.js.map +1 -1
  116. package/dist/application/upgrade/use-case.js +3 -0
  117. package/dist/application/upgrade/use-case.js.map +1 -1
  118. package/dist/cli/args/definitions.js +11 -3
  119. package/dist/cli/args/definitions.js.map +1 -1
  120. package/dist/cli/args/parser.js +29 -3
  121. package/dist/cli/args/parser.js.map +1 -1
  122. package/dist/domain/governance/activation/approvals.d.ts +264 -7
  123. package/dist/domain/governance/activation/approvals.js +166 -23
  124. package/dist/domain/governance/activation/approvals.js.map +1 -1
  125. package/dist/domain/governance/activation/capabilities.js +5 -2
  126. package/dist/domain/governance/activation/capabilities.js.map +1 -1
  127. package/dist/domain/governance/activation/evidence.d.ts +2 -1
  128. package/dist/domain/governance/activation/evidence.js +64 -11
  129. package/dist/domain/governance/activation/evidence.js.map +1 -1
  130. package/dist/domain/governance/activation/graph.d.ts +11 -384
  131. package/dist/domain/governance/activation/graph.js +424 -379
  132. package/dist/domain/governance/activation/graph.js.map +1 -1
  133. package/dist/domain/governance/activation/inputs.d.ts +3 -1
  134. package/dist/domain/governance/activation/inputs.js +47 -6
  135. package/dist/domain/governance/activation/inputs.js.map +1 -1
  136. package/dist/domain/governance/activation/operations.d.ts +4 -1
  137. package/dist/domain/governance/activation/operations.js +110 -22
  138. package/dist/domain/governance/activation/operations.js.map +1 -1
  139. package/dist/domain/governance/activation/readiness.d.ts +10 -0
  140. package/dist/domain/governance/activation/readiness.js +77 -38
  141. package/dist/domain/governance/activation/readiness.js.map +1 -1
  142. package/dist/domain/governance/activation/types.d.ts +151 -9
  143. package/dist/domain/governance/activation/types.js +23 -1
  144. package/dist/domain/governance/activation/types.js.map +1 -1
  145. package/dist/domain/governance/activation/validators.d.ts +4 -1
  146. package/dist/domain/governance/activation/validators.js +419 -26
  147. package/dist/domain/governance/activation/validators.js.map +1 -1
  148. package/dist/domain/governance/policy/identity.d.ts +55 -13
  149. package/dist/domain/governance/policy/identity.js +28 -8
  150. package/dist/domain/governance/policy/identity.js.map +1 -1
  151. package/dist/domain/project/artifact-lifecycle.d.ts +2 -1
  152. package/dist/domain/project/artifact-lifecycle.js +7 -1
  153. package/dist/domain/project/artifact-lifecycle.js.map +1 -1
  154. package/dist/domain/project/catalog.d.ts +50 -1
  155. package/dist/domain/project/catalog.js +104 -4
  156. package/dist/domain/project/catalog.js.map +1 -1
  157. package/dist/domain/project/contracts.d.ts +9 -1
  158. package/dist/domain/project/infrastructure-layout.d.ts +1 -1
  159. package/dist/domain/project/infrastructure-layout.js +3 -1
  160. package/dist/domain/project/infrastructure-layout.js.map +1 -1
  161. package/dist/domain/project/manifest/governance.d.ts +1 -1
  162. package/dist/domain/project/manifest/governance.js +9 -10
  163. package/dist/domain/project/manifest/governance.js.map +1 -1
  164. package/dist/domain/project/planning.js +4 -5
  165. package/dist/domain/project/planning.js.map +1 -1
  166. package/dist/domain/repair/stateful-bounded.d.ts +1 -0
  167. package/dist/domain/repair/stateful-bounded.js +37 -0
  168. package/dist/domain/repair/stateful-bounded.js.map +1 -0
  169. package/dist/domain/repair/stateful-invariants.d.ts +28 -0
  170. package/dist/domain/repair/stateful-invariants.js +278 -0
  171. package/dist/domain/repair/stateful-invariants.js.map +1 -0
  172. package/dist/domain/repair/stateful.d.ts +690 -0
  173. package/dist/domain/repair/stateful.js +26 -0
  174. package/dist/domain/repair/stateful.js.map +1 -0
  175. package/dist/domain/workstation/constraints.d.ts +1 -0
  176. package/dist/domain/workstation/constraints.js.map +1 -1
  177. package/dist/domain/workstation/contracts.d.ts +42 -0
  178. package/dist/domain/workstation/contracts.js +2 -0
  179. package/dist/domain/workstation/contracts.js.map +1 -0
  180. package/dist/domain/workstation/executables.d.ts +19 -0
  181. package/dist/domain/workstation/executables.js +68 -0
  182. package/dist/domain/workstation/executables.js.map +1 -0
  183. package/dist/domain/workstation/versions.d.ts +8 -0
  184. package/dist/domain/workstation/versions.js +145 -0
  185. package/dist/domain/workstation/versions.js.map +1 -0
  186. package/dist/framework-adapters.d.ts +2 -0
  187. package/dist/framework-adapters.js +148 -22
  188. package/dist/framework-adapters.js.map +1 -1
  189. package/dist/framework-validation.d.ts +6 -0
  190. package/dist/framework-validation.js +89 -24
  191. package/dist/framework-validation.js.map +1 -1
  192. package/dist/generators/common/base.js +57 -17
  193. package/dist/generators/common/base.js.map +1 -1
  194. package/dist/governance-activation/activation-state.js +39 -7
  195. package/dist/governance-activation/activation-state.js.map +1 -1
  196. package/dist/governance-activation/authority-records.d.ts +4 -0
  197. package/dist/governance-activation/authority-records.js +24 -0
  198. package/dist/governance-activation/authority-records.js.map +1 -0
  199. package/dist/governance-activation/commands.d.ts +9 -1
  200. package/dist/governance-activation/commands.js +392 -97
  201. package/dist/governance-activation/commands.js.map +1 -1
  202. package/dist/governance-activation/compatibility.d.ts +11 -8
  203. package/dist/governance-activation/compatibility.js +37 -23
  204. package/dist/governance-activation/compatibility.js.map +1 -1
  205. package/dist/governance-activation/doctor.js +40 -16
  206. package/dist/governance-activation/doctor.js.map +1 -1
  207. package/dist/governance-activation/github-config.d.ts +17 -0
  208. package/dist/governance-activation/github-config.js +88 -0
  209. package/dist/governance-activation/github-config.js.map +1 -0
  210. package/dist/governance-activation/github-discovery.d.ts +18 -0
  211. package/dist/governance-activation/github-discovery.js +229 -0
  212. package/dist/governance-activation/github-discovery.js.map +1 -0
  213. package/dist/governance-activation/github-ports.d.ts +12 -0
  214. package/dist/governance-activation/github-ports.js +4 -0
  215. package/dist/governance-activation/github-ports.js.map +1 -0
  216. package/dist/governance-activation/github-publication.d.ts +3 -0
  217. package/dist/governance-activation/github-publication.js +190 -0
  218. package/dist/governance-activation/github-publication.js.map +1 -0
  219. package/dist/governance-activation/historical-common.d.ts +98 -0
  220. package/dist/governance-activation/historical-common.js +269 -0
  221. package/dist/governance-activation/historical-common.js.map +1 -0
  222. package/dist/governance-activation/historical-credential-policy.d.ts +3 -0
  223. package/dist/governance-activation/historical-credential-policy.js +91 -0
  224. package/dist/governance-activation/historical-credential-policy.js.map +1 -0
  225. package/dist/governance-activation/historical-inputs.d.ts +6 -0
  226. package/dist/governance-activation/historical-inputs.js +57 -0
  227. package/dist/governance-activation/historical-inputs.js.map +1 -0
  228. package/dist/governance-activation/historical-safety.d.ts +9 -0
  229. package/dist/governance-activation/historical-safety.js +102 -0
  230. package/dist/governance-activation/historical-safety.js.map +1 -0
  231. package/dist/governance-activation/historical-source-metadata.d.ts +31 -0
  232. package/dist/governance-activation/historical-source-metadata.js +55 -0
  233. package/dist/governance-activation/historical-source-metadata.js.map +1 -0
  234. package/dist/governance-activation/historical-state.d.ts +12 -50
  235. package/dist/governance-activation/historical-state.js +195 -308
  236. package/dist/governance-activation/historical-state.js.map +1 -1
  237. package/dist/governance-activation/historical-v1-auxiliary.d.ts +1 -0
  238. package/dist/governance-activation/historical-v1-auxiliary.js +43 -0
  239. package/dist/governance-activation/historical-v1-auxiliary.js.map +1 -0
  240. package/dist/governance-activation/historical-v1-phase-contracts.d.ts +31 -0
  241. package/dist/governance-activation/historical-v1-phase-contracts.js +46 -0
  242. package/dist/governance-activation/historical-v1-phase-contracts.js.map +1 -0
  243. package/dist/governance-activation/historical-v2.d.ts +53 -0
  244. package/dist/governance-activation/historical-v2.js +313 -0
  245. package/dist/governance-activation/historical-v2.js.map +1 -0
  246. package/dist/governance-activation/history-contracts.d.ts +21 -12
  247. package/dist/governance-activation/history-contracts.js +99 -27
  248. package/dist/governance-activation/history-contracts.js.map +1 -1
  249. package/dist/governance-activation/inputs.d.ts +8 -2
  250. package/dist/governance-activation/inputs.js +41 -6
  251. package/dist/governance-activation/inputs.js.map +1 -1
  252. package/dist/governance-activation/migration-history.d.ts +43 -5
  253. package/dist/governance-activation/migration-history.js +231 -50
  254. package/dist/governance-activation/migration-history.js.map +1 -1
  255. package/dist/governance-activation/migration.d.ts +7 -4
  256. package/dist/governance-activation/migration.js +53 -252
  257. package/dist/governance-activation/migration.js.map +1 -1
  258. package/dist/governance-activation/phase-azure.d.ts +3 -0
  259. package/dist/governance-activation/phase-azure.js +205 -0
  260. package/dist/governance-activation/phase-azure.js.map +1 -0
  261. package/dist/governance-activation/phase-bootstrap-state.js +3 -0
  262. package/dist/governance-activation/phase-bootstrap-state.js.map +1 -1
  263. package/dist/governance-activation/phase-github.d.ts +3 -0
  264. package/dist/governance-activation/phase-github.js +192 -0
  265. package/dist/governance-activation/phase-github.js.map +1 -0
  266. package/dist/governance-activation/phase-governance.js +8 -2
  267. package/dist/governance-activation/phase-governance.js.map +1 -1
  268. package/dist/governance-activation/public-plans.d.ts +36 -0
  269. package/dist/governance-activation/public-plans.js +100 -0
  270. package/dist/governance-activation/public-plans.js.map +1 -0
  271. package/dist/governance-activation/read-only.d.ts +3 -1
  272. package/dist/governance-activation/read-only.js +7 -3
  273. package/dist/governance-activation/read-only.js.map +1 -1
  274. package/dist/governance-activation/reconciliation.js +10 -0
  275. package/dist/governance-activation/reconciliation.js.map +1 -1
  276. package/dist/governance-activation/seed-lifecycle.js +14 -25
  277. package/dist/governance-activation/seed-lifecycle.js.map +1 -1
  278. package/dist/governance-activation/source-of-truth.d.ts +21 -5
  279. package/dist/governance-activation/source-of-truth.js +102 -13
  280. package/dist/governance-activation/source-of-truth.js.map +1 -1
  281. package/dist/governance-activation/task-projection.d.ts +4 -1
  282. package/dist/governance-activation/task-projection.js +32 -3
  283. package/dist/governance-activation/task-projection.js.map +1 -1
  284. package/dist/governance-activation/task-writes.d.ts +42 -0
  285. package/dist/governance-activation/task-writes.js +250 -0
  286. package/dist/governance-activation/task-writes.js.map +1 -0
  287. package/dist/governance-activation/transition-files.d.ts +12 -0
  288. package/dist/governance-activation/transition-files.js +109 -0
  289. package/dist/governance-activation/transition-files.js.map +1 -0
  290. package/dist/governance-activation/transition-planning.d.ts +3 -1
  291. package/dist/governance-activation/transition-planning.js +158 -20
  292. package/dist/governance-activation/transition-planning.js.map +1 -1
  293. package/dist/governance-activation/transition-ports.d.ts +31 -3
  294. package/dist/governance-activation/transition-records.d.ts +11 -1
  295. package/dist/governance-activation/transition-records.js +52 -6
  296. package/dist/governance-activation/transition-records.js.map +1 -1
  297. package/dist/governance-activation/transitions.d.ts +5 -0
  298. package/dist/governance-activation/transitions.js +271 -37
  299. package/dist/governance-activation/transitions.js.map +1 -1
  300. package/dist/governance-assessment/engine.js +22 -15
  301. package/dist/governance-assessment/engine.js.map +1 -1
  302. package/dist/governance-assessment/project.d.ts +2 -0
  303. package/dist/governance-assessment/project.js +2 -1
  304. package/dist/governance-assessment/project.js.map +1 -1
  305. package/dist/interactive.d.ts +0 -1
  306. package/dist/interactive.js +3 -22
  307. package/dist/interactive.js.map +1 -1
  308. package/dist/openspec-profile.d.ts +2 -1
  309. package/dist/openspec-profile.js +11 -41
  310. package/dist/openspec-profile.js.map +1 -1
  311. package/dist/repository-governance.d.ts +10 -5
  312. package/dist/repository-governance.js +178 -92
  313. package/dist/repository-governance.js.map +1 -1
  314. package/dist/self-upgrade.d.ts +8 -2
  315. package/dist/self-upgrade.js +180 -47
  316. package/dist/self-upgrade.js.map +1 -1
  317. package/dist/telemetry/contract.d.ts +1 -1
  318. package/dist/telemetry/contract.js +3 -0
  319. package/dist/telemetry/contract.js.map +1 -1
  320. package/dist/workstation-catalog.d.ts +13 -1
  321. package/dist/workstation-catalog.js +122 -0
  322. package/dist/workstation-catalog.js.map +1 -1
  323. package/dist/workstation.d.ts +69 -14
  324. package/dist/workstation.js +600 -215
  325. package/dist/workstation.js.map +1 -1
  326. package/docs/cli-reference.md +69 -22
  327. package/docs/configuration-and-manifests.md +9 -8
  328. package/docs/prerequisites.md +93 -18
  329. package/docs/repository-governance.md +68 -40
  330. package/docs/spec-workflows-and-agents.md +132 -13
  331. package/docs/troubleshooting.md +32 -2
  332. package/package.json +1 -1
@@ -0,0 +1,145 @@
1
+ import path from 'node:path';
2
+ import { StateMigrationError, stateFailureCodes } from '../../domain/repair/stateful.js';
3
+ import { stateAssert } from '../../domain/repair/stateful-invariants.js';
4
+ import { startPrivateStateProcess, nativeLocalStateProtocol } from './native-system.js';
5
+ import { posixStateLockProgram } from './posix-lock-program.js';
6
+ import { stopOwnedStateProcess } from './owned-process.js';
7
+ class PrivateLockSession {
8
+ child;
9
+ timeoutMs;
10
+ #closed = false;
11
+ #buffer = '';
12
+ #pending = null;
13
+ #queue = Promise.resolve();
14
+ #timer = null;
15
+ constructor(child, timeoutMs) {
16
+ this.child = child;
17
+ this.timeoutMs = timeoutMs;
18
+ child.stdout.on('data', (data) => {
19
+ this.#buffer += data.toString('utf8');
20
+ if (this.#buffer.length > 8192) {
21
+ this.fail('storage-limit');
22
+ return;
23
+ }
24
+ const newline = this.#buffer.indexOf('\n');
25
+ if (newline < 0)
26
+ return;
27
+ const line = this.#buffer.slice(0, newline);
28
+ this.#buffer = this.#buffer.slice(newline + 1);
29
+ const pending = this.#pending;
30
+ this.#pending = null;
31
+ if (this.#timer)
32
+ clearTimeout(this.#timer);
33
+ try {
34
+ const reply = JSON.parse(line);
35
+ if (!pending || typeof reply.ok !== 'boolean') {
36
+ this.fail('operation-failed');
37
+ return;
38
+ }
39
+ if (!reply.ok) {
40
+ const code = reply.code && stateFailureCodes.includes(reply.code) ? reply.code : 'operation-failed';
41
+ this.#closed = true;
42
+ pending.reject(new StateMigrationError(code));
43
+ }
44
+ else
45
+ pending.resolve(reply);
46
+ }
47
+ catch {
48
+ pending?.reject(new StateMigrationError('operation-failed'));
49
+ this.fail('operation-failed');
50
+ }
51
+ });
52
+ child.stderr.on('data', (data) => { if (data.length)
53
+ this.fail('operation-failed'); });
54
+ child.stdin.on('error', () => this.fail('lock-lost'));
55
+ child.once('error', () => this.fail('tool-unavailable'));
56
+ child.once('close', () => this.fail('lock-lost'));
57
+ }
58
+ fail(code) {
59
+ this.#closed = true;
60
+ if (this.#timer)
61
+ clearTimeout(this.#timer);
62
+ this.#pending?.reject(new StateMigrationError(code));
63
+ this.#pending = null;
64
+ this.child.stdin.end();
65
+ this.child.kill('SIGTERM');
66
+ }
67
+ send(command) {
68
+ const work = this.#queue.then(() => new Promise((resolve, reject) => {
69
+ if (this.#closed) {
70
+ reject(new StateMigrationError('lock-lost'));
71
+ return;
72
+ }
73
+ this.#pending = { resolve, reject };
74
+ this.#timer = setTimeout(() => this.fail('timeout'), this.timeoutMs);
75
+ this.#timer.unref();
76
+ this.child.stdin.write(`${JSON.stringify(command)}\n`, (error) => { if (error)
77
+ this.fail('lock-lost'); });
78
+ }));
79
+ this.#queue = work.catch(() => undefined);
80
+ return work;
81
+ }
82
+ cancel() { this.fail('cancelled'); }
83
+ async release() {
84
+ try {
85
+ if (!this.#closed)
86
+ await this.send({ action: 'release' });
87
+ }
88
+ finally {
89
+ this.#closed = true;
90
+ this.child.stdin.end();
91
+ if (this.#timer)
92
+ clearTimeout(this.#timer);
93
+ await stopOwnedStateProcess(this.child, { immediate: false });
94
+ }
95
+ }
96
+ }
97
+ export class DarwinPosixStateLockProvider {
98
+ options;
99
+ capabilities = Object.freeze({
100
+ protocol: nativeLocalStateProtocol.version, existingInPlace: true, createAbsent: false, remove: false
101
+ });
102
+ constructor(options) {
103
+ this.options = options;
104
+ }
105
+ async acquire(request) {
106
+ stateAssert(process.platform === 'darwin', 'unsupported-native-platform');
107
+ stateAssert((this.options.timeoutMs ?? 15_000) > 0 && (this.options.timeoutMs ?? 15_000) <= 120_000, 'invalid-binding');
108
+ stateAssert(request.expectedVersion !== null, 'unsupported-local-state-operation');
109
+ stateAssert(/^[a-f0-9]{64}$/.test(request.expectedVersion) && /^[a-f0-9-]{36}$/.test(request.operationId), 'invalid-binding');
110
+ stateAssert(!request.signal?.aborted, 'cancelled');
111
+ const child = await startPrivateStateProcess(this.options.python, ['-I', '-S', '-B', '-u', '-c', posixStateLockProgram], path.dirname(request.path), undefined, 'lease');
112
+ const session = new PrivateLockSession(child, this.options.timeoutMs ?? 15_000);
113
+ const abort = () => session.cancel();
114
+ request.signal?.addEventListener('abort', abort, { once: true });
115
+ if (request.signal?.aborted)
116
+ abort();
117
+ try {
118
+ await session.send({
119
+ path: request.path, operationId: request.operationId, expectedVersion: request.expectedVersion, holdSeconds: 1800
120
+ });
121
+ }
122
+ catch (error) {
123
+ request.signal?.removeEventListener('abort', abort);
124
+ session.cancel();
125
+ await session.release();
126
+ throw error;
127
+ }
128
+ // Cancellation of the native operation is not release authority. The
129
+ // coordinator releases this lease only after native descendants stop.
130
+ request.signal?.removeEventListener('abort', abort);
131
+ return {
132
+ assertHeld: async () => { await session.send({ action: 'assert' }); },
133
+ replace: async (bytes, expectedVersion) => {
134
+ stateAssert(expectedVersion !== null && bytes.byteLength <= 32 * 1024 * 1024, 'unsupported-local-state-operation');
135
+ await session.send({ action: 'replace', expectedVersion, bytes: Buffer.from(bytes).toString('base64') });
136
+ },
137
+ remove: async () => { throw new StateMigrationError('unsupported-local-state-operation'); },
138
+ release: async () => {
139
+ request.signal?.removeEventListener('abort', abort);
140
+ await session.release();
141
+ }
142
+ };
143
+ }
144
+ }
145
+ //# sourceMappingURL=posix-native-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posix-native-lock.js","sourceRoot":"","sources":["../../../src/adapters/state/posix-native-lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { type ProtectedArtifactStorage, type ProtectedStateWorkspace, type ProtectedVolumeAttestor, type StateArtifactDescriptor, type StateArtifactPurpose, type StateEncryptionKeyProvider, type StateExecutionContext } from '../../domain/repair/stateful.js';
2
+ export type { ProtectedArtifactStorage, ProtectedVolumeAttestor, StateEncryptionKeyProvider } from '../../domain/repair/stateful.js';
3
+ export declare class EncryptedStateWorkspace implements ProtectedStateWorkspace {
4
+ #private;
5
+ private readonly options;
6
+ readonly workspaceRef: string;
7
+ constructor(options: {
8
+ workspaceId: string;
9
+ keyRef: string;
10
+ ownerId: string;
11
+ storage: ProtectedArtifactStorage;
12
+ keys: StateEncryptionKeyProvider;
13
+ maxArtifactBytes?: number;
14
+ maxWrittenBytes?: number;
15
+ timeoutMs?: number;
16
+ });
17
+ toJSON(): {
18
+ workspaceRef: string;
19
+ };
20
+ private bounded;
21
+ assertAvailable(context: StateExecutionContext): Promise<void>;
22
+ private key;
23
+ private id;
24
+ private aad;
25
+ private seal;
26
+ private unseal;
27
+ put(purpose: StateArtifactPurpose, scope: string, bytes: Uint8Array, id?: `${string}-${string}-${string}-${string}-${string}`): Promise<StateArtifactDescriptor>;
28
+ get(ref: string, purpose: StateArtifactPurpose, scope: string): Promise<Uint8Array>;
29
+ describe(ref: string, purpose: StateArtifactPurpose, scope: string): Promise<StateArtifactDescriptor | null>;
30
+ replace(ref: string, purpose: StateArtifactPurpose, scope: string, previousDigest: string, bytes: Uint8Array): Promise<StateArtifactDescriptor>;
31
+ removeExact(descriptor: StateArtifactDescriptor): Promise<void>;
32
+ withScratch<T>(context: StateExecutionContext, action: (directory: string) => Promise<T>): Promise<T>;
33
+ }
34
+ export declare function assertPrivateStatePath(target: string, context: StateExecutionContext, attestor: ProtectedVolumeAttestor, directory: boolean, now?: number): Promise<void>;
35
+ export declare function assertExistingStateSourcePath(target: string, context: StateExecutionContext): Promise<void>;
36
+ export declare class FilesystemProtectedArtifactStorage implements ProtectedArtifactStorage {
37
+ #private;
38
+ private readonly root;
39
+ private readonly attestor;
40
+ private readonly maxStoredBytes;
41
+ constructor(root: string, attestor: ProtectedVolumeAttestor, maxStoredBytes?: number);
42
+ toJSON(): {
43
+ storage: string;
44
+ };
45
+ assertAvailable(context: StateExecutionContext): Promise<void>;
46
+ private filename;
47
+ private durableCreate;
48
+ private syncDirectory;
49
+ private allocate;
50
+ create(id: string, bytes: Uint8Array): Promise<void>;
51
+ read(id: string): Promise<Uint8Array>;
52
+ private guarded;
53
+ compareExchange(id: string, previousDigest: string, bytes: Uint8Array): Promise<void>;
54
+ remove(id: string, expectedDigest: string): Promise<void>;
55
+ withScratch<T>(context: StateExecutionContext, action: (directory: string) => Promise<T>): Promise<T>;
56
+ }
57
+ export declare function protectedStateScope(context: StateExecutionContext): string;
@@ -0,0 +1,412 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, randomUUID } from 'node:crypto';
2
+ import { constants } from 'node:fs';
3
+ import { lstat, mkdir, open, readdir, realpath, rename, rm, stat, unlink } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { StateMigrationError } from '../../domain/repair/stateful.js';
6
+ import { stateAssert, stateDigest, stateObjectDigest } from '../../domain/repair/stateful-invariants.js';
7
+ import { boundedStateOperation } from '../../domain/repair/stateful-bounded.js';
8
+ import { cleanupOwnedStateScratch } from './owned-process.js';
9
+ const artifactId = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
10
+ const purposes = new Set(['inspection', 'state', 'plan', 'journal', 'backup', 'candidate', 'recovery']);
11
+ export class EncryptedStateWorkspace {
12
+ options;
13
+ workspaceRef;
14
+ #ready = false;
15
+ #writtenBytes = 0;
16
+ constructor(options) {
17
+ this.options = options;
18
+ stateAssert(artifactId.test(options.workspaceId), 'protected-workspace-required');
19
+ this.workspaceRef = `state-workspace:${options.workspaceId}`;
20
+ }
21
+ toJSON() { return { workspaceRef: this.workspaceRef }; }
22
+ bounded(action) {
23
+ const timeoutMs = this.options.timeoutMs ?? 30_000;
24
+ stateAssert(timeoutMs > 0 && timeoutMs <= 120_000, 'invalid-binding');
25
+ return boundedStateOperation(AbortSignal.timeout(timeoutMs), action);
26
+ }
27
+ async assertAvailable(context) {
28
+ this.#ready = false;
29
+ try {
30
+ await this.bounded(() => this.options.storage.assertAvailable(context));
31
+ const key = await this.bounded(() => this.options.keys.describe(this.options.keyRef, context));
32
+ stateAssert(key.keyRef === this.options.keyRef && key.ownerId === this.options.ownerId && key.hostId === context.hostId
33
+ && key.storage === 'external-key-provider' && key.algorithm === 'aes-256-gcm', 'key-unavailable');
34
+ await this.key(async () => undefined);
35
+ this.#ready = true;
36
+ }
37
+ catch (error) {
38
+ if (error instanceof StateMigrationError)
39
+ throw error;
40
+ throw new StateMigrationError('key-unavailable');
41
+ }
42
+ }
43
+ async key(action) {
44
+ let supplied = false;
45
+ try {
46
+ return await this.bounded(() => this.options.keys.withKey(this.options.keyRef, async (provided) => {
47
+ stateAssert(provided.byteLength === 32, 'key-unavailable');
48
+ const key = Buffer.from(provided);
49
+ supplied = true;
50
+ try {
51
+ return await action(key);
52
+ }
53
+ finally {
54
+ key.fill(0);
55
+ }
56
+ }));
57
+ }
58
+ catch (error) {
59
+ if (error instanceof StateMigrationError)
60
+ throw error;
61
+ if (supplied)
62
+ throw error;
63
+ throw new StateMigrationError('key-unavailable');
64
+ }
65
+ }
66
+ id(ref) {
67
+ const prefix = `${this.workspaceRef}/`;
68
+ stateAssert(ref.startsWith(prefix) && artifactId.test(ref.slice(prefix.length)), 'artifact-integrity');
69
+ return ref.slice(prefix.length);
70
+ }
71
+ aad(ref, purpose, scope) {
72
+ stateAssert(this.#ready, 'protected-workspace-required');
73
+ stateAssert(purposes.has(purpose) && /^[a-f0-9]{64}$/.test(scope), 'artifact-purpose');
74
+ return Buffer.from(JSON.stringify([this.workspaceRef, ref, purpose, scope, this.options.ownerId]));
75
+ }
76
+ async seal(ref, purpose, scope, bytes) {
77
+ stateAssert(bytes.byteLength <= (this.options.maxArtifactBytes ?? 64 * 1024 * 1024), 'storage-limit');
78
+ stateAssert(this.#writtenBytes + bytes.byteLength <= (this.options.maxWrittenBytes ?? 512 * 1024 * 1024), 'storage-limit');
79
+ const aad = this.aad(ref, purpose, scope);
80
+ const sealed = await this.key(async (key) => {
81
+ const nonce = randomBytes(12);
82
+ const cipher = createCipheriv('aes-256-gcm', key, nonce);
83
+ cipher.setAAD(aad);
84
+ const ciphertext = Buffer.concat([cipher.update(bytes), cipher.final()]);
85
+ const envelope = {
86
+ version: 1, keyRef: this.options.keyRef, nonce: nonce.toString('base64'),
87
+ tag: cipher.getAuthTag().toString('base64'), ciphertext: ciphertext.toString('base64')
88
+ };
89
+ return Buffer.from(JSON.stringify(envelope));
90
+ });
91
+ this.#writtenBytes += bytes.byteLength;
92
+ return sealed;
93
+ }
94
+ async unseal(ref, purpose, scope, bytes) {
95
+ stateAssert(bytes.byteLength <= (this.options.maxArtifactBytes ?? 64 * 1024 * 1024) * 2 + 4096, 'storage-limit');
96
+ const aad = this.aad(ref, purpose, scope);
97
+ try {
98
+ const envelope = JSON.parse(Buffer.from(bytes).toString('utf8'));
99
+ stateAssert(envelope.version === 1 && envelope.keyRef === this.options.keyRef, 'artifact-integrity');
100
+ stateAssert(typeof envelope.nonce === 'string' && typeof envelope.tag === 'string' && typeof envelope.ciphertext === 'string', 'artifact-integrity');
101
+ const nonce = Buffer.from(envelope.nonce, 'base64');
102
+ const tag = Buffer.from(envelope.tag, 'base64');
103
+ stateAssert(nonce.length === 12 && tag.length === 16, 'artifact-integrity');
104
+ return await this.key(async (key) => {
105
+ const cipher = createDecipheriv('aes-256-gcm', key, nonce);
106
+ cipher.setAAD(aad);
107
+ cipher.setAuthTag(tag);
108
+ return Buffer.concat([cipher.update(Buffer.from(envelope.ciphertext, 'base64')), cipher.final()]);
109
+ });
110
+ }
111
+ catch (error) {
112
+ if (error instanceof StateMigrationError && error.code === 'key-unavailable')
113
+ throw error;
114
+ throw new StateMigrationError('artifact-integrity');
115
+ }
116
+ }
117
+ async put(purpose, scope, bytes, id = randomUUID()) {
118
+ stateAssert(artifactId.test(id), 'artifact-integrity');
119
+ const ref = `${this.workspaceRef}/${id}`;
120
+ try {
121
+ const sealed = await this.seal(ref, purpose, scope, bytes);
122
+ await this.bounded(() => this.options.storage.create(id, sealed));
123
+ }
124
+ catch (error) {
125
+ if (error?.code === 'EEXIST')
126
+ throw new StateMigrationError('recovery-required');
127
+ throw error;
128
+ }
129
+ const verified = await this.get(ref, purpose, scope);
130
+ try {
131
+ stateAssert(stateDigest(verified) === stateDigest(bytes), 'artifact-integrity');
132
+ }
133
+ finally {
134
+ verified.fill(0);
135
+ }
136
+ return { ref, digest: stateDigest(bytes), purpose, scope };
137
+ }
138
+ async get(ref, purpose, scope) {
139
+ const id = this.id(ref);
140
+ return this.unseal(ref, purpose, scope, await this.bounded(() => this.options.storage.read(id)));
141
+ }
142
+ async describe(ref, purpose, scope) {
143
+ const id = this.id(ref);
144
+ this.aad(ref, purpose, scope);
145
+ const envelope = await this.bounded(() => this.options.storage.read(id)).catch((error) => {
146
+ if (error.code === 'ENOENT')
147
+ return null;
148
+ throw error;
149
+ });
150
+ if (!envelope)
151
+ return null;
152
+ const bytes = await this.unseal(ref, purpose, scope, envelope);
153
+ try {
154
+ return { ref, digest: stateDigest(bytes), purpose, scope };
155
+ }
156
+ finally {
157
+ bytes.fill(0);
158
+ }
159
+ }
160
+ async replace(ref, purpose, scope, previousDigest, bytes) {
161
+ const id = this.id(ref);
162
+ const priorEnvelope = await this.bounded(() => this.options.storage.read(id));
163
+ const prior = await this.unseal(ref, purpose, scope, priorEnvelope);
164
+ try {
165
+ stateAssert(stateDigest(prior) === previousDigest, 'artifact-integrity');
166
+ }
167
+ finally {
168
+ prior.fill(0);
169
+ }
170
+ const sealed = await this.seal(ref, purpose, scope, bytes);
171
+ await this.bounded(() => this.options.storage.compareExchange(id, stateDigest(priorEnvelope), sealed));
172
+ const verified = await this.get(ref, purpose, scope);
173
+ try {
174
+ stateAssert(stateDigest(verified) === stateDigest(bytes), 'artifact-integrity');
175
+ }
176
+ finally {
177
+ verified.fill(0);
178
+ }
179
+ return { ref, digest: stateDigest(bytes), purpose, scope };
180
+ }
181
+ async removeExact(descriptor) {
182
+ const id = this.id(descriptor.ref);
183
+ const envelope = await this.bounded(() => this.options.storage.read(id));
184
+ const bytes = await this.unseal(descriptor.ref, descriptor.purpose, descriptor.scope, envelope);
185
+ try {
186
+ stateAssert(stateDigest(bytes) === descriptor.digest, 'artifact-integrity');
187
+ }
188
+ finally {
189
+ bytes.fill(0);
190
+ }
191
+ await this.bounded(() => this.options.storage.remove(id, stateDigest(envelope)));
192
+ }
193
+ async withScratch(context, action) {
194
+ await this.assertAvailable(context);
195
+ return this.options.storage.withScratch(context, action);
196
+ }
197
+ }
198
+ function isUnder(root, candidate) {
199
+ const relative = path.relative(root.toLowerCase(), candidate.toLowerCase());
200
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
201
+ }
202
+ export async function assertPrivateStatePath(target, context, attestor, directory, now = Date.now()) {
203
+ stateAssert(path.isAbsolute(target) && path.normalize(target) === target && !isUnder(context.projectRoot, target), 'unsafe-path');
204
+ const parent = directory ? target : path.dirname(target);
205
+ let current = parent;
206
+ while (true) {
207
+ const info = await lstat(current).catch(() => { throw new StateMigrationError('unsafe-path'); });
208
+ stateAssert(info.isDirectory() && !info.isSymbolicLink(), 'unsafe-path');
209
+ const git = await lstat(path.join(current, '.git')).then(() => true, (error) => {
210
+ if (error.code === 'ENOENT')
211
+ return false;
212
+ throw new StateMigrationError('unsafe-path');
213
+ });
214
+ stateAssert(!git, 'unsafe-path');
215
+ const next = path.dirname(current);
216
+ if (current === next)
217
+ break;
218
+ current = next;
219
+ }
220
+ stateAssert(await realpath(parent) === parent, 'unsafe-path');
221
+ const info = await stat(parent);
222
+ if (process.platform !== 'win32') {
223
+ stateAssert((info.mode & 0o077) === 0 && (process.getuid === undefined || info.uid === process.getuid()), 'unsafe-path');
224
+ }
225
+ const proof = await attestor.verify(parent, context);
226
+ stateAssert(proof.canonicalDirectory === parent && proof.hostId === context.hostId && proof.encryptedVolume === true
227
+ && proof.privateAccess === true && proof.storageClass === 'protected-state-workspace' && proof.expiresAt > now, 'protected-workspace-required');
228
+ if (!directory) {
229
+ const file = await lstat(target).catch((error) => {
230
+ if (error.code === 'ENOENT')
231
+ return null;
232
+ throw new StateMigrationError('unsafe-path');
233
+ });
234
+ stateAssert(!file || (file.isFile() && !file.isSymbolicLink() && file.nlink === 1
235
+ && (process.platform === 'win32' || ((file.mode & 0o077) === 0 && (process.getuid === undefined || file.uid === process.getuid())))), 'unsafe-path');
236
+ }
237
+ }
238
+ export async function assertExistingStateSourcePath(target, context) {
239
+ stateAssert(path.isAbsolute(target) && path.normalize(target) === target
240
+ && isUnder(context.projectRoot, target) && target !== context.projectRoot
241
+ && !path.relative(context.projectRoot, target).split(path.sep).some((part) => part.toLowerCase() === '.git'), 'unsafe-path');
242
+ stateAssert(await realpath(context.projectRoot) === context.projectRoot, 'unsafe-path');
243
+ let directory = path.dirname(target);
244
+ while (true) {
245
+ const info = await lstat(directory);
246
+ stateAssert(info.isDirectory() && !info.isSymbolicLink(), 'unsafe-path');
247
+ if (directory === context.projectRoot)
248
+ break;
249
+ const parent = path.dirname(directory);
250
+ stateAssert(parent !== directory && isUnder(context.projectRoot, parent), 'unsafe-path');
251
+ directory = parent;
252
+ }
253
+ const file = await lstat(target).catch((error) => {
254
+ if (error.code === 'ENOENT')
255
+ return null;
256
+ throw new StateMigrationError('unsafe-path');
257
+ });
258
+ stateAssert(!file || (file.isFile() && !file.isSymbolicLink() && file.nlink === 1
259
+ && (process.platform === 'win32' || ((file.mode & 0o077) === 0 && (process.getuid === undefined || file.uid === process.getuid())))), 'unsafe-path');
260
+ }
261
+ export class FilesystemProtectedArtifactStorage {
262
+ root;
263
+ attestor;
264
+ maxStoredBytes;
265
+ #context = null;
266
+ constructor(root, attestor, maxStoredBytes = 768 * 1024 * 1024) {
267
+ this.root = root;
268
+ this.attestor = attestor;
269
+ this.maxStoredBytes = maxStoredBytes;
270
+ }
271
+ toJSON() { return { storage: 'protected-external' }; }
272
+ async assertAvailable(context) {
273
+ await assertPrivateStatePath(this.root, context, this.attestor, true);
274
+ this.#context = context;
275
+ }
276
+ async filename(id) {
277
+ stateAssert(artifactId.test(id) && this.#context, 'artifact-integrity');
278
+ const filename = path.join(this.root, `${id}.sealed`);
279
+ await assertPrivateStatePath(filename, this.#context, this.attestor, false);
280
+ return filename;
281
+ }
282
+ async durableCreate(filename, bytes) {
283
+ const file = await open(filename, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
284
+ try {
285
+ await file.writeFile(bytes);
286
+ await file.sync();
287
+ }
288
+ finally {
289
+ await file.close();
290
+ }
291
+ }
292
+ async syncDirectory() {
293
+ if (process.platform === 'win32')
294
+ return;
295
+ const directory = await open(this.root, 'r');
296
+ try {
297
+ await directory.sync();
298
+ }
299
+ finally {
300
+ await directory.close();
301
+ }
302
+ }
303
+ async allocate(bytes, action) {
304
+ stateAssert(this.#context, 'protected-workspace-required');
305
+ await assertPrivateStatePath(this.root, this.#context, this.attestor, true);
306
+ const guardPath = path.join(this.root, '.liftoff-state-store.guard');
307
+ const guard = await open(guardPath, 'wx', 0o600).catch(() => { throw new StateMigrationError('lock-unavailable'); });
308
+ try {
309
+ let used = 0;
310
+ let entries = 0;
311
+ const directories = [this.root];
312
+ while (directories.length) {
313
+ const directory = directories.pop();
314
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
315
+ stateAssert(++entries <= 10_000, 'storage-limit');
316
+ const filename = path.join(directory, entry.name);
317
+ const info = await lstat(filename).catch((error) => {
318
+ if (error.code === 'ENOENT')
319
+ return null;
320
+ throw error;
321
+ });
322
+ if (!info)
323
+ continue;
324
+ if (info.isDirectory() && !info.isSymbolicLink())
325
+ directories.push(filename);
326
+ else
327
+ used += info.size;
328
+ stateAssert(used + bytes <= this.maxStoredBytes, 'storage-limit');
329
+ }
330
+ }
331
+ await action();
332
+ }
333
+ finally {
334
+ await guard.close();
335
+ await unlink(guardPath);
336
+ }
337
+ }
338
+ async create(id, bytes) {
339
+ stateAssert(bytes.byteLength <= this.maxStoredBytes, 'storage-limit');
340
+ const filename = await this.filename(id);
341
+ await this.allocate(bytes.byteLength, () => this.durableCreate(filename, bytes));
342
+ await this.syncDirectory();
343
+ }
344
+ async read(id) {
345
+ const file = await open(await this.filename(id), constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
346
+ try {
347
+ const info = await file.stat();
348
+ stateAssert(info.isFile() && info.nlink === 1 && info.size <= Math.min(this.maxStoredBytes, 128 * 1024 * 1024 + 4096), 'artifact-integrity');
349
+ return await file.readFile();
350
+ }
351
+ finally {
352
+ await file.close();
353
+ }
354
+ }
355
+ async guarded(id, previousDigest, action) {
356
+ const filename = await this.filename(id);
357
+ const guard = `${filename}.guard`;
358
+ const lock = await open(guard, 'wx', 0o600).catch(() => { throw new StateMigrationError('lock-unavailable'); });
359
+ try {
360
+ stateAssert(stateDigest(await this.read(id)) === previousDigest, 'artifact-integrity');
361
+ await action(filename);
362
+ await this.syncDirectory();
363
+ }
364
+ finally {
365
+ await lock.close();
366
+ await unlink(guard);
367
+ }
368
+ }
369
+ async compareExchange(id, previousDigest, bytes) {
370
+ stateAssert(bytes.byteLength <= this.maxStoredBytes, 'storage-limit');
371
+ await this.allocate(bytes.byteLength, () => this.guarded(id, previousDigest, async (filename) => {
372
+ const staging = path.join(this.root, `${randomUUID()}.sealed`);
373
+ await this.durableCreate(staging, bytes);
374
+ try {
375
+ await rename(staging, filename);
376
+ }
377
+ finally {
378
+ await unlink(staging).catch((error) => { if (error.code !== 'ENOENT')
379
+ throw error; });
380
+ }
381
+ }));
382
+ }
383
+ async remove(id, expectedDigest) {
384
+ await this.guarded(id, expectedDigest, (filename) => unlink(filename));
385
+ }
386
+ async withScratch(context, action) {
387
+ await this.assertAvailable(context);
388
+ const directory = path.join(this.root, `native-${randomUUID()}`);
389
+ await mkdir(directory, { mode: 0o700 });
390
+ let terminationUnproven = false;
391
+ try {
392
+ return await action(directory);
393
+ }
394
+ catch (error) {
395
+ terminationUnproven = error instanceof StateMigrationError && error.code === 'process-tree-termination-unproven';
396
+ throw error;
397
+ }
398
+ finally {
399
+ if (!terminationUnproven) {
400
+ await cleanupOwnedStateScratch(directory, async () => {
401
+ const info = await lstat(directory);
402
+ stateAssert(info.isDirectory() && !info.isSymbolicLink() && await realpath(directory) === directory, 'unsafe-path');
403
+ await rm(directory, { recursive: true });
404
+ });
405
+ }
406
+ }
407
+ }
408
+ }
409
+ export function protectedStateScope(context) {
410
+ return stateObjectDigest({ projectId: context.projectId, projectRoot: context.projectRoot, hostId: context.hostId });
411
+ }
412
+ //# sourceMappingURL=protected-workspace.js.map