spawnfile 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auth/importers.js +21 -5
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/profileStore.js +4 -1
- package/dist/auth/trainingGrokWriteback.d.ts +4 -0
- package/dist/auth/trainingGrokWriteback.js +119 -0
- package/dist/auth/trainingGrokWritebackLock.d.ts +5 -0
- package/dist/auth/trainingGrokWritebackLock.js +56 -0
- package/dist/cli/authCommands.js +9 -5
- package/dist/cli/compileBuildCommands.js +4 -0
- package/dist/cli/devCommands.js +4 -2
- package/dist/cli/downCommand.d.ts +2 -0
- package/dist/cli/downCommand.js +3 -0
- package/dist/cli/envFileOption.d.ts +18 -0
- package/dist/cli/envFileOption.js +20 -0
- package/dist/cli/lifecycleCommands.d.ts +1 -1
- package/dist/cli/lifecycleCommands.js +3 -1
- package/dist/cli/lifecyclePlan.d.ts +1 -0
- package/dist/cli/lifecyclePlan.js +2 -0
- package/dist/cli/paideiaDelegation.d.ts +2 -1
- package/dist/cli/paideiaDelegation.js +5 -4
- package/dist/cli/recordCommands.d.ts +17 -0
- package/dist/cli/recordCommands.js +43 -0
- package/dist/cli/releaseCommand.d.ts +6 -0
- package/dist/cli/releaseCommand.js +91 -0
- package/dist/cli/runCli.d.ts +7 -1
- package/dist/cli/runCli.js +11 -6
- package/dist/cli/runPublishCommands.js +5 -3
- package/dist/cli/trainCommand.js +73 -20
- package/dist/cli/upCommand.js +5 -1
- package/dist/cli/volumeCommands.d.ts +8 -0
- package/dist/cli/volumeCommands.js +53 -0
- package/dist/compiler/agentNodeMerge.js +3 -2
- package/dist/compiler/buildCompilePlan.js +4 -0
- package/dist/compiler/buildCompilePlanAgent.js +3 -1
- package/dist/compiler/buildCompilePlanTraversal.js +2 -0
- package/dist/compiler/buildCompilePlanTraversalHelpers.d.ts +2 -2
- package/dist/compiler/buildCompilePlanTraversalHelpers.js +4 -3
- package/dist/compiler/buildProject.d.ts +10 -0
- package/dist/compiler/buildProject.js +14 -2
- package/dist/compiler/cleanCompileOutput.d.ts +2 -0
- package/dist/compiler/cleanCompileOutput.js +14 -0
- package/dist/compiler/compilePlanHelpers.js +2 -0
- package/dist/compiler/compileProject.d.ts +10 -0
- package/dist/compiler/compileProject.js +29 -3
- package/dist/compiler/containerArtifactSummaries.js +1 -0
- package/dist/compiler/containerArtifacts.d.ts +4 -4
- package/dist/compiler/containerArtifacts.js +18 -34
- package/dist/compiler/containerArtifactsPlans.js +7 -0
- package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
- package/dist/compiler/containerDaimonBrokerRender.d.ts +3 -1
- package/dist/compiler/containerDaimonBrokerRender.js +6 -3
- package/dist/compiler/containerDaimonGrokTaskClock.d.ts +5 -0
- package/dist/compiler/containerDaimonGrokTaskClock.js +29 -0
- package/dist/compiler/containerDaimonGrokWorkerProvisioning.d.ts +1 -1
- package/dist/compiler/containerDaimonGrokWorkerProvisioning.js +38 -16
- package/dist/compiler/containerDaimonGrokWorkerRender.d.ts +33 -8
- package/dist/compiler/containerDaimonGrokWorkerRender.js +18 -5
- package/dist/compiler/containerDaimonOwnershipGuardRender.d.ts +14 -1
- package/dist/compiler/containerDaimonOwnershipGuardRender.js +15 -2
- package/dist/compiler/containerDaimonUidEntrypointRender.js +6 -12
- package/dist/compiler/containerTargetResources.d.ts +10 -0
- package/dist/compiler/containerTargetResources.js +21 -13
- package/dist/compiler/containerWorkspaceResourceRender.js +4 -1
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/mcpPlaceholders.d.ts +11 -0
- package/dist/compiler/mcpPlaceholders.js +58 -0
- package/dist/compiler/mcpServerInheritance.d.ts +18 -0
- package/dist/compiler/mcpServerInheritance.js +43 -0
- package/dist/compiler/moltnetArtifactTypes.d.ts +1 -0
- package/dist/compiler/moltnetArtifacts.js +1 -0
- package/dist/compiler/preflightCompilePlan.d.ts +9 -0
- package/dist/compiler/preflightCompilePlan.js +38 -0
- package/dist/compiler/recordingAgentFacts.d.ts +4 -0
- package/dist/compiler/recordingAgentFacts.js +35 -0
- package/dist/compiler/recordingCapability.d.ts +4 -0
- package/dist/compiler/recordingCapability.js +22 -0
- package/dist/compiler/recordingReport.d.ts +4 -0
- package/dist/compiler/recordingReport.js +18 -0
- package/dist/compiler/runProject.d.ts +2 -1
- package/dist/compiler/runProject.js +2 -0
- package/dist/compiler/surfaces.d.ts +1 -2
- package/dist/compiler/surfaces.js +0 -1
- package/dist/compiler/syncProjectAuth.js +2 -1
- package/dist/compiler/training/broker/clockProbe.d.ts +6 -0
- package/dist/compiler/training/broker/clockProbe.js +27 -0
- package/dist/compiler/training/broker/declaration.d.ts +4 -2
- package/dist/compiler/training/broker/declaration.js +4 -2
- package/dist/compiler/training/broker/entrypoint.js +33 -18
- package/dist/compiler/training/broker/evaluatorSecrets.d.ts +2 -0
- package/dist/compiler/training/broker/evaluatorSecrets.js +60 -0
- package/dist/compiler/training/broker/index.d.ts +3 -2
- package/dist/compiler/training/broker/index.js +3 -2
- package/dist/compiler/training/broker/main.js +1 -1
- package/dist/compiler/training/broker/paths.d.ts +96 -56
- package/dist/compiler/training/broker/paths.js +97 -89
- package/dist/compiler/training/broker/processes.d.ts +9 -0
- package/dist/compiler/training/broker/processes.js +4 -1
- package/dist/compiler/training/broker/projection.d.ts +3 -2
- package/dist/compiler/training/broker/projection.js +21 -9
- package/dist/compiler/training/broker/provisioning.d.ts +6 -15
- package/dist/compiler/training/broker/provisioning.js +71 -53
- package/dist/compiler/training/broker/registration.d.ts +3 -1
- package/dist/compiler/training/broker/registration.js +29 -21
- package/dist/compiler/training/broker/reset.d.ts +9 -0
- package/dist/compiler/training/broker/reset.js +71 -0
- package/dist/compiler/training/broker/runtime.d.ts +10 -13
- package/dist/compiler/training/broker/runtime.js +69 -115
- package/dist/compiler/training/broker/supervisor.d.ts +10 -11
- package/dist/compiler/training/broker/supervisor.js +22 -23
- package/dist/compiler/training/broker/taskClock.d.ts +5 -0
- package/dist/compiler/training/broker/taskClock.js +41 -0
- package/dist/compiler/training/compiledAgent.d.ts +21 -0
- package/dist/compiler/training/compiledAgent.js +62 -0
- package/dist/compiler/training/container/contract.d.ts +23 -0
- package/dist/compiler/training/container/contract.js +23 -5
- package/dist/compiler/training/container/evaluators.d.ts +48 -0
- package/dist/compiler/training/container/evaluators.js +50 -0
- package/dist/compiler/training/container/grokWriteback.d.ts +8 -0
- package/dist/compiler/training/container/grokWriteback.js +26 -0
- package/dist/compiler/training/container/index.d.ts +2 -1
- package/dist/compiler/training/container/index.js +2 -1
- package/dist/compiler/training/container/launch.d.ts +3 -0
- package/dist/compiler/training/container/launch.js +119 -28
- package/dist/compiler/training/container/nativeSlots.d.ts +14 -0
- package/dist/compiler/training/container/nativeSlots.js +13 -0
- package/dist/compiler/training/container/outputCopy.d.ts +2 -0
- package/dist/compiler/training/container/outputCopy.js +72 -0
- package/dist/compiler/training/container/outputVolume.d.ts +27 -0
- package/dist/compiler/training/container/outputVolume.js +161 -0
- package/dist/compiler/training/container/prepare.d.ts +3 -1
- package/dist/compiler/training/container/prepare.js +57 -12
- package/dist/compiler/training/container/security.d.ts +5 -3
- package/dist/compiler/training/container/security.js +15 -5
- package/dist/compiler/training/container/subjectIsolation.d.ts +5 -0
- package/dist/compiler/training/container/subjectIsolation.js +15 -0
- package/dist/compiler/training/context.d.ts +8 -0
- package/dist/compiler/training/context.js +12 -12
- package/dist/compiler/training/contract.d.ts +2 -0
- package/dist/compiler/training/contract.js +2 -0
- package/dist/compiler/training/execution.d.ts +417 -0
- package/dist/compiler/training/execution.js +160 -0
- package/dist/compiler/training/index.d.ts +8 -0
- package/dist/compiler/training/index.js +7 -0
- package/dist/compiler/training/moments.d.ts +80 -0
- package/dist/compiler/training/moments.js +118 -0
- package/dist/compiler/training/output.d.ts +2 -0
- package/dist/compiler/training/output.js +20 -0
- package/dist/compiler/training/plan/contract.d.ts +287 -0
- package/dist/compiler/training/plan/contract.js +49 -0
- package/dist/compiler/training/plan/index.d.ts +5 -0
- package/dist/compiler/training/plan/index.js +4 -0
- package/dist/compiler/training/plan/load.d.ts +9 -0
- package/dist/compiler/training/plan/load.js +153 -0
- package/dist/compiler/training/plan/references.d.ts +19 -0
- package/dist/compiler/training/plan/references.js +57 -0
- package/dist/compiler/training/plan/resolve.d.ts +4 -0
- package/dist/compiler/training/plan/resolve.js +73 -0
- package/dist/compiler/training/plan/targets.d.ts +11 -0
- package/dist/compiler/training/plan/targets.js +71 -0
- package/dist/compiler/training/plan/types.d.ts +36 -0
- package/dist/compiler/training/plan/types.js +1 -0
- package/dist/compiler/training/preparation/assets/Dockerfile +13 -2
- package/dist/compiler/training/preparation/assets/package-lock.json +2 -2
- package/dist/compiler/training/preparation/broker.d.ts +3 -3
- package/dist/compiler/training/preparation/broker.js +6 -4
- package/dist/compiler/training/preparation/contract.d.ts +27 -2
- package/dist/compiler/training/preparation/contract.js +23 -4
- package/dist/compiler/training/preparation/files.d.ts +5 -1
- package/dist/compiler/training/preparation/files.js +76 -15
- package/dist/compiler/training/preparation/image.js +9 -4
- package/dist/compiler/training/preparation/inputs.d.ts +1 -0
- package/dist/compiler/training/preparation/inputs.js +18 -10
- package/dist/compiler/training/preparation/prepare.d.ts +5 -0
- package/dist/compiler/training/preparation/prepare.js +66 -29
- package/dist/compiler/training/preparation/scratch.d.ts +6 -17
- package/dist/compiler/training/preparation/scratch.js +89 -23
- package/dist/compiler/training/repair/contract.d.ts +338 -4
- package/dist/compiler/training/repair/contract.js +2 -0
- package/dist/compiler/training/repair/index.d.ts +1 -1
- package/dist/compiler/training/repair/index.js +1 -1
- package/dist/compiler/training/repair/prepare.d.ts +155 -3
- package/dist/compiler/training/repair/prepare.js +6 -2
- package/dist/compiler/training/repair/witness.d.ts +152 -3
- package/dist/compiler/training/repair/witness.js +8 -2
- package/dist/compiler/training/resolution.d.ts +25 -0
- package/dist/compiler/training/resolution.js +69 -0
- package/dist/compiler/training/rewind/contract.d.ts +321 -0
- package/dist/compiler/training/rewind/contract.js +39 -0
- package/dist/compiler/training/rewind/deployedResource.d.ts +22 -0
- package/dist/compiler/training/rewind/deployedResource.js +19 -0
- package/dist/compiler/training/rewind/feed.d.ts +12 -0
- package/dist/compiler/training/rewind/feed.js +182 -0
- package/dist/compiler/training/rewind/feedContent.d.ts +6 -0
- package/dist/compiler/training/rewind/feedContent.js +38 -0
- package/dist/compiler/training/rewind/git.d.ts +8 -0
- package/dist/compiler/training/rewind/git.js +159 -0
- package/dist/compiler/training/rewind/gitArchive.d.ts +2 -0
- package/dist/compiler/training/rewind/gitArchive.js +7 -0
- package/dist/compiler/training/rewind/gitHistory.d.ts +3 -0
- package/dist/compiler/training/rewind/gitHistory.js +55 -0
- package/dist/compiler/training/rewind/gitProcess.d.ts +19 -0
- package/dist/compiler/training/rewind/gitProcess.js +81 -0
- package/dist/compiler/training/rewind/gitTree.d.ts +14 -0
- package/dist/compiler/training/rewind/gitTree.js +90 -0
- package/dist/compiler/training/rewind/history.d.ts +28 -0
- package/dist/compiler/training/rewind/history.js +239 -0
- package/dist/compiler/training/rewind/index.d.ts +18 -0
- package/dist/compiler/training/rewind/index.js +15 -0
- package/dist/compiler/training/rewind/inputs.d.ts +9 -0
- package/dist/compiler/training/rewind/inputs.js +67 -0
- package/dist/compiler/training/rewind/instant.d.ts +4 -0
- package/dist/compiler/training/rewind/instant.js +24 -0
- package/dist/compiler/training/rewind/leakCheck.d.ts +10 -0
- package/dist/compiler/training/rewind/leakCheck.js +56 -0
- package/dist/compiler/training/rewind/leakFeed.d.ts +5 -0
- package/dist/compiler/training/rewind/leakFeed.js +25 -0
- package/dist/compiler/training/rewind/leakFiles.d.ts +10 -0
- package/dist/compiler/training/rewind/leakFiles.js +106 -0
- package/dist/compiler/training/rewind/leakGit.d.ts +3 -0
- package/dist/compiler/training/rewind/leakGit.js +78 -0
- package/dist/compiler/training/rewind/leakGitPayload.d.ts +4 -0
- package/dist/compiler/training/rewind/leakGitPayload.js +57 -0
- package/dist/compiler/training/rewind/leakInputs.d.ts +7 -0
- package/dist/compiler/training/rewind/leakInputs.js +135 -0
- package/dist/compiler/training/rewind/leakLayout.d.ts +2 -0
- package/dist/compiler/training/rewind/leakLayout.js +17 -0
- package/dist/compiler/training/rewind/leakProvenance.d.ts +3 -0
- package/dist/compiler/training/rewind/leakProvenance.js +88 -0
- package/dist/compiler/training/rewind/materialize.d.ts +136 -0
- package/dist/compiler/training/rewind/materialize.js +212 -0
- package/dist/compiler/training/rewind/memory.d.ts +10 -0
- package/dist/compiler/training/rewind/memory.js +84 -0
- package/dist/compiler/training/rewind/memorySchemas.d.ts +87 -0
- package/dist/compiler/training/rewind/memorySchemas.js +50 -0
- package/dist/compiler/training/rewind/moltnet.d.ts +2 -0
- package/dist/compiler/training/rewind/moltnet.js +54 -0
- package/dist/compiler/training/rewind/moltnetJson.d.ts +13 -0
- package/dist/compiler/training/rewind/moltnetJson.js +180 -0
- package/dist/compiler/training/rewind/moltnetSchemas.d.ts +1226 -0
- package/dist/compiler/training/rewind/moltnetSchemas.js +75 -0
- package/dist/compiler/training/rewind/moltnetSqlite.d.ts +31 -0
- package/dist/compiler/training/rewind/moltnetSqlite.js +192 -0
- package/dist/compiler/training/rewind/moltnetSqliteData.d.ts +10 -0
- package/dist/compiler/training/rewind/moltnetSqliteData.js +113 -0
- package/dist/compiler/training/rewind/recordInput.d.ts +40 -0
- package/dist/compiler/training/rewind/recordInput.js +49 -0
- package/dist/compiler/training/rewind/recordedBundle.d.ts +4 -0
- package/dist/compiler/training/rewind/recordedBundle.js +58 -0
- package/dist/compiler/training/rewind/recordedCommit.d.ts +2 -0
- package/dist/compiler/training/rewind/recordedCommit.js +10 -0
- package/dist/compiler/training/rewind/select.d.ts +18 -0
- package/dist/compiler/training/rewind/select.js +64 -0
- package/dist/compiler/training/rewind/snapshotManifest.d.ts +42 -0
- package/dist/compiler/training/rewind/snapshotManifest.js +134 -0
- package/dist/compiler/training/rewind/taskClock.d.ts +12 -0
- package/dist/compiler/training/rewind/taskClock.js +40 -0
- package/dist/compiler/training/rewind/timestamps.d.ts +4 -0
- package/dist/compiler/training/rewind/timestamps.js +26 -0
- package/dist/compiler/training/rewind/types.d.ts +43 -0
- package/dist/compiler/training/rewind/types.js +1 -0
- package/dist/compiler/training/rewind/volumes.d.ts +12 -0
- package/dist/compiler/training/rewind/volumes.js +58 -0
- package/dist/compiler/training/selection.d.ts +7 -0
- package/dist/compiler/training/selection.js +15 -0
- package/dist/compiler/types.d.ts +6 -3
- package/dist/compiler/upProject.d.ts +1 -0
- package/dist/compiler/upProject.js +21 -5
- package/dist/compiler/workspaceBundleArtifacts.d.ts +16 -1
- package/dist/compiler/workspaceBundleArtifacts.js +69 -20
- package/dist/compiler/workspaceBundleCache.d.ts +23 -0
- package/dist/compiler/workspaceBundleCache.js +79 -0
- package/dist/compiler/workspaceBundleDependencies.d.ts +21 -0
- package/dist/compiler/workspaceBundleDependencies.js +122 -0
- package/dist/compiler/workspaceBundleFiles.d.ts +51 -0
- package/dist/compiler/workspaceBundleFiles.js +251 -0
- package/dist/compiler/workspaceBundleGenerated.d.ts +25 -0
- package/dist/compiler/workspaceBundleGenerated.js +103 -0
- package/dist/compiler/workspaceBundleGit.d.ts +64 -0
- package/dist/compiler/workspaceBundleGit.js +154 -0
- package/dist/compiler/workspaceBundleKey.d.ts +34 -0
- package/dist/compiler/workspaceBundleKey.js +17 -0
- package/dist/compiler/workspaceBundleResolve.d.ts +56 -0
- package/dist/compiler/workspaceBundleResolve.js +144 -0
- package/dist/compiler/workspaceBundleRun.d.ts +41 -0
- package/dist/compiler/workspaceBundleRun.js +79 -0
- package/dist/compiler/workspaceBundleTar.d.ts +47 -0
- package/dist/compiler/workspaceBundleTar.js +140 -0
- package/dist/compiler/workspaceBundleTree.d.ts +15 -0
- package/dist/compiler/workspaceBundleTree.js +58 -0
- package/dist/compiler/workspaceGitFetch.d.ts +41 -0
- package/dist/compiler/workspaceGitFetch.js +155 -0
- package/dist/compiler/workspaceGitPins.d.ts +33 -0
- package/dist/compiler/workspaceGitPins.js +127 -0
- package/dist/compiler/workspaceResources.d.ts +5 -1
- package/dist/compiler/workspaceResources.js +32 -5
- package/dist/deployment/activation.d.ts +3 -0
- package/dist/deployment/activation.js +12 -0
- package/dist/deployment/canaryCutover.d.ts +1 -1
- package/dist/deployment/candidateRecording.d.ts +8 -0
- package/dist/deployment/candidateRecording.js +22 -0
- package/dist/deployment/dockerManager.d.ts +5 -3
- package/dist/deployment/dockerManager.js +12 -2
- package/dist/deployment/downDeployment.d.ts +2 -0
- package/dist/deployment/downDeployment.js +6 -2
- package/dist/deployment/homeStore.d.ts +9 -1
- package/dist/deployment/homeStore.js +16 -53
- package/dist/deployment/index.d.ts +6 -0
- package/dist/deployment/index.js +6 -0
- package/dist/deployment/pidLock.d.ts +25 -0
- package/dist/deployment/pidLock.js +81 -0
- package/dist/deployment/record.d.ts +85 -1
- package/dist/deployment/record.js +11 -5
- package/dist/deployment/recordPublication.d.ts +15 -0
- package/dist/deployment/recordPublication.js +76 -0
- package/dist/deployment/recordedMemoryRetention.d.ts +18 -0
- package/dist/deployment/recordedMemoryRetention.js +27 -0
- package/dist/deployment/resourceLog.d.ts +140 -0
- package/dist/deployment/resourceLog.js +78 -0
- package/dist/distribution/buildDistributionReport.d.ts +3 -0
- package/dist/distribution/buildDistributionReport.js +5 -0
- package/dist/distribution/consumeImage.d.ts +3 -0
- package/dist/distribution/consumeImage.js +23 -31
- package/dist/distribution/consumeImageLifecycle.d.ts +4 -1
- package/dist/distribution/consumeImageLifecycle.js +14 -3
- package/dist/distribution/consumeImageSupport.d.ts +7 -0
- package/dist/distribution/consumeImageSupport.js +25 -0
- package/dist/distribution/distributionReportSchema.d.ts +47 -0
- package/dist/distribution/distributionReportSchema.js +20 -0
- package/dist/distribution/renderImageInterface.js +1 -1
- package/dist/distribution/types.d.ts +15 -0
- package/dist/filesystem/durableWrite.d.ts +10 -0
- package/dist/filesystem/durableWrite.js +87 -0
- package/dist/filesystem/index.d.ts +2 -0
- package/dist/filesystem/index.js +2 -0
- package/dist/filesystem/retainedJsonl.d.ts +23 -0
- package/dist/filesystem/retainedJsonl.js +123 -0
- package/dist/manifest/commandSchemas.d.ts +5 -0
- package/dist/manifest/commandSchemas.js +5 -0
- package/dist/manifest/executionSchemas.d.ts +20 -0
- package/dist/manifest/executionSchemas.js +13 -3
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.js +2 -0
- package/dist/manifest/loadManifest.js +3 -26
- package/dist/manifest/mcpSchemas.d.ts +61 -0
- package/dist/manifest/mcpSchemas.js +57 -11
- package/dist/manifest/memorySchemas.d.ts +1 -1
- package/dist/manifest/memorySchemas.js +1 -1
- package/dist/manifest/parseManifest.d.ts +7 -0
- package/dist/manifest/parseManifest.js +65 -0
- package/dist/manifest/renderSpawnfile.js +4 -1
- package/dist/manifest/renderSpawnfileWorkspace.js +6 -2
- package/dist/manifest/schemas.d.ts +1206 -132
- package/dist/manifest/schemas.js +14 -4
- package/dist/manifest/teamNetworkSchemas.d.ts +1 -1
- package/dist/manifest/trainingDataSchemas.d.ts +357 -0
- package/dist/manifest/trainingDataSchemas.js +31 -0
- package/dist/manifest/trainingSchemas.d.ts +49 -0
- package/dist/manifest/trainingSchemas.js +44 -0
- package/dist/manifest/volumeFeedSchemas.d.ts +52 -0
- package/dist/manifest/volumeFeedSchemas.js +104 -0
- package/dist/manifest/workspaceSchemas.d.ts +182 -5
- package/dist/manifest/workspaceSchemas.js +111 -7
- package/dist/recording/compose.d.ts +3 -0
- package/dist/recording/compose.js +93 -0
- package/dist/recording/deployment.d.ts +15 -0
- package/dist/recording/deployment.js +38 -0
- package/dist/recording/history.d.ts +16 -0
- package/dist/recording/history.js +47 -0
- package/dist/recording/index.d.ts +9 -0
- package/dist/recording/index.js +9 -0
- package/dist/recording/moment.d.ts +61 -0
- package/dist/recording/moment.js +36 -0
- package/dist/recording/parse.d.ts +15 -0
- package/dist/recording/parse.js +74 -0
- package/dist/recording/read.d.ts +8 -0
- package/dist/recording/read.js +61 -0
- package/dist/recording/store.d.ts +10 -0
- package/dist/recording/store.js +94 -0
- package/dist/recording/sync.d.ts +24 -0
- package/dist/recording/sync.js +126 -0
- package/dist/recording/types.d.ts +45 -0
- package/dist/recording/types.js +2 -0
- package/dist/release/drainControl.d.ts +54 -0
- package/dist/release/drainControl.js +137 -0
- package/dist/release/drainPhase.d.ts +37 -0
- package/dist/release/drainPhase.js +161 -0
- package/dist/release/index.d.ts +11 -0
- package/dist/release/index.js +11 -0
- package/dist/release/ledger.d.ts +75 -0
- package/dist/release/ledger.js +111 -0
- package/dist/release/notify.d.ts +58 -0
- package/dist/release/notify.js +126 -0
- package/dist/release/pending.d.ts +35 -0
- package/dist/release/pending.js +60 -0
- package/dist/release/postDeploy.d.ts +32 -0
- package/dist/release/postDeploy.js +62 -0
- package/dist/release/releaseDefaults.d.ts +9 -0
- package/dist/release/releaseDefaults.js +115 -0
- package/dist/release/releaseDocker.d.ts +38 -0
- package/dist/release/releaseDocker.js +94 -0
- package/dist/release/releaseTypes.d.ts +94 -0
- package/dist/release/releaseTypes.js +1 -0
- package/dist/release/runRelease.d.ts +9 -0
- package/dist/release/runRelease.js +218 -0
- package/dist/release/swap.d.ts +23 -0
- package/dist/release/swap.js +55 -0
- package/dist/release/types.d.ts +50 -0
- package/dist/release/types.js +29 -0
- package/dist/report/index.d.ts +1 -0
- package/dist/report/index.js +1 -0
- package/dist/report/recording.d.ts +57 -0
- package/dist/report/recording.js +26 -0
- package/dist/report/types.d.ts +58 -0
- package/dist/runtime/container.js +8 -13
- package/dist/runtime/daimon/adapter.js +15 -4
- package/dist/runtime/daimon/config.d.ts +4 -0
- package/dist/runtime/daimon/config.js +53 -4
- package/dist/runtime/daimon/contract-manifest.json +1 -0
- package/dist/runtime/daimon/contract-manifest.sha256 +1 -0
- package/dist/runtime/daimon/contractManifest.d.ts +59 -8
- package/dist/runtime/daimon/contractManifest.js +26 -10
- package/dist/runtime/daimon/grokTurnLimits.d.ts +25 -0
- package/dist/runtime/daimon/grokTurnLimits.js +51 -0
- package/dist/runtime/daimon/grokWorkerConfigBytes.d.ts +17 -0
- package/dist/runtime/daimon/grokWorkerConfigBytes.js +17 -0
- package/dist/runtime/daimon/grokWorkerContract.d.ts +5 -0
- package/dist/runtime/daimon/grokWorkerContract.js +13 -1
- package/dist/runtime/daimon/recording.d.ts +19 -0
- package/dist/runtime/daimon/recording.js +50 -0
- package/dist/runtime/daimon/scheduleAuthority.d.ts +8 -0
- package/dist/runtime/daimon/scheduleAuthority.js +19 -5
- package/dist/runtime/daimon/wakeMoments.d.ts +15 -0
- package/dist/runtime/daimon/wakeMoments.js +17 -0
- package/dist/runtime/daimonInstall.d.ts +3 -0
- package/dist/runtime/daimonInstall.js +25 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +2 -0
- package/dist/runtime/install.js +1 -0
- package/dist/runtime/openclaw/adapter.js +3 -0
- package/dist/runtime/pi/adapter.js +3 -0
- package/dist/runtime/pi/containerTargets.js +6 -0
- package/dist/runtime/picoclaw/adapter.js +3 -0
- package/dist/runtime/registry.d.ts +3 -0
- package/dist/runtime/registry.js +13 -1
- package/dist/runtime/types.d.ts +15 -0
- package/dist/runtime/usageLedgerRead.d.ts +6 -0
- package/dist/runtime/usageLedgerRead.js +3 -3
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/recording.d.ts +8 -0
- package/dist/shared/recording.js +25 -0
- package/dist/target/dockerCommandExecutorCore.js +1 -1
- package/dist/target/dockerTargetBinding.js +2 -1
- package/dist/volume/feedLand.d.ts +31 -0
- package/dist/volume/feedLand.js +174 -0
- package/dist/volume/feedLandings.d.ts +45 -0
- package/dist/volume/feedLandings.js +111 -0
- package/dist/volume/feedLayout.d.ts +61 -0
- package/dist/volume/feedLayout.js +234 -0
- package/dist/volume/feedLock.d.ts +7 -0
- package/dist/volume/feedLock.js +131 -0
- package/dist/volume/feedManifest.d.ts +27 -0
- package/dist/volume/feedManifest.js +123 -0
- package/dist/volume/feedPrepare.d.ts +16 -0
- package/dist/volume/feedPrepare.js +228 -0
- package/dist/volume/feedProject.d.ts +39 -0
- package/dist/volume/feedProject.js +92 -0
- package/dist/volume/feedRecord.d.ts +55 -0
- package/dist/volume/feedRecord.js +148 -0
- package/dist/volume/feedRef.d.ts +43 -0
- package/dist/volume/feedRef.js +117 -0
- package/dist/volume/feedRefresh.d.ts +15 -0
- package/dist/volume/feedRefresh.js +187 -0
- package/dist/volume/feedSource.d.ts +66 -0
- package/dist/volume/feedSource.js +104 -0
- package/dist/volume/feedTarget.d.ts +70 -0
- package/dist/volume/feedTarget.js +9 -0
- package/dist/volume/feedTestKit.d.ts +11 -0
- package/dist/volume/feedTestKit.js +45 -0
- package/dist/volume/feedVerify.d.ts +18 -0
- package/dist/volume/feedVerify.js +104 -0
- package/dist/volume/index.d.ts +13 -0
- package/dist/volume/index.js +13 -0
- package/moltnet-releases.json +4 -4
- package/package.json +3 -3
- package/runtimes.yaml +8 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { createReadStream } from "node:fs";
|
|
3
|
-
import { copyFile, lstat, mkdir, readdir, realpath, chmod } from "node:fs/promises";
|
|
3
|
+
import { copyFile, lstat, mkdir, readdir, readlink, realpath, chmod, symlink } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
export const within = (root, file) => file === root || file.startsWith(root + path.sep);
|
|
@@ -37,27 +37,50 @@ export async function sealFile(source, destination) {
|
|
|
37
37
|
throw Error("Training source changed while hashing");
|
|
38
38
|
return { source, destination, sha256: `sha256:${digest.digest("hex")}`, mode: Number(before.mode & 511n), size };
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
function assertLink(root, file, target) {
|
|
41
|
+
// Absolute host paths cannot reproduce a confined link in a relocated snapshot.
|
|
42
|
+
if (!target || path.isAbsolute(target) || !within(root, path.resolve(path.dirname(file), target))) {
|
|
43
|
+
throw Error("Training source trees must not contain escaping symlinks");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function sealLink(source, destination) {
|
|
47
|
+
const before = await lstat(source, { bigint: true }), target = await readlink(source);
|
|
48
|
+
const after = await lstat(source, { bigint: true });
|
|
49
|
+
if (!before.isSymbolicLink() || !after.isSymbolicLink() || after.ino !== before.ino ||
|
|
50
|
+
after.ctimeNs !== before.ctimeNs || after.dev !== before.dev)
|
|
51
|
+
throw Error("Training source changed while hashing");
|
|
52
|
+
return { source, destination, target, sha256: hashJson({ type: "symlink", target }), mode: 0o777, size: Buffer.byteLength(target) };
|
|
53
|
+
}
|
|
54
|
+
/** Only physical entries are walked; confined input links retain their own identity. */
|
|
41
55
|
export async function sealTree(source, destination, options = {}) {
|
|
42
56
|
await exactPath(source);
|
|
43
57
|
const files = [];
|
|
58
|
+
let entries = 0;
|
|
44
59
|
const walk = async (root, target, depth) => {
|
|
45
|
-
if (depth > 32
|
|
60
|
+
if (depth > 32)
|
|
61
|
+
throw Error("Training source tree exceeds bounds");
|
|
62
|
+
const stat = await lstat(root);
|
|
63
|
+
if ((depth > 0 || !stat.isDirectory()) && ++entries > 10000)
|
|
46
64
|
throw Error("Training source tree exceeds bounds");
|
|
47
|
-
let stat = await lstat(root);
|
|
48
65
|
if (stat.isSymbolicLink()) {
|
|
49
|
-
|
|
50
|
-
if (!options.internalSymlinks || !within(source, actual))
|
|
66
|
+
if (!options.internalSymlinks)
|
|
51
67
|
throw Error("Training source trees must not contain escaping symlinks");
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
const link = await sealLink(root, target);
|
|
69
|
+
assertLink(source, root, link.target);
|
|
70
|
+
// Resolve only for containment validation, never to enumerate or copy the target.
|
|
71
|
+
if (!within(source, await realpath(root)))
|
|
72
|
+
throw Error("Training source trees must not contain escaping symlinks");
|
|
73
|
+
files.push(link);
|
|
74
|
+
return;
|
|
54
75
|
}
|
|
55
76
|
if (stat.isDirectory()) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
const children = (await readdir(root)).sort().filter(entry => !(options.ignoreGit && entry === ".git") &&
|
|
78
|
+
!(options.ignoreDevelopment && (ignored.has(entry) || /(?:\.test\.[cm]?[jt]s|_test\.py|\.pyc)$/u.test(entry))));
|
|
79
|
+
// Retain empty input directories so their preserved links still resolve after relocation.
|
|
80
|
+
if (options.internalSymlinks && !children.length && target)
|
|
81
|
+
files.push({ source: root, destination: target,
|
|
82
|
+
directory: true, sha256: hashJson({ type: "directory" }), mode: stat.mode & 0o777, size: 0 });
|
|
83
|
+
for (const entry of children) {
|
|
61
84
|
await walk(path.join(root, entry), path.posix.join(target, entry), depth + 1);
|
|
62
85
|
}
|
|
63
86
|
}
|
|
@@ -70,16 +93,54 @@ export async function sealTree(source, destination, options = {}) {
|
|
|
70
93
|
return files;
|
|
71
94
|
}
|
|
72
95
|
export async function copySealed(files, root) {
|
|
96
|
+
const directories = new Set();
|
|
97
|
+
const ensureDirectory = async (directory) => {
|
|
98
|
+
if (directories.has(directory))
|
|
99
|
+
return;
|
|
100
|
+
const parent = path.dirname(directory);
|
|
101
|
+
if (parent !== directory) {
|
|
102
|
+
await ensureDirectory(parent);
|
|
103
|
+
await mkdir(directory, { mode: 0o700 }).catch((error) => { if (error.code !== "EEXIST")
|
|
104
|
+
throw error; });
|
|
105
|
+
}
|
|
106
|
+
await exactPath(directory);
|
|
107
|
+
if (!(await lstat(directory)).isDirectory())
|
|
108
|
+
throw Error("Training staging parent must be a directory");
|
|
109
|
+
directories.add(directory);
|
|
110
|
+
};
|
|
111
|
+
await ensureDirectory(root);
|
|
73
112
|
for (const file of files) {
|
|
74
113
|
const target = path.resolve(root, file.destination);
|
|
75
114
|
if (!within(root, target) || target === root)
|
|
76
115
|
throw Error("Training destination escapes owned staging");
|
|
77
|
-
await
|
|
116
|
+
await ensureDirectory(path.dirname(target));
|
|
117
|
+
if (file.directory) {
|
|
118
|
+
await ensureDirectory(target);
|
|
119
|
+
await chmod(target, file.mode);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (file.target !== undefined) {
|
|
123
|
+
assertLink(root, target, file.target);
|
|
124
|
+
const current = await sealLink(file.source, file.destination);
|
|
125
|
+
if (hashJson(fileIdentity([current])) !== hashJson(fileIdentity([file])))
|
|
126
|
+
throw Error("Training source changed during staging");
|
|
127
|
+
await symlink(file.target, target);
|
|
128
|
+
if (await readlink(target) !== file.target)
|
|
129
|
+
throw Error("Training source changed during staging");
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
await exactPath(file.source);
|
|
78
133
|
await copyFile(file.source, target, 1);
|
|
79
134
|
await chmod(target, file.mode);
|
|
80
135
|
const copied = await sealFile(target, file.destination);
|
|
81
136
|
if (copied.sha256 !== file.sha256 || copied.size !== file.size)
|
|
82
137
|
throw Error("Training source changed during staging");
|
|
83
138
|
}
|
|
139
|
+
// Validate chains after every target has been staged, including links ordered before files.
|
|
140
|
+
for (const file of files.filter(file => file.target !== undefined)) {
|
|
141
|
+
if (!within(root, await realpath(path.join(root, file.destination))))
|
|
142
|
+
throw Error("Training staged symlink escapes owned staging");
|
|
143
|
+
}
|
|
84
144
|
}
|
|
85
|
-
export const fileIdentity = (files) => files.map(({ destination, sha256, mode, size }) => ({ destination, sha256, mode, size })
|
|
145
|
+
export const fileIdentity = (files) => files.map(({ destination, sha256, mode, size, target, directory }) => ({ destination, sha256, mode, size, ...(target !== undefined ? { type: "symlink", target } : {}),
|
|
146
|
+
...(directory ? { type: "directory" } : {}) }));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { TRAINING_EXECUTION_CAPABILITY_LABEL } from "../contract.js";
|
|
4
5
|
import { assertInputRoot, copySealed, fileIdentity, hashJson, sealFile, sealTree } from "./files.js";
|
|
5
6
|
import { bindDaimonParentVerification, resolveTrainingDaimonParent } from "./daimonParent.js";
|
|
6
7
|
import { normalizeTrainingContext } from "./contextModes.js";
|
|
@@ -27,11 +28,15 @@ export async function planTrainingImage(build, root, auth, ownRoot = packageRoot
|
|
|
27
28
|
// Before any Docker call, including --dry-run: the attested Daimon runtime identity
|
|
28
29
|
// and the declared native parent must name one rebuild (see daimonParent.ts).
|
|
29
30
|
const daimonParent = await resolveTrainingDaimonParent(build, declarationPath);
|
|
31
|
+
assertInputRoot(ownRoot, auth);
|
|
30
32
|
for (const source of [build.paideia, build.bridge, build.claude, build.integration.source, ...build.compiler ? [build.compiler] : [], ...build.bootstrap ? [build.bootstrap] : []])
|
|
31
33
|
assertInputRoot(resolve(source), auth);
|
|
32
34
|
const assets = ownRoot === packageRoot ? trainingAssets : path.join(ownRoot, "runtime-images/training");
|
|
35
|
+
assertInputRoot(assets, auth);
|
|
33
36
|
const recipe = await readFile(path.join(assets, "Dockerfile"), "utf8");
|
|
34
|
-
|
|
37
|
+
// Always override the parent's label: compatibility belongs to this installed integration.
|
|
38
|
+
const capability = build.integration.execution ?? "";
|
|
39
|
+
const dockerfile = `${daimonParent ? bindDaimonParentVerification(recipe, daimonParent) : recipe}\nLABEL ${TRAINING_EXECUTION_CAPABILITY_LABEL}=${JSON.stringify(capability)}\n`;
|
|
35
40
|
const ownLock = path.extname(fileURLToPath(import.meta.url)) === ".ts" || ownRoot !== packageRoot
|
|
36
41
|
? path.join(ownRoot, "package-lock.json") : path.join(assets, "package-lock.json");
|
|
37
42
|
const files = [
|
|
@@ -64,12 +69,12 @@ export async function buildTrainingImage(plan, options) {
|
|
|
64
69
|
timeoutMs: options.timeoutMs, signal: options.signal, ...stream ? options.streams : {}
|
|
65
70
|
});
|
|
66
71
|
const inspect = async () => {
|
|
67
|
-
const result = await call(["image", "inspect", tag, "--format",
|
|
72
|
+
const result = await call(["image", "inspect", tag, "--format", `{{json .Id}}\n{{json (index .Config.Labels "com.spawnfile.training.recipe")}}\n{{json (index .Config.Labels "${TRAINING_EXECUTION_CAPABILITY_LABEL}")}}`]);
|
|
68
73
|
if (result.code !== 0)
|
|
69
74
|
return undefined;
|
|
70
75
|
try {
|
|
71
|
-
const [id, digest] = result.stdout.trim().split("\n").map(line => JSON.parse(line));
|
|
72
|
-
return /^sha256:[a-f0-9]{64}$/u.test(id) && digest === plan.digest ? id : undefined;
|
|
76
|
+
const [id, digest, capability] = result.stdout.trim().split("\n").map(line => JSON.parse(line));
|
|
77
|
+
return /^sha256:[a-f0-9]{64}$/u.test(id) && digest === plan.digest && capability === (plan.build.integration.execution ?? "") ? id : undefined;
|
|
73
78
|
}
|
|
74
79
|
catch {
|
|
75
80
|
return undefined;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
|
-
import { assertInputRoot, copySealed, exactPath, fileIdentity, hashJson, sealFile, sealTree } from "./files.js";
|
|
6
|
+
import { assertInputRoot, copySealed, exactPath, fileIdentity, hashJson, sealFile, sealTree, within } from "./files.js";
|
|
7
7
|
const execute = promisify(execFile);
|
|
8
|
-
const git = async (cwd, args) => {
|
|
8
|
+
const git = async (cwd, args, input) => {
|
|
9
9
|
const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_")));
|
|
10
|
-
|
|
10
|
+
const operation = execute("git", ["-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", ...args], {
|
|
11
11
|
cwd, env: { ...env, GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null", GIT_ATTR_NOSYSTEM: "1", GIT_TERMINAL_PROMPT: "0" },
|
|
12
12
|
timeout: 120000, maxBuffer: 8 * 1024 * 1024
|
|
13
|
-
})
|
|
13
|
+
});
|
|
14
|
+
operation.child.stdin?.end(input);
|
|
15
|
+
return (await operation).stdout;
|
|
14
16
|
};
|
|
15
17
|
export async function planInputs(config, root, auth) {
|
|
16
18
|
return Promise.all(config.inputs.map(async (input) => {
|
|
@@ -49,7 +51,7 @@ export async function planInputs(config, root, auth) {
|
|
|
49
51
|
throw Error("Git overlay digest mismatch or duplicate destination");
|
|
50
52
|
overlays.push(file);
|
|
51
53
|
}
|
|
52
|
-
return { id: input.id, source, destination: input.destination, git: { revision: input.git.revision, tree, common, overlays },
|
|
54
|
+
return { id: input.id, source, destination: input.destination, git: { revision: input.git.revision, tree, common, overlays, paths: entries.map(entry => entry.slice(entry.indexOf("\t") + 1)) },
|
|
53
55
|
digest: hashJson({ revision: input.git.revision, tree, overlays: fileIdentity(overlays) }) };
|
|
54
56
|
}));
|
|
55
57
|
}
|
|
@@ -65,7 +67,12 @@ export async function stageInput(input, target) {
|
|
|
65
67
|
await mkdir(target, { mode: 0o700 });
|
|
66
68
|
await git(target, ["init", "--quiet", "--template="]);
|
|
67
69
|
await git(target, ["-c", "protocol.file.allow=always", "fetch", "--quiet", "--depth=1", pathToFileURL(input.git.common).href, input.git.revision]);
|
|
68
|
-
|
|
70
|
+
// Populate the index/HEAD without writing paths that the overlay owns.
|
|
71
|
+
await git(target, ["update-ref", "--no-deref", "HEAD", input.git.revision]);
|
|
72
|
+
await git(target, ["read-tree", input.git.revision]);
|
|
73
|
+
const overlayPaths = new Set(input.git.overlays.map(file => file.destination));
|
|
74
|
+
const basePaths = input.git.paths.filter(file => !overlayPaths.has(file));
|
|
75
|
+
await git(target, ["checkout-index", "--stdin", "-z"], basePaths.map(file => `${file}\0`).join(""));
|
|
69
76
|
if ((await git(target, ["rev-parse", "HEAD^{tree}"])).trim() !== input.git.tree ||
|
|
70
77
|
(await git(target, ["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim() !== path.join(target, ".git"))
|
|
71
78
|
throw Error("Git training snapshot identity mismatch");
|
|
@@ -78,10 +85,11 @@ export async function verifyCanonicalPins(inputs, sources, staged) {
|
|
|
78
85
|
const index = inputs.findIndex(input => source.sourcePath === input.source || source.sourcePath.startsWith(input.source + path.sep));
|
|
79
86
|
if (index < 0)
|
|
80
87
|
throw Error("Canonical training source is outside declared inputs");
|
|
81
|
-
if (!inputs[index].git && !inputs[index].files)
|
|
82
|
-
continue;
|
|
83
88
|
const file = path.join(staged[index], path.relative(inputs[index].source, source.sourcePath));
|
|
84
|
-
|
|
89
|
+
const canonicalFile = await realpath(file);
|
|
90
|
+
if (!within(staged[index], canonicalFile))
|
|
91
|
+
throw Error("Canonical training source escapes its staged input");
|
|
92
|
+
if ((await sealFile(canonicalFile, "pin")).sha256 !== source.sha256)
|
|
85
93
|
throw Error("Pinned Git snapshot differs from the selected canonical agent");
|
|
86
94
|
}
|
|
87
95
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { type TrainingExecution } from "../execution.js";
|
|
1
2
|
import type { TrainingContext } from "../contract.js";
|
|
2
3
|
import type { TrainingDockerProcess } from "../container/process.js";
|
|
3
4
|
export interface PrepareTrainingOptions {
|
|
5
|
+
execution?: TrainingExecution;
|
|
6
|
+
outputOverride?: string;
|
|
7
|
+
imageOverride?: string;
|
|
4
8
|
configPath: string;
|
|
5
9
|
context: TrainingContext;
|
|
6
10
|
args: readonly string[];
|
|
@@ -18,6 +22,7 @@ export interface PrepareTrainingOptions {
|
|
|
18
22
|
repairWitness?: string;
|
|
19
23
|
}
|
|
20
24
|
export interface PreparedTraining {
|
|
25
|
+
execution?: TrainingExecution;
|
|
21
26
|
digest: string;
|
|
22
27
|
image: string;
|
|
23
28
|
configPath: string;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { lstat, mkdir, readFile, realpath,
|
|
1
|
+
import { lstat, mkdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { mapTrainingExecution, trainingExecutionIdentity } from "../execution.js";
|
|
3
4
|
import { trainingContainerConfigSchema } from "../container/contract.js";
|
|
5
|
+
import { assertTrainingSubjectIsolation } from "../container/subjectIsolation.js";
|
|
4
6
|
import { trainingPreparationSchema, parseTrainingMappedPreparation } from "./contract.js";
|
|
5
7
|
import { assertInputRoot, exactPath, fileIdentity, hashJson, sealTree, within } from "./files.js";
|
|
6
8
|
import { planInputs, readBoundedJson, stageInput, verifyCanonicalPins } from "./inputs.js";
|
|
7
9
|
import { planTrainingImage, buildTrainingImage } from "./image.js";
|
|
8
|
-
import { planMeasurementRepair, stageMeasurementRepair, writeTrainingWitness } from "../repair/index.js";
|
|
10
|
+
import { planMeasurementRepair, stageMeasurementRepair, validateMeasurementRepairOutput, writeTrainingWitness } from "../repair/index.js";
|
|
9
11
|
import { prepareTrainingBroker } from "./broker.js";
|
|
10
|
-
import { claimTrainingPreparationScratch } from "./scratch.js";
|
|
12
|
+
import { claimTrainingPreparationScratch, removeTrainingPreparationScratch } from "./scratch.js";
|
|
11
13
|
function mappedReceipt(digest, image, config) {
|
|
12
14
|
return parseTrainingMappedPreparation({ version: "spawnfile.training-preparation.v1", preparationDigest: digest, imageId: image,
|
|
13
15
|
bindings: config.inputs.map(input => ({ inputId: input.id, destination: input.destination })), outputRoot: "/run/training/output",
|
|
@@ -17,11 +19,20 @@ function mappedReceipt(digest, image, config) {
|
|
|
17
19
|
}
|
|
18
20
|
/** Reads only until the explicit dry-run boundary; preparation never executes project code. */
|
|
19
21
|
export async function prepareTraining(options) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const raw = await readBoundedJson(options.configPath);
|
|
23
|
+
const config = trainingPreparationSchema.parse({ ...raw,
|
|
24
|
+
...(options.outputOverride ? { output: { source: options.outputOverride, destination: "/run/training/output" } } : {}),
|
|
25
|
+
...(options.imageOverride ? { image: { ref: options.imageOverride } } : {}) });
|
|
26
|
+
await assertTrainingSubjectIsolation(config, options.context);
|
|
27
|
+
// A v2 declaration still lowers to the unchanged v1 launch config; v4 carries the broker slots into Docker.
|
|
28
|
+
const launchVersion = config.version === "spawnfile.training-container.v4" ? config.version : "spawnfile.training-container.v1";
|
|
23
29
|
const root = path.dirname(path.resolve(options.configPath));
|
|
24
30
|
const auth = config.auth.map(entry => ({ ...entry, source: path.resolve(root, entry.source) }));
|
|
31
|
+
const evaluatorSecrets = config.evaluatorSecrets?.map(entry => ({ ...entry, source: path.resolve(root, entry.source) }));
|
|
32
|
+
const protectedLeaves = [...auth, ...evaluatorSecrets ?? []];
|
|
33
|
+
const evaluatorConfig = { ...(config.evaluatorRoutes ? { evaluatorRoutes: config.evaluatorRoutes } : {}),
|
|
34
|
+
...(config.subjectIsolation ? { subjectIsolation: config.subjectIsolation } : {}),
|
|
35
|
+
...(evaluatorSecrets ? { evaluatorSecrets } : {}) };
|
|
25
36
|
const output = path.resolve(root, config.output.source), parent = path.dirname(output);
|
|
26
37
|
for (let index = 0; index < options.args.length; index++) {
|
|
27
38
|
if (options.args[index] !== "--out")
|
|
@@ -30,42 +41,58 @@ export async function prepareTraining(options) {
|
|
|
30
41
|
if (declared === undefined || path.resolve(declared) !== output)
|
|
31
42
|
throw Error("Training --out must match configured output");
|
|
32
43
|
}
|
|
33
|
-
|
|
44
|
+
// Refuse invalid repair destinations before auth validation or source/witness reads.
|
|
45
|
+
if (options.repairMeasurements)
|
|
46
|
+
await validateMeasurementRepairOutput({ parent: options.repairMeasurements,
|
|
47
|
+
output, resume: options.args.includes("--resume") });
|
|
48
|
+
// Validate references before traversing image/input sources; aliases must never bypass secret exclusions.
|
|
49
|
+
for (const entry of options.dryRun ? evaluatorSecrets ?? [] : protectedLeaves) {
|
|
50
|
+
const source = await exactPath(entry.source), info = await lstat(source);
|
|
51
|
+
if (source !== entry.source || !info.isFile())
|
|
52
|
+
throw Error("Training auth must be a canonical regular leaf");
|
|
53
|
+
if (evaluatorSecrets?.includes(entry) && info.nlink !== 1)
|
|
54
|
+
throw Error("Evaluator secrets must not have hardlink aliases");
|
|
55
|
+
}
|
|
56
|
+
assertInputRoot(output, protectedLeaves.map(entry => entry.source));
|
|
34
57
|
if (await realpath(parent) !== parent)
|
|
35
58
|
throw Error("Training output parent must be canonical and already exist");
|
|
36
|
-
const inputs = await planInputs(config, root,
|
|
59
|
+
const inputs = await planInputs(config, root, protectedLeaves.map(entry => entry.source));
|
|
60
|
+
const canonicalPins = [...options.context.sources, ...options.execution?.datasetPins ?? []];
|
|
37
61
|
const roots = inputs.map(input => input.source);
|
|
38
62
|
for (let index = 0; index < inputs.length; index++) {
|
|
39
63
|
const input = inputs[index];
|
|
40
|
-
|
|
64
|
+
// A selected/Git snapshot is staged before launch; never bind the source project over its own output.
|
|
65
|
+
const snapshotPaths = input.files?.map(file => path.join(input.source, file.destination))
|
|
66
|
+
?? (input.git ? [...input.git.paths, ...input.git.overlays.map(file => file.destination)].map(file => path.join(input.source, file)) : undefined);
|
|
67
|
+
if (within(output, input.source) || (within(input.source, output) &&
|
|
68
|
+
(!snapshotPaths || snapshotPaths.some(file => within(output, file) || within(file, output))))) {
|
|
41
69
|
throw Error("Training output overlaps readonly input");
|
|
70
|
+
}
|
|
42
71
|
for (const other of inputs.slice(index + 1))
|
|
43
72
|
if (within(input.source, other.source) || within(other.source, input.source) ||
|
|
44
73
|
within(input.destination, other.destination) || within(other.destination, input.destination))
|
|
45
74
|
throw Error("Training inputs overlap");
|
|
46
75
|
}
|
|
47
76
|
const imagePlan = "build" in config.image
|
|
48
|
-
? await planTrainingImage(config.image.build, root,
|
|
77
|
+
? await planTrainingImage(config.image.build, root, protectedLeaves.map(entry => entry.source), options.packageRoot, path.resolve(options.configPath))
|
|
49
78
|
: undefined;
|
|
50
79
|
if (options.repairWitness && !options.repairMeasurements)
|
|
51
80
|
throw Error("A repair witness requires --repair-measurements");
|
|
52
81
|
if (options.repairMeasurements && !imagePlan)
|
|
53
82
|
throw Error("Measurement repair requires a verifiable image build recipe");
|
|
54
83
|
const repair = options.repairMeasurements ? await planMeasurementRepair({ parent: options.repairMeasurements,
|
|
55
|
-
witness: options.repairWitness, output, auth:
|
|
84
|
+
witness: options.repairWitness, output, auth: protectedLeaves.map(entry => entry.source), image: imagePlan, inputs,
|
|
56
85
|
context: options.context, resume: options.args.includes("--resume") }) : undefined;
|
|
57
86
|
if (repair && hashJson(config.integration) !== hashJson(repair.witness.manifest.config.integration))
|
|
58
87
|
throw Error("Repair integration settings binding changed");
|
|
59
88
|
const digest = hashJson({ config, sources: inputs.map(input => ({ id: input.id, digest: input.digest })), image: imagePlan?.digest ?? config.image,
|
|
60
|
-
canonical: options.context.project.sourceDigest,
|
|
89
|
+
canonical: options.context.project.sourceDigest,
|
|
90
|
+
...(options.execution ? { execution: trainingExecutionIdentity(options.execution) } : {}), ...(repair ? { repair: { witness: repair.witness.digest, parent: repair.manifestDigest } } : {}) });
|
|
61
91
|
if (config.broker)
|
|
62
|
-
await prepareTrainingBroker(config.broker, root, path.dirname(path.resolve(options.configPath)), false);
|
|
92
|
+
await prepareTrainingBroker(config.broker, root, path.dirname(path.resolve(options.configPath)), false, config.slots);
|
|
63
93
|
if (options.dryRun)
|
|
64
94
|
return { digest, dryRun: true };
|
|
65
95
|
options.signal?.throwIfAborted();
|
|
66
|
-
for (const entry of auth)
|
|
67
|
-
if (await exactPath(entry.source) !== entry.source || !(await lstat(entry.source)).isFile())
|
|
68
|
-
throw Error("Training auth must be a canonical regular leaf");
|
|
69
96
|
const execute = (args) => options.process(args, { timeoutMs: options.timeoutMs, signal: options.signal });
|
|
70
97
|
const endpoint = await execute(["context", "inspect", config.dockerContext, "--format", "{{json .Endpoints.docker.Host}}"]);
|
|
71
98
|
if (endpoint.code !== 0 || !/^unix:\/\//u.test(JSON.parse(endpoint.stdout)))
|
|
@@ -82,6 +109,7 @@ export async function prepareTraining(options) {
|
|
|
82
109
|
const resume = options.args.includes("--resume");
|
|
83
110
|
let image, staged;
|
|
84
111
|
if (resume) {
|
|
112
|
+
await claimTrainingPreparationScratch(staging, digest, options.streams.stderr, "resume");
|
|
85
113
|
const previous = JSON.parse(await readFile(statePath, "utf8"));
|
|
86
114
|
if (previous.digest !== digest || !/^sha256:[a-f0-9]{64}$/u.test(previous.image) ||
|
|
87
115
|
JSON.stringify(previous.staged) !== JSON.stringify(inputs.map(input => input.git || input.files ? path.join(staging, input.id) : input.source)))
|
|
@@ -94,7 +122,7 @@ export async function prepareTraining(options) {
|
|
|
94
122
|
const mapped = parseTrainingMappedPreparation(await readBoundedJson(preparationPath));
|
|
95
123
|
if (mapped.preparationDigest !== digest || mapped.imageId !== image)
|
|
96
124
|
throw Error("Persisted training preparation identity mismatch");
|
|
97
|
-
await verifyCanonicalPins(inputs,
|
|
125
|
+
await verifyCanonicalPins(inputs, canonicalPins, staged);
|
|
98
126
|
}
|
|
99
127
|
else {
|
|
100
128
|
let owned = false;
|
|
@@ -108,8 +136,13 @@ export async function prepareTraining(options) {
|
|
|
108
136
|
if (error.code !== "EEXIST" || !(await lstat(output)).isDirectory() || await realpath(output) !== output)
|
|
109
137
|
throw error;
|
|
110
138
|
}
|
|
111
|
-
|
|
112
|
-
await
|
|
139
|
+
// Do not remove owned staging while sibling input jobs can still write into it.
|
|
140
|
+
const results = await Promise.allSettled(inputs.map(input => stageInput(input, path.join(staging, input.id))));
|
|
141
|
+
const failure = results.find(result => result.status === "rejected");
|
|
142
|
+
if (failure?.status === "rejected")
|
|
143
|
+
throw failure.reason;
|
|
144
|
+
staged = results.map(result => result.value);
|
|
145
|
+
await verifyCanonicalPins(inputs, canonicalPins, staged);
|
|
113
146
|
image = imagePlan ? (await buildTrainingImage(imagePlan, { parent, dockerContext: config.dockerContext, process: options.process,
|
|
114
147
|
timeoutMs: options.timeoutMs, signal: options.signal, streams: options.streams })).imageId : "ref" in config.image ? config.image.ref : "";
|
|
115
148
|
if (!image.startsWith("sha256:")) {
|
|
@@ -119,9 +152,9 @@ export async function prepareTraining(options) {
|
|
|
119
152
|
image = inspected.stdout.trim();
|
|
120
153
|
}
|
|
121
154
|
const mapped = mappedReceipt(digest, image, config);
|
|
122
|
-
const preparedBroker = config.broker ? await prepareTrainingBroker(config.broker, root, staging, true) : undefined;
|
|
123
|
-
const launch = trainingContainerConfigSchema.parse({ version: launchVersion, dockerContext: config.dockerContext,
|
|
124
|
-
inputs: inputs.map((input, index) => ({ source: staged[index], destination: input.destination })), output: { source: output, destination: "/run/training/output" }, auth,
|
|
155
|
+
const preparedBroker = config.broker ? await prepareTrainingBroker(config.broker, root, staging, true, config.slots) : undefined;
|
|
156
|
+
const launch = trainingContainerConfigSchema.parse({ version: launchVersion, slots: config.slots, dockerContext: config.dockerContext,
|
|
157
|
+
inputs: inputs.map((input, index) => ({ source: staged[index], destination: input.destination })), output: { source: output, destination: "/run/training/output" }, auth, ...evaluatorConfig,
|
|
125
158
|
...(preparedBroker ? { broker: preparedBroker.launch } : {}) });
|
|
126
159
|
await writeFile(configPath, JSON.stringify(launch), { flag: "wx", mode: 0o600 });
|
|
127
160
|
await writeFile(preparationPath, JSON.stringify(parseTrainingMappedPreparation(mapped)), { flag: "wx", mode: 0o400 });
|
|
@@ -129,18 +162,18 @@ export async function prepareTraining(options) {
|
|
|
129
162
|
await writeFile(statePath, JSON.stringify({ digest, image, staged, snapshots }), { flag: "wx", mode: 0o600 });
|
|
130
163
|
if (imagePlan)
|
|
131
164
|
await writeTrainingWitness({ directory: staging, digest, image, config, context: options.context,
|
|
132
|
-
args: options.args, inputs, staged, snapshots, plan: imagePlan,
|
|
165
|
+
args: options.args, execution: options.execution, inputs, staged, snapshots, plan: imagePlan,
|
|
133
166
|
...(repair ? { repair: { witness: repair.witness.digest, parent: repair.manifestDigest } } : {}) });
|
|
134
167
|
}
|
|
135
168
|
catch (error) {
|
|
136
169
|
if (owned)
|
|
137
|
-
await
|
|
170
|
+
await removeTrainingPreparationScratch(staging);
|
|
138
171
|
throw error;
|
|
139
172
|
}
|
|
140
173
|
}
|
|
141
|
-
const expectedBroker = config.broker ? await prepareTrainingBroker(config.broker, root, staging, false) : undefined;
|
|
142
|
-
const expectedLaunch = trainingContainerConfigSchema.parse({ version: launchVersion, dockerContext: config.dockerContext,
|
|
143
|
-
inputs: inputs.map((input, index) => ({ source: staged[index], destination: input.destination })), output: { source: output, destination: "/run/training/output" }, auth,
|
|
174
|
+
const expectedBroker = config.broker ? await prepareTrainingBroker(config.broker, root, staging, false, config.slots) : undefined;
|
|
175
|
+
const expectedLaunch = trainingContainerConfigSchema.parse({ version: launchVersion, slots: config.slots, dockerContext: config.dockerContext,
|
|
176
|
+
inputs: inputs.map((input, index) => ({ source: staged[index], destination: input.destination })), output: { source: output, destination: "/run/training/output" }, auth, ...evaluatorConfig,
|
|
144
177
|
...(expectedBroker ? { broker: expectedBroker.launch } : {}) });
|
|
145
178
|
if (hashJson(await readBoundedJson(configPath)) !== hashJson(expectedLaunch) ||
|
|
146
179
|
hashJson(await readBoundedJson(preparationPath)) !== hashJson(mappedReceipt(digest, image, config)))
|
|
@@ -150,6 +183,8 @@ export async function prepareTraining(options) {
|
|
|
150
183
|
throw Error("Saved training image is missing or changed");
|
|
151
184
|
const map = (file) => {
|
|
152
185
|
const absolute = path.resolve(file);
|
|
186
|
+
if (within(output, absolute))
|
|
187
|
+
return absolute;
|
|
153
188
|
const index = roots.findIndex(source => within(source, absolute));
|
|
154
189
|
return index < 0 ? absolute : path.join(staged[index], path.relative(roots[index], absolute));
|
|
155
190
|
};
|
|
@@ -161,7 +196,7 @@ export async function prepareTraining(options) {
|
|
|
161
196
|
skills: options.context.skills.map(source => ({ ...source, sourcePath: map(source.sourcePath) })) };
|
|
162
197
|
const args = [...options.args];
|
|
163
198
|
for (let index = 0; index < args.length; index++) {
|
|
164
|
-
if (["--train", "--test", "--cost-config"].includes(args[index]))
|
|
199
|
+
if (["--train", "--validation", "--test", "--cost-config"].includes(args[index]))
|
|
165
200
|
args[++index] = map(args[index]);
|
|
166
201
|
else if (args[index] === "--resource") {
|
|
167
202
|
const value = args[++index], split = value.indexOf("=");
|
|
@@ -180,7 +215,9 @@ export async function prepareTraining(options) {
|
|
|
180
215
|
else
|
|
181
216
|
await writeFile(repairConfig, JSON.stringify(repairLaunch), { flag: "wx", mode: 0o600 });
|
|
182
217
|
return { digest, image, configPath: repairConfig, preparationPath, repairPath: projected.repairPath, context,
|
|
218
|
+
...(options.execution ? { execution: mapTrainingExecution(options.execution, map) } : {}),
|
|
183
219
|
args: [...args, "--repair-measurements", "/run/training/inputs/repair-parent", "--repair-context", "/run/paideia/repair.json"] };
|
|
184
220
|
}
|
|
185
|
-
return { digest, image, configPath, preparationPath, context, args
|
|
221
|
+
return { digest, image, configPath, preparationPath, context, args,
|
|
222
|
+
...(options.execution ? { execution: mapTrainingExecution(options.execution, map) } : {}) };
|
|
186
223
|
}
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* aborted in its entrypoint — leaves this directory behind, and the next
|
|
6
|
-
* attempt used to die on `EEXIST: mkdir '.spawnfile-training-preparation-…'`
|
|
7
|
-
* before it could report anything useful. A live P8 run lost its second attempt
|
|
8
|
-
* to exactly that.
|
|
9
|
-
*
|
|
10
|
-
* Leftovers from the *same* preparation are reclaimed: `state.json` records the
|
|
11
|
-
* preparation digest, which pins the config, every input identity, the image
|
|
12
|
-
* plan and the canonical source digest, so a matching one can only have staged
|
|
13
|
-
* the same bytes and nothing is lost by staging them again (the image is
|
|
14
|
-
* content-addressed and resolves from its tag). Anything else — a different
|
|
15
|
-
* digest, or a run that aborted before it wrote its state — is left untouched
|
|
16
|
-
* and reported by name, because only the operator can know whether it is
|
|
17
|
-
* wanted or whether another launch is still using it.
|
|
2
|
+
* The receipt binds identity AND the owning process before staging starts.
|
|
3
|
+
* A matching digest alone never permits deletion. Unknown/remote/reused PIDs
|
|
4
|
+
* fail closed; only ESRCH on this host proves that a writer has stopped.
|
|
18
5
|
*/
|
|
19
|
-
export declare const claimTrainingPreparationScratch: (staging: string, digest: string, notify: (line: string) => void) => Promise<void>;
|
|
6
|
+
export declare const claimTrainingPreparationScratch: (staging: string, digest: string, notify: (line: string) => void, mode?: "fresh" | "resume") => Promise<void>;
|
|
7
|
+
/** Make sealed directories removable without following input links out of owned scratch. */
|
|
8
|
+
export declare const removeTrainingPreparationScratch: (staging: string) => Promise<void>;
|
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { chmod, lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* aborted in its entrypoint — leaves this directory behind, and the next
|
|
7
|
-
* attempt used to die on `EEXIST: mkdir '.spawnfile-training-preparation-…'`
|
|
8
|
-
* before it could report anything useful. A live P8 run lost its second attempt
|
|
9
|
-
* to exactly that.
|
|
10
|
-
*
|
|
11
|
-
* Leftovers from the *same* preparation are reclaimed: `state.json` records the
|
|
12
|
-
* preparation digest, which pins the config, every input identity, the image
|
|
13
|
-
* plan and the canonical source digest, so a matching one can only have staged
|
|
14
|
-
* the same bytes and nothing is lost by staging them again (the image is
|
|
15
|
-
* content-addressed and resolves from its tag). Anything else — a different
|
|
16
|
-
* digest, or a run that aborted before it wrote its state — is left untouched
|
|
17
|
-
* and reported by name, because only the operator can know whether it is
|
|
18
|
-
* wanted or whether another launch is still using it.
|
|
5
|
+
* The receipt binds identity AND the owning process before staging starts.
|
|
6
|
+
* A matching digest alone never permits deletion. Unknown/remote/reused PIDs
|
|
7
|
+
* fail closed; only ESRCH on this host proves that a writer has stopped.
|
|
19
8
|
*/
|
|
20
|
-
export const claimTrainingPreparationScratch = async (staging, digest, notify) => {
|
|
9
|
+
export const claimTrainingPreparationScratch = async (staging, digest, notify, mode = "fresh") => {
|
|
10
|
+
const recordOwner = (flag = "wx") => writeFile(path.join(staging, "owner.json"), JSON.stringify({ digest, host: hostname(), pid: process.pid }), { flag, mode: 0o600 });
|
|
11
|
+
// This guard lives outside scratch so removal/recreation cannot drop the
|
|
12
|
+
// exclusion barrier. Never break a stranded guard automatically: its absence
|
|
13
|
+
// of a receipt cannot establish that its holder stopped.
|
|
14
|
+
const guard = `${staging}.reclaim`;
|
|
21
15
|
try {
|
|
22
|
-
await mkdir(
|
|
23
|
-
return;
|
|
16
|
+
await mkdir(guard, { mode: 0o700 });
|
|
24
17
|
}
|
|
25
18
|
catch (error) {
|
|
26
19
|
if (error.code !== "EEXIST")
|
|
27
20
|
throw error;
|
|
21
|
+
throw Error(`Training preparation scratch recovery is already claimed: ${guard}; if no preparation is running, remove that guard and retry`);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
if (mode === "fresh") {
|
|
25
|
+
let created = false;
|
|
26
|
+
try {
|
|
27
|
+
await mkdir(staging, { mode: 0o700 });
|
|
28
|
+
created = true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code !== "EEXIST")
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
if (created) {
|
|
35
|
+
await recordOwner();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Re-read under exclusion, never delete based on an earlier observation.
|
|
40
|
+
await assertAbandonedScratch(staging, digest, mode === "resume");
|
|
41
|
+
if (mode === "resume") {
|
|
42
|
+
// Claim before validating or reading snapshots: fresh recovery must see
|
|
43
|
+
// this live process rather than the stopped original preparer.
|
|
44
|
+
await recordOwner("w");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
notify(`Reusing the preparation scratch left by an earlier launch of this exact preparation: ${staging}`);
|
|
48
|
+
await removeTrainingPreparationScratch(staging);
|
|
49
|
+
await mkdir(staging, { mode: 0o700 });
|
|
50
|
+
await recordOwner();
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await rm(guard, { recursive: true });
|
|
28
54
|
}
|
|
55
|
+
};
|
|
56
|
+
const assertAbandonedScratch = async (staging, digest, resume) => {
|
|
29
57
|
const info = await lstat(staging);
|
|
30
58
|
if (!info.isDirectory() || info.isSymbolicLink() || await realpath(staging) !== staging) {
|
|
31
59
|
throw Error(`Training preparation scratch ${staging} is not a canonical directory; remove it and retry`);
|
|
@@ -35,14 +63,52 @@ export const claimTrainingPreparationScratch = async (staging, digest, notify) =
|
|
|
35
63
|
previous = JSON.parse(await readFile(`${staging}/state.json`, "utf8"));
|
|
36
64
|
}
|
|
37
65
|
catch {
|
|
38
|
-
|
|
66
|
+
try {
|
|
67
|
+
previous = JSON.parse(await readFile(path.join(staging, "owner.json"), "utf8"));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
previous = undefined;
|
|
71
|
+
}
|
|
39
72
|
}
|
|
40
73
|
if (previous?.digest !== digest) {
|
|
74
|
+
if (resume)
|
|
75
|
+
throw Error("Training preparation changed; exact resume rejected");
|
|
41
76
|
throw Error(previous === undefined
|
|
42
77
|
? `Training preparation scratch ${staging} is left over from an interrupted launch that never recorded its identity; remove it to retry: rm -rf ${JSON.stringify(staging)}`
|
|
43
78
|
: `Training preparation scratch ${staging} belongs to a different preparation (${String(previous.digest)}); remove it to retry: rm -rf ${JSON.stringify(staging)}`);
|
|
44
79
|
}
|
|
45
|
-
|
|
80
|
+
let owner;
|
|
81
|
+
try {
|
|
82
|
+
owner = JSON.parse(await readFile(path.join(staging, "owner.json"), "utf8"));
|
|
83
|
+
}
|
|
84
|
+
catch { /* Unknown ownership fails closed. */ }
|
|
85
|
+
if (owner?.digest !== digest || owner.host !== hostname() || typeof owner.pid !== "number" ||
|
|
86
|
+
!Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
|
|
87
|
+
throw Error(`Training preparation scratch ${staging} has no verifiable local owner; cannot prove its previous launch stopped`);
|
|
88
|
+
}
|
|
89
|
+
// Revalidation by the owning process is idempotent; other live owners refuse.
|
|
90
|
+
if (resume && owner.pid === process.pid)
|
|
91
|
+
return;
|
|
92
|
+
let stopped = false;
|
|
93
|
+
try {
|
|
94
|
+
process.kill(owner.pid, 0);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
stopped = error.code === "ESRCH";
|
|
98
|
+
}
|
|
99
|
+
if (!stopped)
|
|
100
|
+
throw Error(`Training preparation scratch ${staging} still has an active or unverifiable owner (${owner.pid}); retry after it stops`);
|
|
101
|
+
};
|
|
102
|
+
/** Make sealed directories removable without following input links out of owned scratch. */
|
|
103
|
+
export const removeTrainingPreparationScratch = async (staging) => {
|
|
104
|
+
const writable = async (entry) => {
|
|
105
|
+
const info = await lstat(entry);
|
|
106
|
+
if (!info.isDirectory() || info.isSymbolicLink())
|
|
107
|
+
return;
|
|
108
|
+
await chmod(entry, (info.mode & 0o777) | 0o700);
|
|
109
|
+
for (const child of await readdir(entry))
|
|
110
|
+
await writable(path.join(entry, child));
|
|
111
|
+
};
|
|
112
|
+
await writable(staging);
|
|
46
113
|
await rm(staging, { recursive: true, force: true });
|
|
47
|
-
await mkdir(staging, { mode: 0o700 });
|
|
48
114
|
};
|