spawnfile 0.1.17 → 0.1.18
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/package.json +3 -3
- package/runtimes.yaml +8 -6
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// `include` and `prepare`: what a feed adds to the source in staging, before validation and freeze.
|
|
2
|
+
//
|
|
3
|
+
// THE REVISION DESCRIBES WHAT IS SERVED. With either declared, the revision is the digest of the source
|
|
4
|
+
// revision, every included directory's digest and the prepare recipe (command, image digest, platform,
|
|
5
|
+
// network), so it is decided WITHOUT running anything: an unchanged refresh compares digests and lands
|
|
6
|
+
// nothing. The prepare output itself is not hashed into the revision (an install is not byte-reproducible
|
|
7
|
+
// across registries and time); it is hashed into the land-time manifest like every other tree, so verify
|
|
8
|
+
// still catches any change after landing.
|
|
9
|
+
//
|
|
10
|
+
// WHY A PINNED IMAGE. Native modules are built for the platform that installs them; installing on the
|
|
11
|
+
// host would serve host binaries to containers on another libc or arch. A digest-pinned image on the
|
|
12
|
+
// target platform is the same rule the `dependencies` bundle follows. `host: true` is the explicit
|
|
13
|
+
// opt-out for steps that are platform-neutral (copying assets).
|
|
14
|
+
//
|
|
15
|
+
// WHY A CACHE. A heal re-lands the same revision from the source; without a cache that is a full
|
|
16
|
+
// reinstall at the moment the volume is under attack. The cache is keyed by the revision (which already
|
|
17
|
+
// covers tree + recipe + image + platform), lives in host state, and is re-verified by digest on every use.
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
20
|
+
import { cpSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { containerArgv } from "../compiler/workspaceBundleRun.js";
|
|
23
|
+
import { feedError, removeTree } from "./feedLayout.js";
|
|
24
|
+
import { sha256Hex } from "./feedManifest.js";
|
|
25
|
+
import { digestDirectory, resolveFeedSource, stageFeedSource } from "./feedSource.js";
|
|
26
|
+
import { feedPreparedDir } from "./feedTarget.js";
|
|
27
|
+
const PREPARE_CACHE_KEEP = 2;
|
|
28
|
+
const CONTAINER_TREE = "/spawnfile/feed";
|
|
29
|
+
const preparedOf = (target) => target.include !== undefined || target.prepare !== undefined;
|
|
30
|
+
const prepareRecipe = (target) => {
|
|
31
|
+
const prepare = target.prepare;
|
|
32
|
+
if (!prepare)
|
|
33
|
+
return null;
|
|
34
|
+
return prepare.kind === "image"
|
|
35
|
+
? { command: prepare.command, image: prepare.image, network: prepare.network, platform: prepare.platform }
|
|
36
|
+
: { command: prepare.command, image: null, network: null, platform: null };
|
|
37
|
+
};
|
|
38
|
+
const compose = (target, source, include) => {
|
|
39
|
+
const prepared = { include, prepare: prepareRecipe(target), source_revision: source.revision };
|
|
40
|
+
return { provenance: { ...source.provenance, prepared }, revision: sha256Hex(`prepared\n${JSON.stringify(prepared)}\n`) };
|
|
41
|
+
};
|
|
42
|
+
const includeDigest = (from) => {
|
|
43
|
+
let stat;
|
|
44
|
+
try {
|
|
45
|
+
stat = lstatSync(from);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw feedError(`feed include directory ${from} does not exist`);
|
|
49
|
+
}
|
|
50
|
+
if (!stat.isDirectory())
|
|
51
|
+
throw feedError(`feed include ${from} is not a directory`);
|
|
52
|
+
return digestDirectory(from);
|
|
53
|
+
};
|
|
54
|
+
/** The source revision, composed with `include` and `prepare` when the feed declares them. */
|
|
55
|
+
export const resolveFeedContent = (target, { exec }) => {
|
|
56
|
+
const source = resolveFeedSource(target.source, { exec });
|
|
57
|
+
if (!preparedOf(target))
|
|
58
|
+
return source;
|
|
59
|
+
return compose(target, source, (target.include ?? []).map(({ from, to }) => ({ digest: includeDigest(from), to })));
|
|
60
|
+
};
|
|
61
|
+
/** The source half of a composed revision, which is what staging the source must reproduce. */
|
|
62
|
+
const sourcePart = (resolved) => {
|
|
63
|
+
const { prepared, ...provenance } = resolved.provenance;
|
|
64
|
+
return { provenance, revision: prepared?.source_revision ?? resolved.revision };
|
|
65
|
+
};
|
|
66
|
+
/** Every component of `to` below the staged root must be absent or a real directory: never follow a staged symlink out. */
|
|
67
|
+
const stageInclude = (stagingDir, from, to) => {
|
|
68
|
+
let parent = stagingDir;
|
|
69
|
+
const segments = to.split("/");
|
|
70
|
+
for (const [index, segment] of segments.entries()) {
|
|
71
|
+
const next = path.join(parent, segment);
|
|
72
|
+
let stat;
|
|
73
|
+
try {
|
|
74
|
+
stat = lstatSync(next);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
stat = null;
|
|
78
|
+
}
|
|
79
|
+
if (index === segments.length - 1) {
|
|
80
|
+
if (stat)
|
|
81
|
+
throw feedError(`feed include target ${to} already exists in the fed tree; an include adds files, it never replaces them`);
|
|
82
|
+
}
|
|
83
|
+
else if (stat && !stat.isDirectory()) {
|
|
84
|
+
throw feedError(`feed include target ${to} passes through ${segments.slice(0, index + 1).join("/")}, which is not a directory in the fed tree`);
|
|
85
|
+
}
|
|
86
|
+
else if (!stat)
|
|
87
|
+
mkdirSync(next);
|
|
88
|
+
parent = next;
|
|
89
|
+
}
|
|
90
|
+
const destination = path.join(stagingDir, to);
|
|
91
|
+
cpSync(from, destination, { errorOnExist: true, force: false, preserveTimestamps: true, recursive: true, verbatimSymlinks: true });
|
|
92
|
+
return digestDirectory(destination);
|
|
93
|
+
};
|
|
94
|
+
const TIMEOUT_MARK = "SPAWNFILE_PREPARE_TIMEOUT", SPAWN_MARK = "SPAWNFILE_PREPARE_SPAWN_ERROR";
|
|
95
|
+
/**
|
|
96
|
+
* spawnSync cannot kill a process group, and a child that ignores SIGTERM (or a descendant holding the
|
|
97
|
+
* pipes) would outlive its timeout while the refresh holds the lock. So the step runs under a tiny
|
|
98
|
+
* supervisor that starts it in its own process group and SIGKILLs the whole group on timeout and on exit.
|
|
99
|
+
*/
|
|
100
|
+
const SUPERVISOR = [
|
|
101
|
+
'const { spawn } = require("node:child_process");',
|
|
102
|
+
"const [ms, file, ...args] = process.argv.slice(1);",
|
|
103
|
+
'const child = spawn(file, args, { detached: true, stdio: "inherit" });',
|
|
104
|
+
'const killGroup = () => { try { process.kill(-child.pid, "SIGKILL"); } catch {} };',
|
|
105
|
+
"let timedOut = false;",
|
|
106
|
+
"const timer = setTimeout(() => { timedOut = true; killGroup(); }, Number(ms));",
|
|
107
|
+
`child.on("error", (error) => { clearTimeout(timer); console.error("${SPAWN_MARK} " + error.message); process.exit(127); });`,
|
|
108
|
+
`child.on("exit", (code, signal) => { clearTimeout(timer); killGroup(); if (timedOut) { console.error("${TIMEOUT_MARK}"); process.exit(124); } if (signal) console.error("killed by " + signal); process.exit(code === null ? 128 : code); });`
|
|
109
|
+
].join("\n");
|
|
110
|
+
const superviseSync = (argv, timeoutMs, options) => spawnSync(process.execPath, ["-e", SUPERVISOR, String(timeoutMs), ...argv], {
|
|
111
|
+
...options, encoding: "utf8", killSignal: "SIGKILL", maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"],
|
|
112
|
+
// Backstop only: the supervisor enforces the real deadline on the whole group.
|
|
113
|
+
timeout: timeoutMs + 10_000
|
|
114
|
+
});
|
|
115
|
+
const failure = (result, timeoutMs) => {
|
|
116
|
+
const stderr = String(result.stderr ?? "");
|
|
117
|
+
if (stderr.includes(TIMEOUT_MARK) || result.error?.code === "ETIMEDOUT")
|
|
118
|
+
return `did not finish within ${Math.round(timeoutMs / 1000)}s and was killed`;
|
|
119
|
+
if (result.error)
|
|
120
|
+
return `could not run: ${result.error.message}`;
|
|
121
|
+
if (stderr.includes(SPAWN_MARK))
|
|
122
|
+
return `could not run: ${stderr.slice(stderr.indexOf(SPAWN_MARK) + SPAWN_MARK.length).trim()}`;
|
|
123
|
+
if (result.status === 0)
|
|
124
|
+
return null;
|
|
125
|
+
const output = `${stderr}${String(result.stdout ?? "")}`.trim().split("\n").slice(-20).join("\n");
|
|
126
|
+
return `exited ${result.status}${output ? `:\n${output}` : ""}`;
|
|
127
|
+
};
|
|
128
|
+
/** Runs the declared prepare step in the staged tree; any failure lands nothing. */
|
|
129
|
+
export const runFeedPrepare = (target, tree, resolved) => {
|
|
130
|
+
const prepare = target.prepare;
|
|
131
|
+
if (!prepare)
|
|
132
|
+
return;
|
|
133
|
+
const env = { SPAWNFILE_FEED_RESOURCE: target.resourceId, SPAWNFILE_FEED_REVISION: resolved.revision };
|
|
134
|
+
let result;
|
|
135
|
+
if (prepare.kind === "host") {
|
|
136
|
+
result = superviseSync(prepare.command, prepare.timeoutMs, { cwd: tree, env: { ...process.env, ...env, SPAWNFILE_FEED_TREE: tree } });
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const name = `spawnfile-feed-${randomBytes(8).toString("hex")}`;
|
|
140
|
+
result = superviseSync(containerArgv({
|
|
141
|
+
argv: prepare.command, dockerCommand: prepare.dockerCommand, env: { ...env, SPAWNFILE_FEED_TREE: CONTAINER_TREE }, image: prepare.image,
|
|
142
|
+
mounts: [[tree, CONTAINER_TREE]], name, network: prepare.network, platform: prepare.platform, workdir: CONTAINER_TREE
|
|
143
|
+
}), prepare.timeoutMs, {});
|
|
144
|
+
// Killing the client does not stop its container: remove it, bounded, before reporting.
|
|
145
|
+
if (result.status !== 0)
|
|
146
|
+
superviseSync([prepare.dockerCommand, "rm", "--force", name], 30_000, {});
|
|
147
|
+
}
|
|
148
|
+
const why = failure(result, prepare.timeoutMs);
|
|
149
|
+
if (why)
|
|
150
|
+
throw feedError(`the feed prepare command for ${target.resourceId} ${why}; nothing was landed`);
|
|
151
|
+
if (readdirSync(tree).length === 0)
|
|
152
|
+
throw feedError(`the feed prepare command for ${target.resourceId} left an empty tree; a fed volume never serves nothing`);
|
|
153
|
+
};
|
|
154
|
+
const cachePaths = (target, revision) => ({ marker: path.join(feedPreparedDir(target), `${revision}.json`), tree: path.join(feedPreparedDir(target), revision) });
|
|
155
|
+
/** Copies a cached prepared tree into staging; true only when the copy's digest is the one recorded at store time. */
|
|
156
|
+
const restoreCached = (target, revision, stagingDir, log) => {
|
|
157
|
+
const { marker, tree } = cachePaths(target, revision);
|
|
158
|
+
let digest;
|
|
159
|
+
try {
|
|
160
|
+
digest = JSON.parse(readFileSync(marker, "utf8")).digest;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
if (!lstatSync(tree).isDirectory())
|
|
167
|
+
return false;
|
|
168
|
+
cpSync(tree, stagingDir, { errorOnExist: true, force: false, preserveTimestamps: true, recursive: true, verbatimSymlinks: true });
|
|
169
|
+
if (digestDirectory(stagingDir) === digest) {
|
|
170
|
+
log(`reused the prepared tree for ${revision.slice(0, 12)} from the cache`);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch { /* fall through to a fresh build */ }
|
|
175
|
+
log(`the cached prepared tree for ${revision.slice(0, 12)} no longer matches its digest; preparing it again`);
|
|
176
|
+
removeTree(tree, { volume: target.volume });
|
|
177
|
+
rmSync(marker, { force: true });
|
|
178
|
+
removeTree(stagingDir, { volume: target.volume });
|
|
179
|
+
mkdirSync(stagingDir);
|
|
180
|
+
return false;
|
|
181
|
+
};
|
|
182
|
+
/** Best-effort: a cache that cannot be written costs a reinstall later, never this landing. */
|
|
183
|
+
const storeCached = (target, revision, stagingDir, log) => {
|
|
184
|
+
const directory = feedPreparedDir(target), { marker, tree } = cachePaths(target, revision);
|
|
185
|
+
const tmp = `${tree}.${process.pid}.tmp`;
|
|
186
|
+
try {
|
|
187
|
+
mkdirSync(directory, { mode: 0o700, recursive: true });
|
|
188
|
+
removeTree(tmp, { volume: target.volume });
|
|
189
|
+
cpSync(stagingDir, tmp, { errorOnExist: true, force: false, preserveTimestamps: true, recursive: true, verbatimSymlinks: true });
|
|
190
|
+
const digest = digestDirectory(tmp);
|
|
191
|
+
removeTree(tree, { volume: target.volume });
|
|
192
|
+
renameSync(tmp, tree);
|
|
193
|
+
writeFileSync(`${marker}.tmp`, `${JSON.stringify({ digest, revision })}\n`, { mode: 0o600 });
|
|
194
|
+
renameSync(`${marker}.tmp`, marker);
|
|
195
|
+
const entries = readdirSync(directory).filter((name) => name.endsWith(".json"))
|
|
196
|
+
.map((name) => ({ name: name.slice(0, -".json".length), time: statSync(path.join(directory, name)).mtimeMs }))
|
|
197
|
+
.sort((left, right) => right.time - left.time);
|
|
198
|
+
for (const stale of entries.slice(PREPARE_CACHE_KEEP)) {
|
|
199
|
+
rmSync(path.join(directory, `${stale.name}.json`), { force: true });
|
|
200
|
+
removeTree(path.join(directory, stale.name), { volume: target.volume });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
try {
|
|
205
|
+
removeTree(tmp, { volume: target.volume });
|
|
206
|
+
}
|
|
207
|
+
catch { /* reported below */ }
|
|
208
|
+
log(`could not cache the prepared tree for ${revision.slice(0, 12)}: ${error.message}`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Stages the source, then the includes, then runs prepare, in `stagingDir` (existing, empty). Returns the
|
|
213
|
+
* revision of what was actually staged: a source or include edited mid-copy lands under its own revision.
|
|
214
|
+
*/
|
|
215
|
+
export const stageFeedContent = (target, resolved, stagingDir, { exec, log }) => {
|
|
216
|
+
if (!preparedOf(target))
|
|
217
|
+
return stageFeedSource(target.source, resolved, stagingDir, { exec });
|
|
218
|
+
if (target.prepare && restoreCached(target, resolved.revision, stagingDir, log))
|
|
219
|
+
return resolved;
|
|
220
|
+
const source = stageFeedSource(target.source, sourcePart(resolved), stagingDir, { exec });
|
|
221
|
+
const staged = compose(target, source, (target.include ?? []).map(({ from, to }) => ({ digest: stageInclude(stagingDir, from, to), to })));
|
|
222
|
+
if (target.prepare) {
|
|
223
|
+
log(`preparing ${staged.revision.slice(0, 12)}: ${target.prepare.command.join(" ")}${target.prepare.kind === "image" ? ` in ${target.prepare.image} (${target.prepare.platform})` : " on the host"}`);
|
|
224
|
+
runFeedPrepare(target, stagingDir, staged);
|
|
225
|
+
storeCached(target, staged.revision, stagingDir, log);
|
|
226
|
+
}
|
|
227
|
+
return staged;
|
|
228
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { TeamWorkspaceResource } from "../manifest/index.js";
|
|
2
|
+
import { type FeedExec } from "./feedSource.js";
|
|
3
|
+
import { type FeedTarget } from "./feedTarget.js";
|
|
4
|
+
type VolumeResource = Extract<TeamWorkspaceResource, {
|
|
5
|
+
kind: "volume";
|
|
6
|
+
}>;
|
|
7
|
+
type DeclaredFeed = NonNullable<VolumeResource["feed"]>;
|
|
8
|
+
export interface FeedTargetOptions {
|
|
9
|
+
/** Docker client a containerized `prepare` runs through; default `docker`. */
|
|
10
|
+
dockerCommand?: string;
|
|
11
|
+
exec?: FeedExec;
|
|
12
|
+
fetch?: boolean;
|
|
13
|
+
healLimit?: number;
|
|
14
|
+
stateDir?: string;
|
|
15
|
+
validateTimeoutMs?: number;
|
|
16
|
+
volumePath?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DeclaredVolumeFeed {
|
|
19
|
+
/** Directory of the manifest that declared the resource; relative feed paths resolve here. */
|
|
20
|
+
base: string;
|
|
21
|
+
feed: DeclaredFeed;
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
record?: {
|
|
25
|
+
keepMs: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Every fed volume visible in the compile plan, deduplicated by id (team resources reach many agents). */
|
|
29
|
+
export declare const findDeclaredVolumeFeeds: (inputPath: string) => Promise<DeclaredVolumeFeed[]>;
|
|
30
|
+
/** `docker volume inspect` names the host directory of a named volume; the override serves other hosts and tests. */
|
|
31
|
+
export declare const resolveVolumeHostPath: (name: string, { exec, volumePath }: {
|
|
32
|
+
exec?: FeedExec;
|
|
33
|
+
volumePath?: string;
|
|
34
|
+
}) => string;
|
|
35
|
+
/** Shared host-history location, including callers which only have deployed volume facts. */
|
|
36
|
+
export declare const resolveFeedStateDirectory: (name: string, options?: FeedTargetOptions) => string;
|
|
37
|
+
export declare const toFeedTarget: (declared: DeclaredVolumeFeed, options?: FeedTargetOptions) => FeedTarget;
|
|
38
|
+
export declare const resolveFeedTarget: (inputPath: string, resourceId: string, options?: FeedTargetOptions) => Promise<FeedTarget>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Resolves `<resource id>` in a Spawnfile project to a FeedTarget: the declared feed (paths resolved
|
|
2
|
+
// against the declaring manifest), the host path of the named volume, and the host state directory.
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { buildCompilePlan } from "../compiler/index.js";
|
|
5
|
+
import { resolveBundleArchitecture } from "../compiler/workspaceBundleResolve.js";
|
|
6
|
+
import { recordingKeepMs, SpawnfileError } from "../shared/index.js";
|
|
7
|
+
import { feedError } from "./feedLayout.js";
|
|
8
|
+
import { hostExec } from "./feedSource.js";
|
|
9
|
+
import { DEFAULT_FEED_HEAL_LIMIT, DEFAULT_FEED_KEEP, DEFAULT_FEED_PREPARE_TIMEOUT_MS, DEFAULT_FEED_REF_COMMAND_TIMEOUT_MS, DEFAULT_FEED_VALIDATE_TIMEOUT_MS } from "./feedTarget.js";
|
|
10
|
+
/** Every fed volume visible in the compile plan, deduplicated by id (team resources reach many agents). */
|
|
11
|
+
export const findDeclaredVolumeFeeds = async (inputPath) => {
|
|
12
|
+
const plan = await buildCompilePlan(inputPath);
|
|
13
|
+
const byId = new Map();
|
|
14
|
+
const recordingById = new Map();
|
|
15
|
+
for (const node of plan.nodes) {
|
|
16
|
+
const keepMs = node.value.kind === "agent" ? recordingKeepMs(node.value.training?.record) : undefined;
|
|
17
|
+
for (const resource of node.value.workspaceResources ?? []) {
|
|
18
|
+
if (keepMs !== undefined && resource.kind === "volume" && resource.feed) {
|
|
19
|
+
recordingById.set(resource.id, Math.max(recordingById.get(resource.id) ?? 0, keepMs));
|
|
20
|
+
}
|
|
21
|
+
if (resource.kind !== "volume" || !resource.feed || byId.has(resource.id))
|
|
22
|
+
continue;
|
|
23
|
+
byId.set(resource.id, { base: path.dirname(resource.scope.key), feed: resource.feed, id: resource.id, name: resource.name });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const [id, keepMs] of recordingById)
|
|
27
|
+
byId.get(id).record = { keepMs };
|
|
28
|
+
return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
29
|
+
};
|
|
30
|
+
/** `docker volume inspect` names the host directory of a named volume; the override serves other hosts and tests. */
|
|
31
|
+
export const resolveVolumeHostPath = (name, { exec = hostExec, volumePath }) => {
|
|
32
|
+
if (volumePath)
|
|
33
|
+
return path.resolve(volumePath);
|
|
34
|
+
try {
|
|
35
|
+
const mountpoint = exec("docker", ["volume", "inspect", "--format", "{{.Mountpoint}}", name]).toString().trim();
|
|
36
|
+
if (!path.isAbsolute(mountpoint))
|
|
37
|
+
throw new Error(`unexpected mountpoint ${JSON.stringify(mountpoint)}`);
|
|
38
|
+
return mountpoint;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw feedError(`cannot find the host path of volume ${name}: ${String(error.message).trim().slice(0, 200)}. Deploy the organization once, or pass --volume-path.`);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** Shared host-history location, including callers which only have deployed volume facts. */
|
|
45
|
+
export const resolveFeedStateDirectory = (name, options = {}) => path.resolve(options.stateDir ?? path.join(path.dirname(resolveVolumeHostPath(name, options)), "spawnfile-feed"));
|
|
46
|
+
const toPrepare = (prepare, options) => {
|
|
47
|
+
const timeoutMs = (prepare.timeout_seconds ?? DEFAULT_FEED_PREPARE_TIMEOUT_MS / 1000) * 1000;
|
|
48
|
+
if (prepare.host)
|
|
49
|
+
return { command: prepare.command, kind: "host", timeoutMs };
|
|
50
|
+
return {
|
|
51
|
+
command: prepare.command, dockerCommand: options.dockerCommand ?? "docker", image: prepare.image, kind: "image",
|
|
52
|
+
// The refresh runs on the host that serves the volume, so the target platform defaults to its own.
|
|
53
|
+
network: prepare.network ?? true, platform: prepare.platform ?? `linux/${resolveBundleArchitecture()}`, timeoutMs
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const toRefRule = (ref, base) => ({
|
|
57
|
+
...(ref.command ? { command: { argv: ref.command, cwd: base, timeoutMs: DEFAULT_FEED_REF_COMMAND_TIMEOUT_MS } } : {}),
|
|
58
|
+
...(ref.fallback ? { fallback: ref.fallback } : {}),
|
|
59
|
+
...(ref.template ? { template: ref.template } : {})
|
|
60
|
+
});
|
|
61
|
+
export const toFeedTarget = (declared, options = {}) => {
|
|
62
|
+
const volume = resolveVolumeHostPath(declared.name, options);
|
|
63
|
+
const feed = declared.feed, ref = feed.git?.ref;
|
|
64
|
+
const source = feed.git
|
|
65
|
+
? { fetch: options.fetch ?? feed.git.fetch ?? false, kind: "git", ...(feed.git.paths ? { paths: feed.git.paths } : {}), ref: typeof ref === "string" ? ref : "HEAD", repo: path.resolve(declared.base, feed.git.repo) }
|
|
66
|
+
: { directory: path.resolve(declared.base, feed.directory), kind: "directory" };
|
|
67
|
+
return {
|
|
68
|
+
...(feed.freeze ? { freeze: { after: feed.freeze.after, timezone: feed.freeze.timezone } } : {}),
|
|
69
|
+
healLimit: options.healLimit ?? DEFAULT_FEED_HEAL_LIMIT,
|
|
70
|
+
...(feed.include ? { include: feed.include.map((entry) => ({ from: path.resolve(declared.base, entry.from), to: entry.to })) } : {}),
|
|
71
|
+
keep: feed.keep ?? DEFAULT_FEED_KEEP,
|
|
72
|
+
...(feed.owner ? { owner: feed.owner } : {}),
|
|
73
|
+
...(feed.prepare ? { prepare: toPrepare(feed.prepare, options) } : {}),
|
|
74
|
+
...(declared.record ? { record: declared.record } : {}),
|
|
75
|
+
...(ref !== undefined && typeof ref !== "string" ? { refRule: toRefRule(ref, declared.base) } : {}),
|
|
76
|
+
resourceId: declared.id,
|
|
77
|
+
source,
|
|
78
|
+
// Beside the volume's content root: the same filesystem (rename stays atomic), never inside it.
|
|
79
|
+
stateDir: resolveFeedStateDirectory(declared.name, { ...options, volumePath: volume }),
|
|
80
|
+
...(feed.validate ? { validate: { command: feed.validate, cwd: declared.base, timeoutMs: options.validateTimeoutMs ?? DEFAULT_FEED_VALIDATE_TIMEOUT_MS } } : {}),
|
|
81
|
+
volume,
|
|
82
|
+
volumeName: declared.name
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export const resolveFeedTarget = async (inputPath, resourceId, options = {}) => {
|
|
86
|
+
const feeds = await findDeclaredVolumeFeeds(inputPath);
|
|
87
|
+
const declared = feeds.find((entry) => entry.id === resourceId);
|
|
88
|
+
if (!declared) {
|
|
89
|
+
throw new SpawnfileError("validation_error", `no fed volume ${resourceId} in ${inputPath}${feeds.length ? `; fed volumes: ${feeds.map((entry) => entry.id).join(", ")}` : "; no volume declares feed"}`);
|
|
90
|
+
}
|
|
91
|
+
return toFeedTarget(declared, options);
|
|
92
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type FeedLanding } from "./feedLandings.js";
|
|
2
|
+
import type { FeedProvenance } from "./feedSource.js";
|
|
3
|
+
export declare const FEED_LANDED_VERSION = "spawnfile.volume-feed-landed.v1";
|
|
4
|
+
export declare const FEED_IDENTITY_VERSION = "spawnfile.volume-feed.v1";
|
|
5
|
+
export interface FeedIdentity {
|
|
6
|
+
files: number;
|
|
7
|
+
landed_at: string;
|
|
8
|
+
resource: string;
|
|
9
|
+
revision: string;
|
|
10
|
+
source: FeedProvenance;
|
|
11
|
+
tree: string;
|
|
12
|
+
version: typeof FEED_IDENTITY_VERSION;
|
|
13
|
+
volume: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FeedLandedRecord {
|
|
16
|
+
/** Recoverable history, independent of the next refresh's source declaration. */
|
|
17
|
+
recording?: {
|
|
18
|
+
keep_ms: number;
|
|
19
|
+
landing: FeedLanding;
|
|
20
|
+
};
|
|
21
|
+
/** Re-land cycles per revision since the volume was last clean; a ceiling suspends auto-repair. */
|
|
22
|
+
heals: Record<string, number>;
|
|
23
|
+
identity: FeedIdentity;
|
|
24
|
+
identity_sha256: string;
|
|
25
|
+
/** Local date (freeze time zone) the serving revision was landed in; only feeds that declare a freeze stamp it. */
|
|
26
|
+
period?: string;
|
|
27
|
+
revision: string;
|
|
28
|
+
/** Physical name of the serving tree under trees/. */
|
|
29
|
+
tree: string;
|
|
30
|
+
/** Recorded tree names in serving order, the serving tree last. */
|
|
31
|
+
trees: string[];
|
|
32
|
+
version: typeof FEED_LANDED_VERSION;
|
|
33
|
+
}
|
|
34
|
+
export declare const isFeedRevision: (value: unknown) => value is string;
|
|
35
|
+
export declare const isFeedTreeName: (value: unknown) => value is string;
|
|
36
|
+
export declare const feedIdentityFindings: (record: unknown) => string[];
|
|
37
|
+
export declare const feedLandedFindings: (record: unknown) => string[];
|
|
38
|
+
/** Findings, not a throw: a corrupt record degrades to "nothing landed" and is re-earned by a full land. */
|
|
39
|
+
export declare const readFeedLanded: (stateDir: string) => {
|
|
40
|
+
reason: string | null;
|
|
41
|
+
record: FeedLandedRecord | null;
|
|
42
|
+
};
|
|
43
|
+
/** tmp + rename, 0600, and only ever after the volume mutation it describes has succeeded. */
|
|
44
|
+
export declare const writeFeedLanded: (stateDir: string, record: FeedLandedRecord) => FeedLandedRecord;
|
|
45
|
+
export declare const feedIdentityBytes: (record: FeedIdentity) => string;
|
|
46
|
+
/**
|
|
47
|
+
* Validated after it is on disk and before it is visible -- reparsing what the filesystem holds is what
|
|
48
|
+
* catches a truncated write -- then moved into the volume with one rename. The temporary file lives in
|
|
49
|
+
* `tmpDir`, outside the volume, so the volume root never gains a transient entry.
|
|
50
|
+
*/
|
|
51
|
+
export declare const writeFeedIdentity: (volume: string, record: FeedIdentity, { owner, tmpDir }: {
|
|
52
|
+
owner?: string;
|
|
53
|
+
tmpDir: string;
|
|
54
|
+
}) => string;
|
|
55
|
+
export declare const readFeedIdentityBytes: (volume: string) => string | null;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Two records, kept apart on purpose:
|
|
2
|
+
//
|
|
3
|
+
// * the HOST record (`<state>/landed.json`), outside the volume: the only input the host decides from;
|
|
4
|
+
// * the IDENTITY record (`<volume>/.spawnfile-feed.json`), inside the volume: an OUTPUT for agents to
|
|
5
|
+
// read. The agent uid can replace that name, so the host takes no decision from it -- it reads it back
|
|
6
|
+
// only to compare its digest with the one the host recorded, which is a tamper check, not trust.
|
|
7
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, chmodSync, lchownSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { writeDurableFileSync } from "../filesystem/index.js";
|
|
10
|
+
import { isFeedLanding } from "./feedLandings.js";
|
|
11
|
+
import { FEED_IDENTITY_FILE, feedError, feedTreeLink } from "./feedLayout.js";
|
|
12
|
+
import { sha256Hex } from "./feedManifest.js";
|
|
13
|
+
export const FEED_LANDED_VERSION = "spawnfile.volume-feed-landed.v1";
|
|
14
|
+
export const FEED_IDENTITY_VERSION = "spawnfile.volume-feed.v1";
|
|
15
|
+
const REVISION = /^[0-9a-f]{64}$/u;
|
|
16
|
+
/** A physical tree name: the revision, or the revision plus a generation for a re-land beside a drifted copy. */
|
|
17
|
+
const TREE_NAME = /^[0-9a-f]{64}(?:\.[1-9][0-9]*)?$/u;
|
|
18
|
+
export const isFeedRevision = (value) => typeof value === "string" && REVISION.test(value);
|
|
19
|
+
export const isFeedTreeName = (value) => typeof value === "string" && TREE_NAME.test(value);
|
|
20
|
+
export const feedIdentityFindings = (record) => {
|
|
21
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
22
|
+
return ["must be a JSON object"];
|
|
23
|
+
const value = record, findings = [];
|
|
24
|
+
if (value.version !== FEED_IDENTITY_VERSION)
|
|
25
|
+
findings.push(`version must be ${FEED_IDENTITY_VERSION}`);
|
|
26
|
+
if (!isFeedRevision(value.revision))
|
|
27
|
+
findings.push("revision must be a 64-character hex digest");
|
|
28
|
+
else if (typeof value.tree !== "string" || !value.tree.startsWith(`${feedTreeLink(value.revision)}`) || !isFeedTreeName(value.tree.slice("trees/".length)))
|
|
29
|
+
findings.push(`tree must be ${feedTreeLink(value.revision)}[.<generation>]`);
|
|
30
|
+
if (typeof value.resource !== "string" || !value.resource)
|
|
31
|
+
findings.push("resource must name the volume resource");
|
|
32
|
+
if (typeof value.volume !== "string" || !value.volume)
|
|
33
|
+
findings.push("volume must name the host volume");
|
|
34
|
+
if (!Number.isInteger(value.files) || value.files < 0)
|
|
35
|
+
findings.push("files must be a count");
|
|
36
|
+
if (typeof value.landed_at !== "string" || !value.landed_at)
|
|
37
|
+
findings.push("landed_at must be an instant");
|
|
38
|
+
if (!value.source || typeof value.source !== "object" || !["git", "directory"].includes(value.source.kind))
|
|
39
|
+
findings.push("source must describe a git or directory source");
|
|
40
|
+
return findings;
|
|
41
|
+
};
|
|
42
|
+
export const feedLandedFindings = (record) => {
|
|
43
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
44
|
+
return ["must be a JSON object"];
|
|
45
|
+
const value = record, findings = [];
|
|
46
|
+
if (value.version !== FEED_LANDED_VERSION)
|
|
47
|
+
findings.push(`version must be ${FEED_LANDED_VERSION}`);
|
|
48
|
+
if (!isFeedRevision(value.revision))
|
|
49
|
+
findings.push("revision must be a 64-character hex digest");
|
|
50
|
+
if (!isFeedTreeName(value.tree) || (isFeedRevision(value.revision) && value.tree.slice(0, 64) !== value.revision))
|
|
51
|
+
findings.push("tree must name a tree of the serving revision");
|
|
52
|
+
if (!Array.isArray(value.trees) || value.trees.some((tree) => !isFeedTreeName(tree)))
|
|
53
|
+
findings.push("trees must list landed tree names");
|
|
54
|
+
else if (value.trees.at(-1) !== value.tree)
|
|
55
|
+
findings.push("trees must end with the serving tree");
|
|
56
|
+
if (typeof value.identity_sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.identity_sha256))
|
|
57
|
+
findings.push("identity_sha256 must be a hex digest");
|
|
58
|
+
findings.push(...feedIdentityFindings(value.identity).map((finding) => `identity ${finding}`));
|
|
59
|
+
if (value.identity && (value.identity.revision !== value.revision || value.identity.tree !== `trees/${value.tree}`))
|
|
60
|
+
findings.push("identity must describe the serving tree");
|
|
61
|
+
if (value.period !== undefined && (typeof value.period !== "string" || !/^\d{4}-\d{2}-\d{2}$/u.test(value.period)))
|
|
62
|
+
findings.push("period must be a YYYY-MM-DD date");
|
|
63
|
+
if (!value.heals || typeof value.heals !== "object" || Array.isArray(value.heals))
|
|
64
|
+
findings.push("heals must be an object");
|
|
65
|
+
if (value.recording !== undefined && (!Number.isSafeInteger(value.recording?.keep_ms)
|
|
66
|
+
|| value.recording.keep_ms <= 0 || !isFeedLanding(value.recording.landing)
|
|
67
|
+
|| value.recording.landing.landed_at !== value.identity?.landed_at
|
|
68
|
+
|| value.recording.landing.tree !== value.identity?.tree))
|
|
69
|
+
findings.push("recording must describe the landed identity");
|
|
70
|
+
return findings;
|
|
71
|
+
};
|
|
72
|
+
const landedPath = (stateDir) => path.join(stateDir, "landed.json");
|
|
73
|
+
/** Findings, not a throw: a corrupt record degrades to "nothing landed" and is re-earned by a full land. */
|
|
74
|
+
export const readFeedLanded = (stateDir) => {
|
|
75
|
+
let text;
|
|
76
|
+
try {
|
|
77
|
+
text = readFileSync(landedPath(stateDir), "utf8");
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { reason: null, record: null };
|
|
81
|
+
}
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(text);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return { reason: "landed.json is not parseable JSON", record: null };
|
|
88
|
+
}
|
|
89
|
+
const findings = feedLandedFindings(parsed);
|
|
90
|
+
return findings.length ? { reason: `landed.json is invalid: ${findings.join("; ")}`, record: null } : { reason: null, record: parsed };
|
|
91
|
+
};
|
|
92
|
+
/** tmp + rename, 0600, and only ever after the volume mutation it describes has succeeded. */
|
|
93
|
+
export const writeFeedLanded = (stateDir, record) => {
|
|
94
|
+
const findings = feedLandedFindings(record);
|
|
95
|
+
if (findings.length)
|
|
96
|
+
throw feedError(`refusing to write an invalid landed.json: ${findings.join("; ")}`);
|
|
97
|
+
mkdirSync(stateDir, { mode: 0o700, recursive: true });
|
|
98
|
+
const file = landedPath(stateDir), bytes = `${JSON.stringify(record, null, 2)}\n`;
|
|
99
|
+
if (record.recording)
|
|
100
|
+
writeDurableFileSync(file, bytes);
|
|
101
|
+
else {
|
|
102
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
103
|
+
rmSync(tmp, { force: true });
|
|
104
|
+
writeFileSync(tmp, bytes, { mode: 0o600 });
|
|
105
|
+
renameSync(tmp, file);
|
|
106
|
+
}
|
|
107
|
+
return record;
|
|
108
|
+
};
|
|
109
|
+
export const feedIdentityBytes = (record) => `${JSON.stringify(record, null, 2)}\n`;
|
|
110
|
+
/**
|
|
111
|
+
* Validated after it is on disk and before it is visible -- reparsing what the filesystem holds is what
|
|
112
|
+
* catches a truncated write -- then moved into the volume with one rename. The temporary file lives in
|
|
113
|
+
* `tmpDir`, outside the volume, so the volume root never gains a transient entry.
|
|
114
|
+
*/
|
|
115
|
+
export const writeFeedIdentity = (volume, record, { owner, tmpDir }) => {
|
|
116
|
+
const planned = feedIdentityFindings(record);
|
|
117
|
+
if (planned.length)
|
|
118
|
+
throw feedError(`refusing to publish an invalid ${FEED_IDENTITY_FILE}: ${planned.join("; ")}`);
|
|
119
|
+
const tmp = path.join(tmpDir, `${FEED_IDENTITY_FILE}.${process.pid}.tmp`);
|
|
120
|
+
rmSync(tmp, { force: true });
|
|
121
|
+
const bytes = feedIdentityBytes(record);
|
|
122
|
+
writeFileSync(tmp, bytes, { mode: 0o444 });
|
|
123
|
+
let written = null;
|
|
124
|
+
try {
|
|
125
|
+
written = JSON.parse(readFileSync(tmp, "utf8"));
|
|
126
|
+
}
|
|
127
|
+
catch { /* reported below */ }
|
|
128
|
+
const findings = written === null ? ["did not survive the write as JSON"] : feedIdentityFindings(written);
|
|
129
|
+
if (findings.length) {
|
|
130
|
+
rmSync(tmp, { force: true });
|
|
131
|
+
throw feedError(`${FEED_IDENTITY_FILE} failed validation after it was written: ${findings.join("; ")}`);
|
|
132
|
+
}
|
|
133
|
+
if (owner) {
|
|
134
|
+
const [uid, gid] = owner.split(":").map(Number);
|
|
135
|
+
lchownSync(tmp, uid, gid);
|
|
136
|
+
}
|
|
137
|
+
chmodSync(tmp, 0o444);
|
|
138
|
+
renameSync(tmp, path.join(volume, FEED_IDENTITY_FILE));
|
|
139
|
+
return sha256Hex(bytes);
|
|
140
|
+
};
|
|
141
|
+
export const readFeedIdentityBytes = (volume) => {
|
|
142
|
+
try {
|
|
143
|
+
return readFileSync(path.join(volume, FEED_IDENTITY_FILE), "utf8");
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FeedLandedRecord } from "./feedRecord.js";
|
|
2
|
+
import type { FeedExec } from "./feedSource.js";
|
|
3
|
+
import type { FeedTarget } from "./feedTarget.js";
|
|
4
|
+
/** YYYY-MM-DD in `zone` at `now`. */
|
|
5
|
+
export declare const feedLocalDate: (now: Date, zone: string) => string;
|
|
6
|
+
/** HH:MM (24-hour, zero-padded, so a string compare orders it) in `zone` at `now`. */
|
|
7
|
+
export declare const feedLocalClock: (now: Date, zone: string) => string;
|
|
8
|
+
export declare const expandFeedRefTemplate: (template: string, now: Date) => string;
|
|
9
|
+
/**
|
|
10
|
+
* The fed paths at `now`: each declared path with its date placeholders expanded at the same instant as the
|
|
11
|
+
* ref, so a dated ref and the dated directory inside it always name the same day. Undefined feeds the whole tree.
|
|
12
|
+
*/
|
|
13
|
+
export declare const expandFeedPaths: (paths: string[] | undefined, now: Date) => string[] | undefined;
|
|
14
|
+
/** The period a landing at `now` belongs to; only a feed that declares a freeze has periods. */
|
|
15
|
+
export declare const feedPeriod: (target: FeedTarget, now: Date) => string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The INSTANT `after` (HH:MM local) first arrives on `date` in `zone`. An instant, not a wall-clock
|
|
18
|
+
* compare: when clocks fall back the wall clock repeats an hour, and a wall-clock compare would thaw a
|
|
19
|
+
* volume that already froze. An ambiguous local time resolves to its first occurrence and a skipped one
|
|
20
|
+
* to the earlier candidate: both err toward freezing sooner.
|
|
21
|
+
*/
|
|
22
|
+
export declare const feedCutoffInstant: (date: string, after: string, zone: string) => Date;
|
|
23
|
+
export type FeedRefChoice = {
|
|
24
|
+
kind: "ref";
|
|
25
|
+
ref: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "waiting";
|
|
28
|
+
reason: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The ref this refresh lands: the fixed ref, or the rule's ref when it exists, else its fallback. A rule
|
|
32
|
+
* whose ref and fallback are both missing is a wait before the freeze cutoff and a refusal after it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const chooseFeedRef: (target: FeedTarget, { exec, now }: {
|
|
35
|
+
exec: FeedExec;
|
|
36
|
+
now: Date;
|
|
37
|
+
}) => FeedRefChoice;
|
|
38
|
+
/**
|
|
39
|
+
* Frozen: the host landed the serving revision in THIS period, the period's cutoff has passed, and the ref
|
|
40
|
+
* and paths chosen now are the ones it was landed from. A new period, a different ref name (the next
|
|
41
|
+
* period's ref appearing) or different expanded paths advance again.
|
|
42
|
+
*/
|
|
43
|
+
export declare const feedFrozen: (target: FeedTarget, record: FeedLandedRecord | null, ref: string, now: Date, paths?: string[]) => boolean;
|