@plurnk/plurnk-service 1.3.12 → 1.5.0
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/.env.defaults +95 -91
- package/INSTALL.md +54 -28
- package/README.md +25 -10
- package/SPEC.md +2426 -942
- package/dist/Paths.d.ts +4 -1
- package/dist/Paths.d.ts.map +1 -1
- package/dist/Paths.js +24 -23
- package/dist/Paths.js.map +1 -1
- package/dist/build-info.json +1 -1
- package/dist/content/edit-receipt.d.ts +13 -28
- package/dist/content/edit-receipt.d.ts.map +1 -1
- package/dist/content/edit-receipt.js +285 -104
- package/dist/content/edit-receipt.js.map +1 -1
- package/dist/content/edited-span.d.ts.map +1 -1
- package/dist/content/edited-span.js +2 -5
- package/dist/content/edited-span.js.map +1 -1
- package/dist/content/index.d.ts +4 -3
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +2 -1
- package/dist/content/index.js.map +1 -1
- package/dist/content/line-marker.d.ts +10 -9
- package/dist/content/line-marker.d.ts.map +1 -1
- package/dist/content/line-marker.js +8 -11
- package/dist/content/line-marker.js.map +1 -1
- package/dist/content/matcher.d.ts +15 -26
- package/dist/content/matcher.d.ts.map +1 -1
- package/dist/content/matcher.js +77 -107
- package/dist/content/matcher.js.map +1 -1
- package/dist/content/mimetype-binary.d.ts +2 -2
- package/dist/content/mimetype-binary.d.ts.map +1 -1
- package/dist/content/mimetype-binary.js +10 -10
- package/dist/content/mimetype-binary.js.map +1 -1
- package/dist/content/path-mimetype.js +3 -3
- package/dist/content/path-mimetype.js.map +1 -1
- package/dist/content/read-projector.d.ts +14 -0
- package/dist/content/read-projector.d.ts.map +1 -0
- package/dist/content/read-projector.js +86 -0
- package/dist/content/read-projector.js.map +1 -0
- package/dist/content/read-resolve.d.ts +6 -7
- package/dist/content/read-resolve.d.ts.map +1 -1
- package/dist/content/read-resolve.js +36 -59
- package/dist/content/read-resolve.js.map +1 -1
- package/dist/core/BranchReceipt.d.ts +6 -0
- package/dist/core/BranchReceipt.d.ts.map +1 -0
- package/dist/core/BranchReceipt.js +24 -0
- package/dist/core/BranchReceipt.js.map +1 -0
- package/dist/core/BudgetOverflow.d.ts +20 -0
- package/dist/core/BudgetOverflow.d.ts.map +1 -0
- package/dist/core/BudgetOverflow.js +49 -0
- package/dist/core/BudgetOverflow.js.map +1 -0
- package/dist/core/BudgetReadout.d.ts +8 -0
- package/dist/core/BudgetReadout.d.ts.map +1 -0
- package/dist/core/BudgetReadout.js +79 -0
- package/dist/core/BudgetReadout.js.map +1 -0
- package/dist/core/ChannelWrite.d.ts +31 -9
- package/dist/core/ChannelWrite.d.ts.map +1 -1
- package/dist/core/ChannelWrite.js +30 -23
- package/dist/core/ChannelWrite.js.map +1 -1
- package/dist/core/ChannelWrite.sql +14 -12
- package/dist/core/CoreSchemeServices.d.ts +18 -4
- package/dist/core/CoreSchemeServices.d.ts.map +1 -1
- package/dist/core/CoreSchemeServices.js +7 -1
- package/dist/core/CoreSchemeServices.js.map +1 -1
- package/dist/core/Dispatcher.d.ts +26 -21
- package/dist/core/Dispatcher.d.ts.map +1 -1
- package/dist/core/Dispatcher.js +1626 -671
- package/dist/core/Dispatcher.js.map +1 -1
- package/dist/core/DurableStatement.d.ts +6 -0
- package/dist/core/DurableStatement.d.ts.map +1 -0
- package/dist/core/DurableStatement.js +51 -0
- package/dist/core/DurableStatement.js.map +1 -0
- package/dist/core/Engine.d.ts +64 -29
- package/dist/core/Engine.d.ts.map +1 -1
- package/dist/core/Engine.js +1348 -835
- package/dist/core/Engine.js.map +1 -1
- package/dist/core/Engine.sql +329 -234
- package/dist/core/EnvFlags.js +1 -1
- package/dist/core/EnvFlags.js.map +1 -1
- package/dist/core/ErrorDetail.d.ts +6 -0
- package/dist/core/ErrorDetail.d.ts.map +1 -0
- package/dist/core/ErrorDetail.js +20 -0
- package/dist/core/ErrorDetail.js.map +1 -0
- package/dist/core/ExecutorRegistry.d.ts +14 -3
- package/dist/core/ExecutorRegistry.d.ts.map +1 -1
- package/dist/core/ExecutorRegistry.js +56 -30
- package/dist/core/ExecutorRegistry.js.map +1 -1
- package/dist/core/GitBranch.d.ts +20 -0
- package/dist/core/GitBranch.d.ts.map +1 -0
- package/dist/core/GitBranch.js +110 -0
- package/dist/core/GitBranch.js.map +1 -0
- package/dist/core/JournalTurn.d.ts +9 -0
- package/dist/core/JournalTurn.d.ts.map +1 -0
- package/dist/core/JournalTurn.js +14 -0
- package/dist/core/JournalTurn.js.map +1 -0
- package/dist/core/JournalTurn.sql +10 -0
- package/dist/core/LogBody.d.ts +18 -0
- package/dist/core/LogBody.d.ts.map +1 -0
- package/dist/core/LogBody.js +180 -0
- package/dist/core/LogBody.js.map +1 -0
- package/dist/core/LoopFlagsReader.d.ts +7 -0
- package/dist/core/LoopFlagsReader.d.ts.map +1 -0
- package/dist/core/LoopFlagsReader.js +33 -0
- package/dist/core/LoopFlagsReader.js.map +1 -0
- package/dist/core/LoopLifecycle.d.ts +8 -2
- package/dist/core/LoopLifecycle.d.ts.map +1 -1
- package/dist/core/LoopLifecycle.js +54 -11
- package/dist/core/LoopLifecycle.js.map +1 -1
- package/dist/core/LoopLifecycle.sql +10 -7
- package/dist/core/NoticeChannel.d.ts +13 -0
- package/dist/core/NoticeChannel.d.ts.map +1 -0
- package/dist/core/NoticeChannel.js +40 -0
- package/dist/core/NoticeChannel.js.map +1 -0
- package/dist/core/Owner.d.ts +0 -1
- package/dist/core/Owner.d.ts.map +1 -1
- package/dist/core/Owner.js +2 -5
- package/dist/core/Owner.js.map +1 -1
- package/dist/core/PacketBuilder.d.ts +27 -25
- package/dist/core/PacketBuilder.d.ts.map +1 -1
- package/dist/core/PacketBuilder.js +321 -383
- package/dist/core/PacketBuilder.js.map +1 -1
- package/dist/core/ProblemLog.d.ts +26 -0
- package/dist/core/ProblemLog.d.ts.map +1 -0
- package/dist/core/ProblemLog.js +58 -0
- package/dist/core/ProblemLog.js.map +1 -0
- package/dist/core/ProposalLifecycle.d.ts +15 -21
- package/dist/core/ProposalLifecycle.d.ts.map +1 -1
- package/dist/core/ProposalLifecycle.js +291 -78
- package/dist/core/ProposalLifecycle.js.map +1 -1
- package/dist/core/ProviderInstantiate.d.ts.map +1 -1
- package/dist/core/ProviderInstantiate.js +51 -48
- package/dist/core/ProviderInstantiate.js.map +1 -1
- package/dist/core/SchemeRegistry.d.ts +14 -8
- package/dist/core/SchemeRegistry.d.ts.map +1 -1
- package/dist/core/SchemeRegistry.js +189 -114
- package/dist/core/SchemeRegistry.js.map +1 -1
- package/dist/core/ServiceTeardown.d.ts +11 -0
- package/dist/core/ServiceTeardown.d.ts.map +1 -0
- package/dist/core/ServiceTeardown.js +70 -0
- package/dist/core/ServiceTeardown.js.map +1 -0
- package/dist/core/StoredPacket.d.ts +29 -0
- package/dist/core/StoredPacket.d.ts.map +1 -0
- package/dist/core/StoredPacket.js +147 -0
- package/dist/core/StoredPacket.js.map +1 -0
- package/dist/core/StrikeRail.d.ts +6 -3
- package/dist/core/StrikeRail.d.ts.map +1 -1
- package/dist/core/StrikeRail.js +27 -56
- package/dist/core/StrikeRail.js.map +1 -1
- package/dist/core/TerminalResult.d.ts +16 -0
- package/dist/core/TerminalResult.d.ts.map +1 -0
- package/dist/core/TerminalResult.js +49 -0
- package/dist/core/TerminalResult.js.map +1 -0
- package/dist/core/WorkerControlAddress.d.ts +17 -0
- package/dist/core/WorkerControlAddress.d.ts.map +1 -0
- package/dist/core/WorkerControlAddress.js +44 -0
- package/dist/core/WorkerControlAddress.js.map +1 -0
- package/dist/core/WorkerName.d.ts +31 -0
- package/dist/core/WorkerName.d.ts.map +1 -0
- package/dist/core/WorkerName.js +95 -0
- package/dist/core/WorkerName.js.map +1 -0
- package/dist/core/WorkerName.sql +36 -0
- package/dist/core/WorkspaceGate.d.ts +14 -0
- package/dist/core/WorkspaceGate.d.ts.map +1 -0
- package/dist/core/WorkspaceGate.js +155 -0
- package/dist/core/WorkspaceGate.js.map +1 -0
- package/dist/core/caps/CapsResolve.d.ts +5 -1
- package/dist/core/caps/CapsResolve.d.ts.map +1 -1
- package/dist/core/caps/CapsResolve.js +8 -4
- package/dist/core/caps/CapsResolve.js.map +1 -1
- package/dist/core/caps/DbChannelCaps.d.ts +5 -11
- package/dist/core/caps/DbChannelCaps.d.ts.map +1 -1
- package/dist/core/caps/DbChannelCaps.js +30 -12
- package/dist/core/caps/DbChannelCaps.js.map +1 -1
- package/dist/core/caps/DbEntryCaps.d.ts +5 -14
- package/dist/core/caps/DbEntryCaps.d.ts.map +1 -1
- package/dist/core/caps/DbEntryCaps.js +27 -8
- package/dist/core/caps/DbEntryCaps.js.map +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts +1 -1
- package/dist/core/caps/DbNotifyCaps.d.ts.map +1 -1
- package/dist/core/caps/DbNotifyCaps.js +18 -15
- package/dist/core/caps/DbNotifyCaps.js.map +1 -1
- package/dist/core/caps/DbProjectionCaps.d.ts +5 -5
- package/dist/core/caps/DbProjectionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbProjectionCaps.js +21 -4
- package/dist/core/caps/DbProjectionCaps.js.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.d.ts +4 -6
- package/dist/core/caps/DbSubscriptionCaps.d.ts.map +1 -1
- package/dist/core/caps/DbSubscriptionCaps.js +75 -66
- package/dist/core/caps/DbSubscriptionCaps.js.map +1 -1
- package/dist/core/caps/DbTagCaps.d.ts +5 -12
- package/dist/core/caps/DbTagCaps.d.ts.map +1 -1
- package/dist/core/caps/DbTagCaps.js +20 -11
- package/dist/core/caps/DbTagCaps.js.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts +6 -1
- package/dist/core/caps/SchemeCtxImpl.d.ts.map +1 -1
- package/dist/core/caps/SchemeCtxImpl.js +6 -6
- package/dist/core/caps/SchemeCtxImpl.js.map +1 -1
- package/dist/core/content-hash.js +1 -1
- package/dist/core/content-hash.js.map +1 -1
- package/dist/core/env-defaults.d.ts.map +1 -1
- package/dist/core/env-defaults.js.map +1 -1
- package/dist/core/fork.d.ts.map +1 -1
- package/dist/core/fork.js +55 -30
- package/dist/core/fork.js.map +1 -1
- package/dist/core/fork.sql +43 -29
- package/dist/core/git-env.d.ts +2 -0
- package/dist/core/git-env.d.ts.map +1 -1
- package/dist/core/git-env.js +13 -18
- package/dist/core/git-env.js.map +1 -1
- package/dist/core/git-iso.d.ts +3 -2
- package/dist/core/git-iso.d.ts.map +1 -1
- package/dist/core/git-iso.js +36 -27
- package/dist/core/git-iso.js.map +1 -1
- package/dist/core/git-membership.d.ts +2 -1
- package/dist/core/git-membership.d.ts.map +1 -1
- package/dist/core/git-membership.js +276 -162
- package/dist/core/git-membership.js.map +1 -1
- package/dist/core/git-state.d.ts +8 -1
- package/dist/core/git-state.d.ts.map +1 -1
- package/dist/core/git-state.js +44 -26
- package/dist/core/git-state.js.map +1 -1
- package/dist/core/namespace.d.ts +1 -0
- package/dist/core/namespace.d.ts.map +1 -1
- package/dist/core/namespace.js +20 -0
- package/dist/core/namespace.js.map +1 -1
- package/dist/core/optimistic-settlement.d.ts +2 -0
- package/dist/core/optimistic-settlement.d.ts.map +1 -0
- package/dist/core/optimistic-settlement.js +14 -0
- package/dist/core/optimistic-settlement.js.map +1 -0
- package/dist/core/owner.sql +1 -1
- package/dist/core/packet-inject.d.ts.map +1 -1
- package/dist/core/packet-inject.js +7 -11
- package/dist/core/packet-inject.js.map +1 -1
- package/dist/core/packet-wire.d.ts +3 -21
- package/dist/core/packet-wire.d.ts.map +1 -1
- package/dist/core/packet-wire.js +305 -385
- package/dist/core/packet-wire.js.map +1 -1
- package/dist/core/plurnk-uri.d.ts +12 -1
- package/dist/core/plurnk-uri.d.ts.map +1 -1
- package/dist/core/plurnk-uri.js +49 -29
- package/dist/core/plurnk-uri.js.map +1 -1
- package/dist/core/results.d.ts +18 -5
- package/dist/core/results.d.ts.map +1 -1
- package/dist/core/results.js +33 -18
- package/dist/core/results.js.map +1 -1
- package/dist/core/ruler_count.d.ts +4 -0
- package/dist/core/ruler_count.d.ts.map +1 -0
- package/dist/core/ruler_count.js +7 -0
- package/dist/core/ruler_count.js.map +1 -0
- package/dist/core/scheme-types.d.ts +3 -17
- package/dist/core/scheme-types.d.ts.map +1 -1
- package/dist/core/scheme-types.js.map +1 -1
- package/dist/core/search-gate.d.ts.map +1 -1
- package/dist/core/search-gate.js +3 -11
- package/dist/core/search-gate.js.map +1 -1
- package/dist/core/teaching-corpus.d.ts +6 -0
- package/dist/core/teaching-corpus.d.ts.map +1 -0
- package/dist/core/teaching-corpus.js +23 -0
- package/dist/core/teaching-corpus.js.map +1 -0
- package/dist/core/teaching.d.ts +0 -1
- package/dist/core/teaching.d.ts.map +1 -1
- package/dist/core/teaching.js +2 -8
- package/dist/core/teaching.js.map +1 -1
- package/dist/core/token-ruler.d.ts.map +1 -1
- package/dist/core/token-ruler.js +3 -16
- package/dist/core/token-ruler.js.map +1 -1
- package/dist/core/turn-scheduler.d.ts +1 -1
- package/dist/core/turn-scheduler.d.ts.map +1 -1
- package/dist/core/turn-scheduler.js +1 -1
- package/dist/core/turn-scheduler.js.map +1 -1
- package/dist/core/worker-cap.d.ts +2 -4
- package/dist/core/worker-cap.d.ts.map +1 -1
- package/dist/core/worker-cap.js +13 -4
- package/dist/core/worker-cap.js.map +1 -1
- package/dist/core/{run-ops.sql → worker-ops.sql} +14 -14
- package/dist/core/workspace-settings.d.ts.map +1 -1
- package/dist/core/workspace-settings.js +17 -9
- package/dist/core/workspace-settings.js.map +1 -1
- package/dist/digest/Digest.d.ts +2 -1
- package/dist/digest/Digest.d.ts.map +1 -1
- package/dist/digest/Digest.js +510 -149
- package/dist/digest/Digest.js.map +1 -1
- package/dist/digest/digest.sql +23 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/dist/observe/api.d.ts +6 -0
- package/dist/observe/api.d.ts.map +1 -0
- package/dist/observe/api.js +11 -0
- package/dist/observe/api.js.map +1 -0
- package/dist/observe/init.d.ts +7 -0
- package/dist/observe/init.d.ts.map +1 -0
- package/dist/observe/init.js +159 -0
- package/dist/observe/init.js.map +1 -0
- package/dist/observe/metrics.d.ts +6 -0
- package/dist/observe/metrics.d.ts.map +1 -0
- package/dist/observe/metrics.js +15 -0
- package/dist/observe/metrics.js.map +1 -0
- package/dist/observe/spans.d.ts +4 -0
- package/dist/observe/spans.d.ts.map +1 -0
- package/dist/observe/spans.js +51 -0
- package/dist/observe/spans.js.map +1 -0
- package/dist/schemes/EffectPolicy.d.ts +1 -0
- package/dist/schemes/EffectPolicy.d.ts.map +1 -1
- package/dist/schemes/EffectPolicy.js +5 -2
- package/dist/schemes/EffectPolicy.js.map +1 -1
- package/dist/schemes/Exec.d.ts +9 -14
- package/dist/schemes/Exec.d.ts.map +1 -1
- package/dist/schemes/Exec.js +402 -262
- package/dist/schemes/Exec.js.map +1 -1
- package/dist/schemes/ExecOutputScheme.d.ts +9 -9
- package/dist/schemes/ExecOutputScheme.d.ts.map +1 -1
- package/dist/schemes/ExecOutputScheme.js +49 -18
- package/dist/schemes/ExecOutputScheme.js.map +1 -1
- package/dist/schemes/File.d.ts +7 -13
- package/dist/schemes/File.d.ts.map +1 -1
- package/dist/schemes/File.js +280 -151
- package/dist/schemes/File.js.map +1 -1
- package/dist/schemes/Log.d.ts +8 -12
- package/dist/schemes/Log.d.ts.map +1 -1
- package/dist/schemes/Log.js +402 -233
- package/dist/schemes/Log.js.map +1 -1
- package/dist/schemes/Log.sql +71 -32
- package/dist/schemes/Prompt.d.ts +3 -3
- package/dist/schemes/Prompt.d.ts.map +1 -1
- package/dist/schemes/Prompt.js +10 -10
- package/dist/schemes/Prompt.js.map +1 -1
- package/dist/schemes/Skill.d.ts +2 -3
- package/dist/schemes/Skill.d.ts.map +1 -1
- package/dist/schemes/Skill.js +2 -6
- package/dist/schemes/Skill.js.map +1 -1
- package/dist/schemes/Worker.d.ts +11 -19
- package/dist/schemes/Worker.d.ts.map +1 -1
- package/dist/schemes/Worker.js +210 -89
- package/dist/schemes/Worker.js.map +1 -1
- package/dist/schemes/_entry-chunk.d.ts.map +1 -1
- package/dist/schemes/_entry-chunk.js +12 -5
- package/dist/schemes/_entry-chunk.js.map +1 -1
- package/dist/schemes/_entry-crud.d.ts +11 -9
- package/dist/schemes/_entry-crud.d.ts.map +1 -1
- package/dist/schemes/_entry-crud.js +72 -13
- package/dist/schemes/_entry-crud.js.map +1 -1
- package/dist/schemes/_entry-crud.sql +28 -10
- package/dist/schemes/_entry-find.d.ts +49 -18
- package/dist/schemes/_entry-find.d.ts.map +1 -1
- package/dist/schemes/_entry-find.js +348 -195
- package/dist/schemes/_entry-find.js.map +1 -1
- package/dist/schemes/_entry-find.sql +20 -8
- package/dist/schemes/_entry-graph.d.ts +4 -2
- package/dist/schemes/_entry-graph.d.ts.map +1 -1
- package/dist/schemes/_entry-graph.js +55 -31
- package/dist/schemes/_entry-graph.js.map +1 -1
- package/dist/schemes/_entry-graph.sql +37 -22
- package/dist/schemes/_entry-manifest.d.ts +10 -15
- package/dist/schemes/_entry-manifest.d.ts.map +1 -1
- package/dist/schemes/_entry-manifest.js +50 -268
- package/dist/schemes/_entry-manifest.js.map +1 -1
- package/dist/schemes/_entry-ops.d.ts +14 -15
- package/dist/schemes/_entry-ops.d.ts.map +1 -1
- package/dist/schemes/_entry-ops.js +121 -110
- package/dist/schemes/_entry-ops.js.map +1 -1
- package/dist/schemes/_entry-ops.sql +2 -3
- package/dist/schemes/_entry-semantic.d.ts +22 -9
- package/dist/schemes/_entry-semantic.d.ts.map +1 -1
- package/dist/schemes/_entry-semantic.js +127 -67
- package/dist/schemes/_entry-semantic.js.map +1 -1
- package/dist/schemes/_entry-semantic.sql +63 -45
- package/dist/schemes/_entry-send.d.ts +3 -4
- package/dist/schemes/_entry-send.d.ts.map +1 -1
- package/dist/schemes/_entry-send.js +48 -23
- package/dist/schemes/_entry-send.js.map +1 -1
- package/dist/schemes/_path-scope.d.ts +23 -0
- package/dist/schemes/_path-scope.d.ts.map +1 -0
- package/dist/schemes/_path-scope.js +61 -0
- package/dist/schemes/_path-scope.js.map +1 -0
- package/dist/schemes/_search-candidate.d.ts +20 -0
- package/dist/schemes/_search-candidate.d.ts.map +1 -0
- package/dist/schemes/_search-candidate.js +19 -0
- package/dist/schemes/_search-candidate.js.map +1 -0
- package/dist/schemes/_search-exclusion.d.ts +7 -0
- package/dist/schemes/_search-exclusion.d.ts.map +1 -0
- package/dist/schemes/_search-exclusion.js +23 -0
- package/dist/schemes/_search-exclusion.js.map +1 -0
- package/dist/schemes/_search-index.d.ts +6 -0
- package/dist/schemes/_search-index.d.ts.map +1 -0
- package/dist/schemes/_search-index.js +328 -0
- package/dist/schemes/_search-index.js.map +1 -0
- package/dist/schemes/cosine.js +1 -1
- package/dist/schemes/cosine.js.map +1 -1
- package/dist/schemes/exec-abort.js +3 -3
- package/dist/schemes/exec-abort.js.map +1 -1
- package/dist/schemes/exec-env.js +3 -3
- package/dist/schemes/exec-env.js.map +1 -1
- package/dist/server/BranchBatches.d.ts +49 -0
- package/dist/server/BranchBatches.d.ts.map +1 -0
- package/dist/server/BranchBatches.js +592 -0
- package/dist/server/BranchBatches.js.map +1 -0
- package/dist/server/Daemon.d.ts +39 -71
- package/dist/server/Daemon.d.ts.map +1 -1
- package/dist/server/Daemon.js +1133 -491
- package/dist/server/Daemon.js.map +1 -1
- package/dist/server/DaemonModule.d.ts +31 -0
- package/dist/server/DaemonModule.d.ts.map +1 -0
- package/dist/server/DaemonModule.js +2 -0
- package/dist/server/DaemonModule.js.map +1 -0
- package/dist/server/branch-batch.sql +154 -0
- package/dist/server/client-input.d.ts +13 -0
- package/dist/server/client-input.d.ts.map +1 -1
- package/dist/server/client-input.js +268 -57
- package/dist/server/client-input.js.map +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts +1 -1
- package/dist/server/dispatch-as-plurnk.d.ts.map +1 -1
- package/dist/server/dispatch-as-plurnk.js +15 -7
- package/dist/server/dispatch-as-plurnk.js.map +1 -1
- package/dist/server/drain.sql +96 -65
- package/dist/server/envelope.d.ts +4 -7
- package/dist/server/envelope.d.ts.map +1 -1
- package/dist/server/envelope.js +118 -95
- package/dist/server/envelope.js.map +1 -1
- package/dist/server/envelope.sql +18 -23
- package/dist/server/exec-poll-backoff.js +1 -1
- package/dist/server/exec-poll-backoff.js.map +1 -1
- package/dist/server/lifecycle-recovery.sql +112 -2
- package/dist/server/logEntry.d.ts +4 -2
- package/dist/server/logEntry.d.ts.map +1 -1
- package/dist/server/logEntry.js +6 -3
- package/dist/server/logEntry.js.map +1 -1
- package/dist/server/logEntry.sql +1 -1
- package/dist/server/loop-model.d.ts.map +1 -1
- package/dist/server/loop-model.js +22 -9
- package/dist/server/loop-model.js.map +1 -1
- package/dist/server/loopDocs.d.ts.map +1 -1
- package/dist/server/loopDocs.js +5 -4
- package/dist/server/loopDocs.js.map +1 -1
- package/dist/server/seam-entry-read.sql +15 -10
- package/dist/server/seam-log-read.sql +2 -2
- package/dist/server/seam-loop.sql +1 -1
- package/dist/server/seam-proposal-list.sql +17 -7
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +79 -61
- package/dist/service.js.map +1 -1
- package/migrations/001_schema.sql +1010 -0
- package/package.json +65 -32
- package/dist/core/PluginLoader.d.ts +0 -20
- package/dist/core/PluginLoader.d.ts.map +0 -1
- package/dist/core/PluginLoader.js +0 -142
- package/dist/core/PluginLoader.js.map +0 -1
- package/dist/core/TelemetryChannel.d.ts +0 -37
- package/dist/core/TelemetryChannel.d.ts.map +0 -1
- package/dist/core/TelemetryChannel.js +0 -80
- package/dist/core/TelemetryChannel.js.map +0 -1
- package/dist/core/path-decode.d.ts +0 -3
- package/dist/core/path-decode.d.ts.map +0 -1
- package/dist/core/path-decode.js +0 -9
- package/dist/core/path-decode.js.map +0 -1
- package/dist/core/plugin-attribution.d.ts +0 -5
- package/dist/core/plugin-attribution.d.ts.map +0 -1
- package/dist/core/plugin-attribution.js +0 -39
- package/dist/core/plugin-attribution.js.map +0 -1
- package/dist/core/world-state.d.ts +0 -10
- package/dist/core/world-state.d.ts.map +0 -1
- package/dist/core/world-state.js +0 -35
- package/dist/core/world-state.js.map +0 -1
- package/dist/core/world-state.sql +0 -35
- package/dist/core/zero-pin.d.ts +0 -3
- package/dist/core/zero-pin.d.ts.map +0 -1
- package/dist/core/zero-pin.js +0 -16
- package/dist/core/zero-pin.js.map +0 -1
- package/dist/server/auto.d.ts +0 -6
- package/dist/server/auto.d.ts.map +0 -1
- package/dist/server/auto.js +0 -63
- package/dist/server/auto.js.map +0 -1
- package/dist/server/clientTurn.d.ts +0 -6
- package/dist/server/clientTurn.d.ts.map +0 -1
- package/dist/server/clientTurn.js +0 -22
- package/dist/server/clientTurn.js.map +0 -1
- package/dist/server/clientTurn.sql +0 -10
- package/dist/server/noProposals.d.ts +0 -6
- package/dist/server/noProposals.d.ts.map +0 -1
- package/dist/server/noProposals.js +0 -37
- package/dist/server/noProposals.js.map +0 -1
- package/dist/server/version-info.d.ts +0 -14
- package/dist/server/version-info.d.ts.map +0 -1
- package/dist/server/version-info.js +0 -69
- package/dist/server/version-info.js.map +0 -1
- package/migrations/0000-00-00.01_schema.sql +0 -536
- package/migrations/0004_loop-provider.sql +0 -3
- package/migrations/0005_subscription-published-channel.sql +0 -3
- package/migrations/0006_loop-max-turns.sql +0 -3
package/dist/schemes/Exec.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
import { parsePath } from "@plurnk/plurnk-
|
|
1
|
+
import { parsePath } from "@plurnk/plurnk-contracts";
|
|
2
2
|
import { Policy } from "@plurnk/plurnk-execs";
|
|
3
|
-
import { WebFetcher } from "@plurnk/plurnk-schemes-http";
|
|
3
|
+
import { WebFetcher, WebMaterializationError, } from "@plurnk/plurnk-schemes-http";
|
|
4
4
|
import WorkspaceSettings from "../core/workspace-settings.js";
|
|
5
5
|
import EffectPolicy from "./EffectPolicy.js";
|
|
6
|
-
import EntryOps from "./_entry-ops.js";
|
|
7
6
|
import EntryCrud from "./_entry-crud.js";
|
|
8
7
|
import Owner from "../core/Owner.js";
|
|
9
|
-
import { foldAuthorityIntoPath } from "../core/plurnk-uri.js";
|
|
10
|
-
import { decodePathParens } from "../core/path-decode.js";
|
|
11
8
|
import EntryFind from "./_entry-find.js";
|
|
12
9
|
import ChannelWrite from "../core/ChannelWrite.js";
|
|
13
10
|
import ExecEnv from "./exec-env.js";
|
|
14
11
|
import ExecAbort from "./exec-abort.js";
|
|
15
|
-
import { renderAddress } from "../core/plurnk-uri.js";
|
|
12
|
+
import { entryPathnameOf, renderAddress } from "../core/plurnk-uri.js";
|
|
16
13
|
import { writeFile, unlink, stat } from "node:fs/promises";
|
|
17
14
|
import { tmpdir } from "node:os";
|
|
18
15
|
import { join, resolve } from "node:path";
|
|
19
16
|
import { CoreSchemeAdapterBase } from "../core/CoreSchemeServices.js";
|
|
17
|
+
import ErrorDetail from "../core/ErrorDetail.js";
|
|
18
|
+
import Results, { OperationFailureError } from "../core/results.js";
|
|
19
|
+
import { InvalidOperationResultError, NetworkAddress } from "@plurnk/plurnk-schemes";
|
|
20
|
+
import DbProjectionCaps from "../core/caps/DbProjectionCaps.js";
|
|
21
|
+
import WorkerControlAddress from "../core/WorkerControlAddress.js";
|
|
22
|
+
import JournalTurn from "../core/JournalTurn.js";
|
|
23
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
20
24
|
// Executors are discovered + probed at boot into ExecutorRegistry and reach
|
|
21
|
-
// the scheme through ctx.executors (
|
|
25
|
+
// the scheme through ctx.executors ({§exec-registry-resolves}). Each runtime tag
|
|
22
26
|
// resolves to its sibling executor; the scheme itself stays runtime-agnostic.
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// program/data-source. A plurnk-scheme target is NOT local — schemeTargetOf handles that.
|
|
27
|
+
// Extract the local arm of {§exec-target-routing}; non-file schemes are
|
|
28
|
+
// classified separately by schemeSourceOf.
|
|
26
29
|
const localPathFromTarget = (target) => {
|
|
27
30
|
if (target === null)
|
|
28
31
|
return null;
|
|
@@ -33,22 +36,31 @@ const localPathFromTarget = (target) => {
|
|
|
33
36
|
}
|
|
34
37
|
return null;
|
|
35
38
|
};
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
const
|
|
39
|
+
// A non-file scheme target is distinct from the local path handled above. The
|
|
40
|
+
// consumer resolves its content at apply time; executors stay scheme-blind
|
|
41
|
+
// ({§executor-role}).
|
|
42
|
+
const schemeSourceOf = (target) => {
|
|
40
43
|
if (target === null || target.kind !== "url")
|
|
41
44
|
return null;
|
|
42
45
|
if (target.scheme === null || target.scheme === "file")
|
|
43
46
|
return null;
|
|
44
|
-
return
|
|
47
|
+
return target.raw;
|
|
45
48
|
};
|
|
49
|
+
// {§exec-target-routing} — effect classifies the invocation core is admitting,
|
|
50
|
+
// before a scheme-backed source can be materialized. A scheme target with no
|
|
51
|
+
// body becomes the command, so the executor has no target. With a body, the
|
|
52
|
+
// scheme content becomes a target at run time; its authored address is the
|
|
53
|
+
// stable, opaque target-present identity used only for the one effect call.
|
|
54
|
+
const effectTargetOf = (authoredTarget, routedTarget, schemeSource, command) => schemeSource !== null && command.length > 0
|
|
55
|
+
? authoredTarget?.raw ?? null
|
|
56
|
+
: routedTarget;
|
|
46
57
|
// EXEC's pathname is <runtime>/<loop_seq>/<turn_seq>/<sequence> (stamped by
|
|
47
58
|
// Dispatcher.#writeLog). Exec owns this convention, so it — not the client — turns
|
|
48
59
|
// the pathname into the entry's coordinate, mirrored onto stream payloads so
|
|
49
|
-
//
|
|
60
|
+
// clients read fields instead of parsing the URI. The
|
|
50
61
|
// coordinate is the trailing three segments (runtime-agnostic); a pathname that
|
|
51
62
|
// isn't a numeric triple yields undefined (no coordinate on the wire).
|
|
63
|
+
// {§notifications-stream-event-on-channel-change}, {§notifications-stream-concluded}
|
|
52
64
|
const coordinateFromPathname = (pathname) => {
|
|
53
65
|
const seg = pathname.split("/").filter(Boolean);
|
|
54
66
|
if (seg.length < 3)
|
|
@@ -61,7 +73,8 @@ const coordinateFromPathname = (pathname) => {
|
|
|
61
73
|
// {§stream-owner-scoped} — resolve a stream statement's authority to the owning worker and hand
|
|
62
74
|
// back the statement authority-stripped (the storage path is the bare loop coordinate; the owner
|
|
63
75
|
// rides the owner_id column, never the pathname). Empty authority = the CALLING worker — your own
|
|
64
|
-
// streams need no qualifier, so a fan-out sibling's identical coordinate can never be yours
|
|
76
|
+
// streams need no qualifier, so a fan-out sibling's identical coordinate can never be yours
|
|
77
|
+
// ({§stream-owner-scoped}).
|
|
65
78
|
// A named authority = that worker's streams, ancestry-gated (reader must be the owner or an
|
|
66
79
|
// ancestor); an unknown name or unpermitted reader resolves null → the face 404s, no existence leak.
|
|
67
80
|
export const resolveStreamStatement = async (statement, ctx) => {
|
|
@@ -80,38 +93,36 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
80
93
|
channels: { stdout: "text/stream", stderr: "text/stream" },
|
|
81
94
|
defaultChannel: "stdout",
|
|
82
95
|
category: "data",
|
|
83
|
-
scope: "workspace",
|
|
84
96
|
writableBy: ["model", "client"],
|
|
85
97
|
volatile: true,
|
|
86
98
|
modelVisible: true,
|
|
87
99
|
example: "<<EXEC[sqlite]:SELECT 22.0 / 7.0:EXEC",
|
|
88
|
-
documentation: "Runs a command in a runtime — `<<EXEC[runtime](
|
|
100
|
+
documentation: "Runs a command in a runtime — `<<EXEC[runtime](target):command:EXEC` — output streams into the worker's `<runtime>:///<loop>/<turn>/<seq>` entry on the runtime's own channels (a subprocess → stdout/stderr; a computational runtime like sqlite/jq → a JSON `results` channel). A host-effecting command proposes for review before it runs; a read-only/pure one runs ungated. Either way you never fetch the output: the engine surfaces each turn's new stream bytes to you automatically — folded while the command runs, opened when it finishes.",
|
|
89
101
|
flags: {
|
|
90
102
|
excludedInAsk: true,
|
|
91
103
|
},
|
|
92
104
|
};
|
|
93
|
-
// The web-fetch the entry sink calls on content:null (§exec-entry-sink
|
|
94
|
-
//
|
|
95
|
-
// fallback renders; injectable so tests substitute the network.
|
|
105
|
+
// The web-fetch the entry sink calls on content:null ({§exec-entry-sink}).
|
|
106
|
+
// Default = schemes-http's checked WebFetcher; injectable for tests.
|
|
96
107
|
#fetchWeb;
|
|
97
|
-
#closeWebFetcher;
|
|
98
108
|
constructor(fetchWeb) {
|
|
99
109
|
super();
|
|
100
|
-
const webFetcher = new WebFetcher();
|
|
101
110
|
if (fetchWeb === undefined) {
|
|
111
|
+
const webFetcher = new WebFetcher();
|
|
102
112
|
this.#fetchWeb = (url, opts) => webFetcher.fetch(url, opts);
|
|
103
|
-
this.#closeWebFetcher = () => webFetcher.close();
|
|
104
113
|
}
|
|
105
114
|
else {
|
|
106
115
|
this.#fetchWeb = fetchWeb;
|
|
107
|
-
this.#closeWebFetcher = async () => { };
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
#activeAborts = new Map();
|
|
111
119
|
#activeSpawns = new Map();
|
|
112
120
|
async idle() {
|
|
113
121
|
await Promise.allSettled([...this.#activeSpawns.values()]);
|
|
114
|
-
|
|
122
|
+
}
|
|
123
|
+
// {§handler-lifecycle} — idle is the streaming drain barrier.
|
|
124
|
+
async close() {
|
|
125
|
+
await this.idle();
|
|
115
126
|
}
|
|
116
127
|
// Whether the worker has an in-flight spawn (a background exec). The daemon
|
|
117
128
|
// reads this only for loop.cancel's cancelled=true/false answer — the
|
|
@@ -123,23 +134,39 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
123
134
|
return true;
|
|
124
135
|
return false;
|
|
125
136
|
}
|
|
126
|
-
// §
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
137
|
+
// {§worker-optimistic-settlement} — wait on exactly the spawns initiated by
|
|
138
|
+
// this turn, ending immediately when all settle and never extending the
|
|
139
|
+
// opportunity to streams inherited from an earlier turn.
|
|
140
|
+
async settleTurnSpawns(workerId, turnId, timeoutMs, signal) {
|
|
141
|
+
signal?.throwIfAborted();
|
|
142
|
+
const pending = [...this.#activeAborts.entries()]
|
|
143
|
+
.filter(([, active]) => active.workerId === workerId && active.turnId === turnId)
|
|
144
|
+
.map(([subscriptionId]) => {
|
|
145
|
+
const spawn = this.#activeSpawns.get(subscriptionId);
|
|
146
|
+
if (spawn === undefined) {
|
|
147
|
+
throw new Error(`Active EXEC subscription ${subscriptionId} has no spawn settlement promise.`);
|
|
148
|
+
}
|
|
149
|
+
return spawn;
|
|
150
|
+
});
|
|
151
|
+
if (pending.length === 0)
|
|
152
|
+
return true;
|
|
153
|
+
if (timeoutMs === 0)
|
|
154
|
+
return false;
|
|
155
|
+
return await Promise.race([
|
|
156
|
+
Promise.allSettled(pending).then(() => true),
|
|
157
|
+
delay(timeoutMs, false, { signal, ref: false }),
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
// {§exec-hold-until-concluded} — match active spawns against the
|
|
161
|
+
// operator's runtime or runtime:effect hold selectors.
|
|
130
162
|
hasActiveHoldSpawns(workerId, holdSet) {
|
|
131
163
|
for (const { workerId: r, runtime, effect } of this.#activeAborts.values())
|
|
132
164
|
if (r === workerId && (holdSet.has(runtime) || holdSet.has(`${runtime}:${effect}`)))
|
|
133
165
|
return true;
|
|
134
166
|
return false;
|
|
135
167
|
}
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// the model's signal — KILL[code] → exactly that signal once (KILL[9] = SIGKILL), a
|
|
139
|
-
// bare KILL → the executor's SIGHUP default. The model owns escalation, so there
|
|
140
|
-
// is no auto-escalation here. The full #203 status matrix: 200 killed (in-flight) · 410
|
|
141
|
-
// killed-earlier (a prior abort closed the stream 499) · 304 already-exited (closed
|
|
142
|
-
// with any other terminal status) · 404 unknown (no subscription for that coordinate).
|
|
168
|
+
// {§stream-control} — active KILL routes through the live controller;
|
|
169
|
+
// terminal and missing outcomes resolve from the durable subscription.
|
|
143
170
|
async kill(pathname, signal, ctx, scheme = "exec") {
|
|
144
171
|
const core = this.coreContext(ctx);
|
|
145
172
|
for (const entry of this.#activeAborts.values()) {
|
|
@@ -151,32 +178,44 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
151
178
|
// Not running — settle the outcome from the closed subscription's status, scoped to the
|
|
152
179
|
// caller's own subscription (coordinates duplicate across workers, {§stream-owner-scoped}).
|
|
153
180
|
// The error answers in the model's OWN runtime-tag address (sh:///…), never the retired
|
|
154
|
-
// internal `exec`
|
|
155
|
-
const terminal = await ChannelWrite.execTerminalStatus(core.db, {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
181
|
+
// internal `exec` machinery ({§fs-answer-in-canon}) — the model KILLs what it addressed.
|
|
182
|
+
const terminal = await ChannelWrite.execTerminalStatus(core.db, {
|
|
183
|
+
workspaceId: core.workspaceId,
|
|
184
|
+
workerId: core.workerId,
|
|
185
|
+
scheme,
|
|
186
|
+
pathname,
|
|
187
|
+
});
|
|
188
|
+
const target = renderAddress(scheme, pathname);
|
|
189
|
+
if (terminal === null) {
|
|
190
|
+
return Results.failure("scheme:exec", "stream-not-found", 404, `No stream exists at ${target}.`, {}, { target });
|
|
191
|
+
}
|
|
192
|
+
if (terminal === 499) {
|
|
193
|
+
return Results.failure("scheme:exec", "stream-already-killed", 410, `Stream ${target} was already killed.`, {}, {
|
|
194
|
+
target,
|
|
195
|
+
retryable: false,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return Results.failure("scheme:exec", "stream-already-terminal", 409, `${target} already concluded with status ${terminal}.`, {}, {
|
|
199
|
+
target,
|
|
200
|
+
terminalStatus: terminal,
|
|
201
|
+
retryable: false,
|
|
202
|
+
});
|
|
161
203
|
}
|
|
162
204
|
// EXEC op handler — the actual model-facing entry point per plurnk.md.
|
|
163
|
-
// `<<EXEC[runtime](
|
|
164
|
-
// signal=runtime, target=
|
|
205
|
+
// `<<EXEC[runtime](target):command:EXEC` →
|
|
206
|
+
// signal=runtime, target=optional source/program/cwd, body=command.
|
|
165
207
|
//
|
|
166
208
|
// Proposes (status=202) with attrs={runtime, cwd, command, pathname}.
|
|
167
209
|
// applyResolution spawns the subprocess; output streams into the
|
|
168
210
|
// coordinate-stamped <runtime>:///<pathname> entry's stdout/stderr channels
|
|
169
|
-
// (e.g. sh:///1/1/2, §exec
|
|
211
|
+
// (e.g. sh:///1/1/2, {§exec}). The model READs that entry on a subsequent turn.
|
|
170
212
|
async exec(statement, ctx) {
|
|
171
213
|
const core = this.coreContext(ctx);
|
|
172
214
|
let command = statement.body ?? "";
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
// the program/data-source the executor runs (body = stdin). A stat-miss falls to the file arm so the
|
|
178
|
-
// runtime reports its own not-found, never a dispatch 400. cwd otherwise = the workspace workspace
|
|
179
|
-
// (project_root) — the directory File writes to, and what a data-source target resolves against.
|
|
215
|
+
// Non-file data sources resolve after acceptance. {§exec-target-routing}
|
|
216
|
+
const schemeSource = schemeSourceOf(statement.target);
|
|
217
|
+
// A local directory overrides cwd; every other local path remains the
|
|
218
|
+
// executor target. {§exec-target-routing}
|
|
180
219
|
const workspaceRow = await core.db.envelope_get_workspace.get({ id: core.workspaceId });
|
|
181
220
|
const projectRoot = workspaceRow?.project_root ?? null;
|
|
182
221
|
const localTarget = localPathFromTarget(statement.target);
|
|
@@ -190,66 +229,96 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
190
229
|
routedTarget = null;
|
|
191
230
|
}
|
|
192
231
|
}
|
|
193
|
-
catch {
|
|
232
|
+
catch (cause) {
|
|
233
|
+
if (cause?.code !== "ENOENT") {
|
|
234
|
+
console.error(`EXEC target classification failed for '${abs}':`, cause);
|
|
235
|
+
return Results.failure("scheme:exec", "target-classification-failed", 500, `EXEC target '${localTarget}' could not be inspected: ${ErrorDetail.preview(cause)}`, {}, {
|
|
236
|
+
target: localTarget,
|
|
237
|
+
stage: "target-classification",
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
194
241
|
}
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
242
|
+
// An empty body requires a file/program or scheme command source.
|
|
243
|
+
// {§exec-target-routing}
|
|
244
|
+
if (command.length === 0 && schemeSource === null && routedTarget === null) {
|
|
245
|
+
return Results.failure("scheme:exec", "command-required", 400, "EXEC requires a command body or an executable target.", {}, {
|
|
246
|
+
recovery: "Provide a command body or executable target.",
|
|
247
|
+
retryable: false,
|
|
248
|
+
});
|
|
200
249
|
}
|
|
201
250
|
const requested = typeof statement.signal === "string" ? statement.signal : "";
|
|
202
251
|
const runtime = requested === "" ? "sh" : requested; // empty signal = default shell
|
|
203
252
|
if (core.executors === undefined)
|
|
204
253
|
throw new Error("exec dispatched without an executor registry");
|
|
205
254
|
const workspaceExecs = (await WorkspaceSettings.read(core.db, core.workspaceId)).execs;
|
|
206
|
-
// §exec-registry-resolves — a non-empty tag selects exactly one registered executable
|
|
255
|
+
// {§exec-registry-resolves} — a non-empty tag selects exactly one registered executable
|
|
207
256
|
// tool. Unknown tags are not reinterpreted as shell command words: that would make the
|
|
208
257
|
// executed command differ from the authored body. Bare EXEC remains the default-shell form.
|
|
209
258
|
const resolved = core.executors.entry(runtime);
|
|
210
259
|
if (resolved === undefined) {
|
|
211
260
|
const available = core.executors.availableRuntimes()
|
|
212
261
|
.filter((tag) => workspaceExecs === null || Policy.isEnabled(tag, workspaceExecs));
|
|
213
|
-
return {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
262
|
+
return Results.failure("scheme:exec", "runtime-not-registered", 501, `Executable tool '${runtime}' is not registered in this workspace.`, {}, {
|
|
263
|
+
requestedRuntime: runtime,
|
|
264
|
+
availableRuntimes: available,
|
|
265
|
+
...(available.length > 0
|
|
266
|
+
? {
|
|
267
|
+
recovery: available.includes("sh")
|
|
268
|
+
? `Use bare EXEC for a shell command or select a registered executable tool: ${available.join(", ")}.`
|
|
269
|
+
: `Select a registered executable tool: ${available.join(", ")}.`,
|
|
270
|
+
}
|
|
271
|
+
: {}),
|
|
272
|
+
retryable: false,
|
|
273
|
+
});
|
|
217
274
|
}
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// runtime. Bare EXEC resolves to sh before this gate, so disabling sh also disables the default.
|
|
275
|
+
// {§operator-config-workspace-execs} — the workspace layer only narrows
|
|
276
|
+
// the registered set. Bare EXEC resolves to sh before the same gate.
|
|
221
277
|
if (workspaceExecs !== null && !Policy.isEnabled(runtime, workspaceExecs)) {
|
|
222
|
-
|
|
278
|
+
const available = core.executors.availableRuntimes()
|
|
279
|
+
.filter((tag) => Policy.isEnabled(tag, workspaceExecs));
|
|
280
|
+
return Results.failure("scheme:exec", "runtime-disabled", 501, `Executable tool '${runtime}' is disabled by workspace policy.`, {}, {
|
|
281
|
+
requestedRuntime: runtime,
|
|
282
|
+
availableRuntimes: available,
|
|
283
|
+
recovery: available.length > 0
|
|
284
|
+
? `Use an enabled executable tool: ${available.join(", ")}.`
|
|
285
|
+
: "Continue without executing a tool in this workspace.",
|
|
286
|
+
retryable: false,
|
|
287
|
+
});
|
|
223
288
|
}
|
|
224
289
|
if (!resolved.available) {
|
|
225
|
-
const why = resolved.detail === undefined ? "" : `: ${resolved.detail}`;
|
|
226
|
-
return
|
|
290
|
+
const why = resolved.detail === undefined ? "" : `: ${ErrorDetail.preview(resolved.detail)}`;
|
|
291
|
+
return Results.failure("scheme:exec", "runtime-unavailable", 501, `Executable tool '${runtime}' is unavailable${why}.`, {}, {
|
|
292
|
+
requestedRuntime: runtime,
|
|
293
|
+
retryable: false,
|
|
294
|
+
});
|
|
227
295
|
}
|
|
228
|
-
// The (target)
|
|
229
|
-
//
|
|
230
|
-
//
|
|
296
|
+
// The local (target) realization the executor receives — its data source
|
|
297
|
+
// or program. A directory routes to cwd and a scheme source is resolved
|
|
298
|
+
// only after acceptance, so either is null here.
|
|
231
299
|
const target = routedTarget;
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//
|
|
300
|
+
// Derive one effect from the canonical logical target before any policy
|
|
301
|
+
// consumes it, then preserve that fact with the invocation (#107,
|
|
302
|
+
// {§executor-effect}). Leaves never receive authored command text.
|
|
303
|
+
const effectTarget = effectTargetOf(statement.target, target, schemeSource, command);
|
|
304
|
+
const effect = resolved.executor.effect(effectTarget);
|
|
305
|
+
// cwd is the workspace project_root unless target routing selected a
|
|
306
|
+
// directory override. {§exec-target-routing}, {§executor-sinks}
|
|
238
307
|
// Pathname is assigned by Dispatcher.#writeLog as <runtime>/<loop_seq>/
|
|
239
308
|
// <turn_seq>/<sequence> (executor-domain + coordinate, e.g. sh/1/1/2).
|
|
240
309
|
// `pathname` is stamped into attrs at log-write time; applyResolution
|
|
241
310
|
// reads it back here.
|
|
242
311
|
// EXEC repurposes the `<L>` slot as `<timeout, poll>` (seconds): mark[0] caps the spawn's
|
|
243
|
-
// lifetime, mark[1] sets the hibernation poll-wake cadence (§exec-poll). N>0 → deadline (504);
|
|
312
|
+
// lifetime, mark[1] sets the hibernation poll-wake cadence ({§exec-poll}). N>0 → deadline (504);
|
|
244
313
|
// -1 / absent → unbounded (loop-life bounded); 0 → turn-scoped (reaped at the next pre-turn,
|
|
245
314
|
// never surviving into the subsequent turn).
|
|
246
315
|
const marks = statement.lineMarker?.marks;
|
|
247
316
|
const timeoutSec = typeof marks?.[0] === "number" && marks[0] > 0 ? Math.floor(marks[0]) : undefined;
|
|
248
317
|
const turnScoped = typeof marks?.[0] === "number" && marks[0] === 0;
|
|
249
|
-
const pollSec = typeof marks?.[1] === "number" && marks[1]
|
|
318
|
+
const pollSec = typeof marks?.[1] === "number" && marks[1] >= 0 ? Math.floor(marks[1]) : undefined;
|
|
250
319
|
const attrs = {
|
|
251
|
-
runtime, cwd, command, target, pathname: "",
|
|
252
|
-
...(
|
|
320
|
+
runtime, cwd, command, target, pathname: "", effect,
|
|
321
|
+
...(schemeSource !== null ? { schemeSource } : {}),
|
|
253
322
|
...(timeoutSec !== undefined ? { timeoutSec } : {}),
|
|
254
323
|
...(turnScoped ? { turnScoped: true } : {}),
|
|
255
324
|
...(pollSec !== undefined ? { pollSec } : {}),
|
|
@@ -257,7 +326,7 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
257
326
|
// Body shown to client during proposal review — `$ command` is the
|
|
258
327
|
// most-readable summary regardless of runtime.
|
|
259
328
|
const preview = runtime !== "" ? `[${runtime}] ${command}` : `$ ${command}`;
|
|
260
|
-
return { status: 202, body: preview, attrs }; // host runtime proposes with 202 — §exec-host-proposes
|
|
329
|
+
return { status: 202, body: preview, attrs }; // host runtime proposes with 202 — {§exec-host-proposes}
|
|
261
330
|
}
|
|
262
331
|
async applyResolution(args, ctx) {
|
|
263
332
|
const core = this.coreContext(ctx);
|
|
@@ -267,25 +336,42 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
267
336
|
const runtime = (typeof attrs.runtime === "string" && attrs.runtime !== "") ? attrs.runtime : "sh";
|
|
268
337
|
const cwd = (typeof attrs.cwd === "string" && attrs.cwd.length > 0) ? attrs.cwd : null;
|
|
269
338
|
let target = (typeof attrs.target === "string" && attrs.target.length > 0) ? attrs.target : null;
|
|
339
|
+
const effect = attrs.effect;
|
|
270
340
|
if (typeof pathname !== "string" || pathname.length === 0) {
|
|
271
|
-
|
|
341
|
+
throw new InvalidOperationResultError("The accepted EXEC proposal is missing its stream pathname.");
|
|
272
342
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
//
|
|
343
|
+
if (!EffectPolicy.isEffect(effect)) {
|
|
344
|
+
throw new InvalidOperationResultError("The accepted EXEC proposal is missing its canonical effect fact.");
|
|
345
|
+
}
|
|
346
|
+
// Resolve a scheme target after acceptance: empty body uses the content
|
|
347
|
+
// as the command; otherwise a temporary file becomes the data target.
|
|
348
|
+
// {§exec-target-routing}
|
|
277
349
|
let tempPath = null;
|
|
278
|
-
if (attrs.
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
350
|
+
if (attrs.schemeSource !== undefined) {
|
|
351
|
+
const sourceTarget = parsePath(attrs.schemeSource);
|
|
352
|
+
if (sourceTarget?.kind !== "url" || sourceTarget.scheme === null || sourceTarget.scheme === "file") {
|
|
353
|
+
throw new InvalidOperationResultError("The accepted EXEC proposal has an invalid scheme source address.");
|
|
354
|
+
}
|
|
355
|
+
const read = await this.readExecSource({
|
|
356
|
+
op: "READ",
|
|
357
|
+
suffix: "",
|
|
358
|
+
signal: null,
|
|
359
|
+
target: sourceTarget,
|
|
360
|
+
lineMarker: { marks: [1, -1] },
|
|
361
|
+
body: null,
|
|
362
|
+
position: { line: 0, column: 0 },
|
|
363
|
+
}, core);
|
|
364
|
+
if (read.status >= 400) {
|
|
365
|
+
return Results.assert({ ...read, outcome: "scheme_source_read_failed" });
|
|
366
|
+
}
|
|
367
|
+
const content = read.content;
|
|
368
|
+
if (typeof content !== "string") {
|
|
369
|
+
return Results.failure("scheme:exec", "source-content-unavailable", 422, `Scheme '${sourceTarget.scheme}' did not supply content for the EXEC source.`, { outcome: "scheme_source_content_unavailable" }, {
|
|
370
|
+
scheme: sourceTarget.scheme,
|
|
371
|
+
sourceStatus: read.status,
|
|
372
|
+
retryable: false,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
289
375
|
if (command.length === 0) {
|
|
290
376
|
command = content;
|
|
291
377
|
}
|
|
@@ -295,25 +381,26 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
295
381
|
target = tempPath;
|
|
296
382
|
}
|
|
297
383
|
}
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
// positional, transient exec per the owner ruling). Empty body with no target of any kind
|
|
301
|
-
// remains the contradiction.
|
|
384
|
+
// {§exec-target-routing} — a local file target is itself the program, so
|
|
385
|
+
// its body may be empty; no target and no command remains invalid.
|
|
302
386
|
if (command.length === 0 && target === null) {
|
|
303
|
-
|
|
387
|
+
if (attrs.schemeSource !== undefined) {
|
|
388
|
+
return Results.failure("scheme:exec", "source-command-empty", 422, "The EXEC source resolved to an empty command.", { outcome: "scheme_source_command_empty" }, { retryable: false });
|
|
389
|
+
}
|
|
390
|
+
throw new InvalidOperationResultError("The accepted EXEC proposal has neither a command nor a target.");
|
|
304
391
|
}
|
|
305
392
|
// Resolve the runtime's executor from the boot registry, then seed
|
|
306
|
-
// channels from its declared topology (
|
|
307
|
-
// executor declares, scheme honors). Each executor declares its own
|
|
393
|
+
// channels from its declared topology ({§executor-channels}). Each executor declares its own
|
|
308
394
|
// shape (subprocess → stdout/stderr; search → results; etc.).
|
|
309
|
-
if (core.executors === undefined)
|
|
310
|
-
|
|
395
|
+
if (core.executors === undefined) {
|
|
396
|
+
throw new InvalidOperationResultError("An accepted EXEC proposal has no executor registry.");
|
|
397
|
+
}
|
|
311
398
|
const resolved = core.executors.entry(runtime);
|
|
312
|
-
if (resolved === undefined)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
//
|
|
316
|
-
|
|
399
|
+
if (resolved === undefined) {
|
|
400
|
+
throw new InvalidOperationResultError(`The '${runtime}' executor disappeared after its EXEC proposal.`);
|
|
401
|
+
}
|
|
402
|
+
// {§executor-effect}, {§exec-hold-until-concluded}, #107: the admitted
|
|
403
|
+
// effect fact rides the hold predicate unchanged through application.
|
|
317
404
|
const seedChannels = {};
|
|
318
405
|
for (const [name, decl] of Object.entries(resolved.executor.channels)) {
|
|
319
406
|
seedChannels[name] = {
|
|
@@ -323,16 +410,23 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
323
410
|
};
|
|
324
411
|
}
|
|
325
412
|
const seed = { channels: seedChannels, tags: [] };
|
|
326
|
-
// §exec — the stream entry's scheme IS the runtime tag (sh/node), so it addresses by
|
|
413
|
+
// {§exec} — the stream entry's scheme IS the runtime tag (sh/node), so it addresses by
|
|
327
414
|
// tag authority (sh:///l/t/s). The engine registers each runtime tag → this handler.
|
|
328
415
|
const { entryId } = await EntryCrud.writeEntry(pathname, seed, core, runtime, core.workerId);
|
|
329
|
-
if (entryId === null)
|
|
330
|
-
return
|
|
416
|
+
if (entryId === null) {
|
|
417
|
+
return Results.failure("scheme:exec", "stream-entry-write-failed", 500, `The ${runtime} stream entry could not be created.`, {
|
|
418
|
+
outcome: "entry_write_failed",
|
|
419
|
+
}, {
|
|
420
|
+
runtime,
|
|
421
|
+
stage: "stream-creation",
|
|
422
|
+
retryable: false,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
331
425
|
const subscriptionId = await ChannelWrite.openSubscription(core.db, {
|
|
332
426
|
workerId: core.workerId, entryId, scheme: runtime,
|
|
333
427
|
handle: runtime !== "" ? `${runtime}: ${command}` : command,
|
|
334
|
-
pollSeconds: typeof attrs.pollSec === "number" ? attrs.pollSec : null, // §exec-poll — hibernation wake cadence
|
|
335
|
-
turnScoped: attrs.turnScoped === true, // §exec-poll — `<0>` reaped at the next pre-turn
|
|
428
|
+
pollSeconds: typeof attrs.pollSec === "number" ? attrs.pollSec : null, // {§exec-poll} — hibernation wake cadence
|
|
429
|
+
turnScoped: attrs.turnScoped === true, // {§exec-poll} — `<0>` reaped at the next pre-turn
|
|
336
430
|
});
|
|
337
431
|
const controller = new AbortController();
|
|
338
432
|
let unlink = () => { };
|
|
@@ -345,14 +439,14 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
345
439
|
// then re-check `aborted`: a listener added to an already-aborted signal never
|
|
346
440
|
// fires, so a check-then-attach order LOSES an abort that lands in the gap (R1's
|
|
347
441
|
// TOCTOU leak). Attach-then-check closes it; controller.abort is idempotent, so a
|
|
348
|
-
// doubled fire is harmless. §exec-timeout
|
|
442
|
+
// doubled fire is harmless. {§exec-timeout}
|
|
349
443
|
const onParentAbort = () => controller.abort(ExecAbort.teardownReason());
|
|
350
444
|
parent.addEventListener("abort", onParentAbort, { once: true });
|
|
351
445
|
unlink = () => parent.removeEventListener("abort", onParentAbort);
|
|
352
446
|
if (parent.aborted)
|
|
353
447
|
controller.abort(ExecAbort.teardownReason());
|
|
354
448
|
}
|
|
355
|
-
this.#activeAborts.set(subscriptionId, { workerId: core.workerId, pathname, runtime, effect, controller, unlink });
|
|
449
|
+
this.#activeAborts.set(subscriptionId, { workerId: core.workerId, turnId: core.turnId, pathname, runtime, effect, controller, unlink });
|
|
356
450
|
this.liveSubscriptions().register(subscriptionId, {
|
|
357
451
|
cancel: () => controller.abort(ExecAbort.teardownReason()),
|
|
358
452
|
});
|
|
@@ -362,18 +456,19 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
362
456
|
entryId, subscriptionId, signal: controller.signal, controller, tempPath,
|
|
363
457
|
timeoutSec: typeof attrs.timeoutSec === "number" ? attrs.timeoutSec : null,
|
|
364
458
|
});
|
|
365
|
-
// Every exec backgrounds + streams (§exec-stream): no same-turn receipt — the output
|
|
459
|
+
// Every exec backgrounds + streams ({§exec-stream}): no same-turn receipt — the output
|
|
366
460
|
// surfaces as the environment-observation injector's delta on the next turn (folded while
|
|
367
|
-
// it runs, opened when it finishes). Pure/read commands still auto-accept
|
|
368
|
-
//
|
|
461
|
+
// it runs, opened when it finishes). Pure/read commands still auto-accept
|
|
462
|
+
// from the preserved effect fact; they resolve a turn later, uniformly
|
|
463
|
+
// with host streams.
|
|
369
464
|
this.#activeSpawns.set(subscriptionId, tail);
|
|
370
465
|
return { status: 200, outcome: "started" };
|
|
371
466
|
}
|
|
372
467
|
// Bridge the executor's sink-style contract (write/setState/emit)
|
|
373
468
|
// onto plurnk-service's storage primitives (appendToChannel,
|
|
374
|
-
// setChannelState, ctx.
|
|
375
|
-
// executor
|
|
376
|
-
// grammar
|
|
469
|
+
// setChannelState, ctx.pushNotice). Under {§executor-results},
|
|
470
|
+
// executor Notices flow through the same engine path as
|
|
471
|
+
// grammar parse advisories: emit, buffer, next packet, and live notify.
|
|
377
472
|
//
|
|
378
473
|
// write() and setState() callbacks must run in emission order:
|
|
379
474
|
// appendToChannel reads channel state AFTER the append commits, so
|
|
@@ -383,7 +478,7 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
383
478
|
async #runExecutor(opts) {
|
|
384
479
|
const { executor, runtime, command, cwd, target, ctx, pathname, entryId, subscriptionId, signal, controller, timeoutSec, tempPath } = opts;
|
|
385
480
|
const db = ctx.db;
|
|
386
|
-
const coordinate = coordinateFromPathname(pathname);
|
|
481
|
+
const coordinate = coordinateFromPathname(pathname);
|
|
387
482
|
// grammar 0.74.20 EXEC `<T>` — kill the spawn after T seconds. unref'd so a pending timer never
|
|
388
483
|
// holds the process open; cleared in finally so a spawn that finishes first leaves no timer.
|
|
389
484
|
let timedOut = false;
|
|
@@ -395,217 +490,262 @@ export default class Exec extends CoreSchemeAdapterBase {
|
|
|
395
490
|
const enqueue = (op) => {
|
|
396
491
|
queue = queue.then(op, op);
|
|
397
492
|
};
|
|
398
|
-
let
|
|
399
|
-
|
|
493
|
+
let result = Results.failure("scheme:exec", "executor-did-not-conclude", 500, `The '${runtime}' executor did not produce a terminal result.`, {}, {
|
|
494
|
+
runtime,
|
|
495
|
+
stage: "execution",
|
|
496
|
+
retryable: false,
|
|
497
|
+
});
|
|
498
|
+
let exitLabel = "did not conclude";
|
|
400
499
|
let stdoutLength = 0;
|
|
401
500
|
let stderrLength = 0;
|
|
402
|
-
// §exec-entry-sink
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
// EDIT row in the reserved plurnk worker's log (the fs-fiction pattern, source = the calling
|
|
406
|
-
// run) — the existing env-delta ambience folds it into every worker's next packet. The row is a
|
|
407
|
-
// FULL fiction: tx carries the statement (body = the written content — the journal records
|
|
408
|
-
// the write, replay/fork-complete), rx carries the span (§edit-result-render, the whole
|
|
409
|
-
// content numbered — a wholesale write's span IS the content; no diff, which would be a
|
|
410
|
-
// pathological cost against a rewritten multi-MB page). Rendered folded by default, so no
|
|
411
|
-
// body rides a packet uninvited — the meta line carries the honest OPEN weight.
|
|
412
|
-
// Serialized: parallel entry() calls (allSettled) write in order; a rejection prunes that
|
|
413
|
-
// survivor executor-side without breaking the chain. Lazy narration context: one plurnk-run
|
|
414
|
-
// turn per spawn, not per entry.
|
|
501
|
+
// {§exec-entry-sink} {§env-delta-entry-materialization} — serialize
|
|
502
|
+
// executor entry() materializations and narrate them through the one
|
|
503
|
+
// reserved-worker turn owned by this spawn.
|
|
415
504
|
let entryChain = Promise.resolve();
|
|
416
505
|
let narration = null;
|
|
506
|
+
let callerSource;
|
|
507
|
+
const resolveCallerSource = async () => {
|
|
508
|
+
if (callerSource !== undefined)
|
|
509
|
+
return callerSource;
|
|
510
|
+
const caller = await db.worker_name_by_id.get({ worker_id: ctx.workerId });
|
|
511
|
+
if (caller === undefined)
|
|
512
|
+
throw new Error(`entry(): calling worker ${ctx.workerId} does not exist`);
|
|
513
|
+
callerSource = WorkerControlAddress.render(caller.name);
|
|
514
|
+
return callerSource;
|
|
515
|
+
};
|
|
417
516
|
const entrySink = (path, content, opts) => {
|
|
418
517
|
const parsed = parsePath(path);
|
|
419
|
-
if (parsed === null || parsed.kind !== "url"
|
|
518
|
+
if (parsed === null || parsed.kind !== "url")
|
|
420
519
|
return Promise.reject(new Error(`entry(): '${path.slice(0, 80)}' is not a URL`));
|
|
421
520
|
if (content !== null && opts.mimetype === undefined)
|
|
422
521
|
return Promise.reject(new Error("entry(): mimetype is required when content is provided"));
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
522
|
+
const address = NetworkAddress.supports(parsed.scheme) ? NetworkAddress.from(parsed) : null;
|
|
523
|
+
if (address?.hasCredentials === true)
|
|
524
|
+
return Promise.reject(new Error("entry(): network URL userinfo is not allowed"));
|
|
525
|
+
const fetchAddress = address !== null && (address.scheme === "http" || address.scheme === "https")
|
|
526
|
+
? address
|
|
527
|
+
: null;
|
|
528
|
+
const pathname = address?.pathname ?? entryPathnameOf(parsed);
|
|
529
|
+
const scheme = address?.scheme ?? parsed.scheme;
|
|
530
|
+
// {§exec-entry-sink}/{§web-search-retrieval} — start content:null
|
|
531
|
+
// acquisition before the write chain so fetches run in parallel;
|
|
532
|
+
// only durable entry writes serialize. A null result rejects the sink.
|
|
533
|
+
let materialized;
|
|
534
|
+
if (content === null) {
|
|
535
|
+
if (fetchAddress === null)
|
|
536
|
+
return Promise.reject(new Error("entry(): content:null requires an http(s):// URL"));
|
|
537
|
+
materialized = this.#fetchWeb(fetchAddress.url, { signal });
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
materialized = Promise.resolve({
|
|
541
|
+
url: fetchAddress?.url ?? "http://localhost",
|
|
542
|
+
body: content,
|
|
543
|
+
mimetype: opts.mimetype,
|
|
544
|
+
allowTavily: false,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
432
547
|
const op = async () => {
|
|
433
548
|
const fetched = await materialized;
|
|
434
549
|
if (fetched === null)
|
|
435
550
|
throw new Error(`entry(): '${path.slice(0, 80)}' is dead`);
|
|
436
|
-
let
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// The web-fetch entry point: a fetched html page stores the handler's readable
|
|
444
|
-
// projection as the decisive `body` (text/markdown — what READ serves, FIND matches,
|
|
445
|
-
// FTS indexes, every weight reports) with the raw page under `html` (xpath + archive).
|
|
446
|
-
// Scoped HERE, not writeEntry: only auto-fetched web content projects; authored files
|
|
447
|
-
// stay verbatim (a `<user email=…>` roster's attribute data must survive a default READ).
|
|
448
|
-
let channels = { body: { content: body, mimetype } };
|
|
449
|
-
if (fetched.header !== undefined)
|
|
450
|
-
channels.header = { content: fetched.header, mimetype: "text/plain" };
|
|
451
|
-
let decisive = body;
|
|
452
|
-
if (mimetype === "text/html") {
|
|
453
|
-
if (ctx.mimetypes === undefined)
|
|
454
|
-
throw new Error("entry(): HTML materialization requires the mimetype registry");
|
|
455
|
-
let projected = (await ctx.mimetypes.process({ content: body, hint: "text/html" }, { channels: ["content"] })).content;
|
|
456
|
-
if ((typeof projected !== "string" || projected.length === 0) && fetched.render !== undefined) {
|
|
457
|
-
const rendered = await fetched.render();
|
|
458
|
-
if (rendered !== null) {
|
|
459
|
-
body = rendered.body;
|
|
460
|
-
mimetype = rendered.mimetype;
|
|
461
|
-
projected = (await ctx.mimetypes.process({ content: body, hint: "text/html" }, { channels: ["content"] })).content;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
if (typeof projected !== "string" || projected.length === 0) {
|
|
465
|
-
throw new Error(`entry(): '${path.slice(0, 80)}' has no readable HTML projection`);
|
|
551
|
+
let web;
|
|
552
|
+
try {
|
|
553
|
+
web = await WebFetcher.materialize(fetched, new DbProjectionCaps(ctx));
|
|
554
|
+
}
|
|
555
|
+
catch (error) {
|
|
556
|
+
if (!signal.aborted && error instanceof WebMaterializationError) {
|
|
557
|
+
console.error("entry() web materialization failed", { path, error });
|
|
466
558
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
if (web === null)
|
|
562
|
+
throw new Error(`entry(): '${path.slice(0, 80)}' has no readable projection`);
|
|
563
|
+
if (web.body === undefined) {
|
|
564
|
+
throw new Error(web.bodyOutcome.failure?.detail
|
|
565
|
+
?? `entry(): '${path.slice(0, 80)}' produced no readable body`);
|
|
473
566
|
}
|
|
474
|
-
const
|
|
567
|
+
const prior = await EntryCrud.readEntry(pathname, ctx, scheme);
|
|
568
|
+
const tags = [...new Set([...(prior.entry?.tags ?? []), ...opts.tags])];
|
|
569
|
+
// {§exec-entry-sink}/{§html-materialization} The shared
|
|
570
|
+
// materializer owns the decisive projection and its provenance.
|
|
571
|
+
const channels = WebFetcher.materializedChannels(web, content === null && fetchAddress !== null
|
|
572
|
+
? { url: fetchAddress.url, method: "GET" }
|
|
573
|
+
: undefined);
|
|
574
|
+
const decisive = web.body.content;
|
|
575
|
+
const source = web.html?.content ?? decisive;
|
|
576
|
+
const causalSource = await resolveCallerSource();
|
|
577
|
+
const written = Results.assert(await EntryCrud.writeEntry(pathname, { channels, tags }, ctx, scheme));
|
|
475
578
|
if (narration === null) {
|
|
476
|
-
const
|
|
579
|
+
const worker = await db.envelope_get_worker_by_name.get({ workspace_id: ctx.workspaceId, name: "plurnk" })
|
|
477
580
|
?? await db.envelope_insert_worker.get({ workspace_id: ctx.workspaceId, name: "plurnk", origin: "plurnk" });
|
|
478
|
-
if (
|
|
581
|
+
if (worker === undefined)
|
|
479
582
|
throw new Error("entry(): plurnk worker resolution returned no row");
|
|
480
|
-
const loop = await db.envelope_insert_client_loop.get({ worker_id:
|
|
583
|
+
const loop = await db.envelope_insert_client_loop.get({ worker_id: worker.id });
|
|
481
584
|
if (loop === undefined)
|
|
482
585
|
throw new Error("entry(): loop insert returned no row");
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
586
|
+
const turn = await JournalTurn.insert(db, loop.id);
|
|
587
|
+
narration = {
|
|
588
|
+
workerId: worker.id,
|
|
589
|
+
loopId: loop.id,
|
|
590
|
+
loopSeq: loop.sequence,
|
|
591
|
+
turnId: turn.id,
|
|
592
|
+
turnSeq: turn.sequence,
|
|
593
|
+
seq: 1,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
const sequence = narration.seq++;
|
|
597
|
+
if (written.problem !== undefined) {
|
|
598
|
+
Results.attachInstance(written, `log:///${narration.loopSeq}/${narration.turnSeq}/${sequence}/EDIT`);
|
|
488
599
|
}
|
|
489
600
|
await db.engine_insert_log_entry.get({
|
|
490
|
-
worker_id: narration.workerId, loop_id: narration.loopId, turn_id: narration.turnId, sequence
|
|
601
|
+
worker_id: narration.workerId, loop_id: narration.loopId, turn_id: narration.turnId, sequence,
|
|
491
602
|
// signal carries the tags — the SAME slot a model's EDIT[tags] uses, so the
|
|
492
603
|
// ambient row renders its tags natively everywhere (packet meta line, digest).
|
|
493
|
-
origin: "plurnk", source:
|
|
494
|
-
scheme
|
|
495
|
-
pathname,
|
|
496
|
-
tx: JSON.stringify({ op: "EDIT", body }), mimetype_tx: "application/json",
|
|
497
|
-
rx: JSON.stringify(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
604
|
+
origin: "plurnk", source: causalSource, op: "EDIT", suffix: "", signal: JSON.stringify(tags),
|
|
605
|
+
scheme, username: null, password: null, hostname: null, port: null,
|
|
606
|
+
pathname, query: null, fragment: null, lineMarker: null,
|
|
607
|
+
tx: JSON.stringify({ op: "EDIT", body: source }), mimetype_tx: "application/json",
|
|
608
|
+
rx: JSON.stringify(written.problem === undefined
|
|
609
|
+
? {
|
|
610
|
+
...written,
|
|
611
|
+
tags,
|
|
612
|
+
span: decisive.split("\n").map((l, n) => `${n + 1}:${l}`).join("\n"),
|
|
613
|
+
}
|
|
614
|
+
: written),
|
|
615
|
+
mimetype_rx: "application/json",
|
|
501
616
|
status_rx: written.status, tokens: ctx.tokenize?.(decisive) ?? 0, state: "resolved", outcome: null,
|
|
502
617
|
// Durable provenance for clients/forensics. This is machine
|
|
503
618
|
// ambience, not a human/model action waterfall item.
|
|
504
619
|
attrs: JSON.stringify({ tags, kind: "entry_materialized" }),
|
|
505
620
|
});
|
|
506
|
-
|
|
621
|
+
if (written.problem !== undefined)
|
|
622
|
+
throw new OperationFailureError(written);
|
|
623
|
+
return renderAddress(scheme, pathname);
|
|
507
624
|
};
|
|
508
625
|
const run = entryChain.then(op, op);
|
|
509
626
|
entryChain = run.then(() => undefined, () => undefined);
|
|
510
627
|
return run;
|
|
511
628
|
};
|
|
512
629
|
try {
|
|
513
|
-
let result = null;
|
|
514
630
|
try {
|
|
515
|
-
|
|
631
|
+
const reported = await executor.run({
|
|
516
632
|
runtime, command, cwd, target, signal,
|
|
517
633
|
entry: entrySink,
|
|
518
|
-
env: ExecEnv.scoped(), // SPEC §exec {§exec-env-scoped} — never plurnk's own secrets
|
|
519
|
-
write: (channel, chunk) => enqueue(() => ChannelWrite.appendToChannel(db, {
|
|
520
|
-
entryId, channel, chunk, notify: ctx.streamEventNotify, coordinate,
|
|
634
|
+
env: ExecEnv.scoped(), // SPEC {§exec} {§exec-env-scoped} — never plurnk's own secrets
|
|
635
|
+
write: (channel, chunk, mimetype) => enqueue(() => ChannelWrite.appendToChannel(db, {
|
|
636
|
+
entryId, channel, chunk, mimetype, notify: ctx.streamEventNotify, coordinate,
|
|
521
637
|
})),
|
|
522
638
|
setState: (channel, state) => enqueue(() => ChannelWrite.setChannelState(db, {
|
|
523
639
|
entryId, channel, state, notify: ctx.streamEventNotify, coordinate,
|
|
524
640
|
})),
|
|
525
|
-
emit: (event) =>
|
|
526
|
-
// The executor plugin's TelemetryEvent predates grammar's required `level`;
|
|
527
|
-
// inject a default (forwarding the producer's own severity when it supplies one). #276
|
|
528
|
-
const level = event.level ?? "info";
|
|
529
|
-
ctx.pushTelemetry?.({ ...event, level });
|
|
530
|
-
},
|
|
641
|
+
emit: (event) => ctx.pushNotice?.(event),
|
|
531
642
|
});
|
|
532
643
|
// Drain the queue so the subscription doesn't close before
|
|
533
644
|
// final chunk events / state transitions have committed.
|
|
534
645
|
await queue;
|
|
646
|
+
try {
|
|
647
|
+
result = Results.assert(reported);
|
|
648
|
+
}
|
|
649
|
+
catch (cause) {
|
|
650
|
+
console.error(`Executor '${runtime}' returned an invalid operation result:`, cause);
|
|
651
|
+
result = Results.failure("scheme:exec", "executor-invalid-result", 500, `The '${runtime}' executor returned an invalid operation result.`, {}, {
|
|
652
|
+
runtime,
|
|
653
|
+
stage: "result-validation",
|
|
654
|
+
retryable: false,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
535
657
|
}
|
|
536
658
|
catch (cause) {
|
|
537
659
|
// A rejecting driver must still CONCLUDE its stream — uncaught, the subscription sat
|
|
538
660
|
// open forever and the floating spawn promise was an unhandled rejection.
|
|
539
|
-
|
|
661
|
+
console.error(`Executor '${runtime}' threw outside its operation result contract:`, cause);
|
|
662
|
+
result = Results.failure("scheme:exec", "executor-threw", 500, `The '${runtime}' executor failed outside its operation result contract.`, {}, {
|
|
663
|
+
runtime,
|
|
664
|
+
stage: "execution",
|
|
665
|
+
retryable: false,
|
|
666
|
+
});
|
|
540
667
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
else
|
|
555
|
-
exitLabel = closeStatus === 504 ? `timed out after ${timeoutSec}s`
|
|
556
|
-
: closeStatus === 499 ? "aborted"
|
|
557
|
-
: closeStatus === 500 && exitCode === -1 ? "spawn_failed"
|
|
558
|
-
: `exit ${exitCode}`;
|
|
668
|
+
const exitCode = typeof result.exitCode === "number" ? result.exitCode : null;
|
|
669
|
+
// A timeout aborts the spawn → the executor reports 499; replace the
|
|
670
|
+
// complete result so status and Problem remain one valid truth.
|
|
671
|
+
if (timedOut) {
|
|
672
|
+
result = Results.failure("scheme:exec", "execution-timeout", 504, `Execution of '${runtime}' exceeded its ${timeoutSec}-second deadline.`, exitCode === null ? {} : { exitCode }, {
|
|
673
|
+
runtime,
|
|
674
|
+
timeoutSeconds: timeoutSec,
|
|
675
|
+
stage: "execution",
|
|
676
|
+
retryable: false,
|
|
677
|
+
});
|
|
678
|
+
// The service's own abort knowledge outranks a driver's claim: a
|
|
679
|
+
// spawn we reaped did not succeed, whatever it resolved under abort.
|
|
559
680
|
}
|
|
560
|
-
|
|
681
|
+
else if (signal.aborted && result.status < 400) {
|
|
682
|
+
result = Results.failure("scheme:exec", "execution-cancelled", 499, `Execution of '${runtime}' was cancelled by the service.`, exitCode === null ? {} : { exitCode }, {
|
|
683
|
+
runtime,
|
|
684
|
+
stage: "execution",
|
|
685
|
+
retryable: false,
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
exitLabel = timedOut
|
|
689
|
+
? `timed out after ${timeoutSec}s`
|
|
690
|
+
: result.status === 499
|
|
691
|
+
? "aborted"
|
|
692
|
+
: exitCode !== null
|
|
693
|
+
? `exit ${exitCode}`
|
|
694
|
+
: result.problem?.title.toLowerCase() ?? "completed";
|
|
695
|
+
await ChannelWrite.closeSubscription(db, { subscriptionId, result });
|
|
561
696
|
const stdoutMeta = await db.channel_meta.get({ entry_id: entryId, channel: "stdout" });
|
|
562
697
|
const stderrMeta = await db.channel_meta.get({ entry_id: entryId, channel: "stderr" });
|
|
563
698
|
stdoutLength = stdoutMeta?.contentLength ?? 0;
|
|
564
699
|
stderrLength = stderrMeta?.contentLength ?? 0;
|
|
565
700
|
}
|
|
566
701
|
finally {
|
|
567
|
-
// §exec-entry-sink —
|
|
568
|
-
// COMPLETE quiescence barrier: a consumer woken below, or a teardown awaiting idle(), must
|
|
569
|
-
// not race an in-flight entry() write into a closing db (#432 teardown race). Each op is
|
|
570
|
-
// enqueued as `.then(op, op)`, so entryChain never rejects — awaiting it in finally is safe.
|
|
702
|
+
// {§exec-entry-sink} — the spawn tail owns every serialized entry/narration write.
|
|
571
703
|
await entryChain;
|
|
572
704
|
if (timeoutTimer !== null)
|
|
573
705
|
clearTimeout(timeoutTimer); // a finished spawn leaves no pending timer
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
if (tempPath !== null)
|
|
577
|
-
await unlink(tempPath).catch(() => {
|
|
706
|
+
// {§exec-source-temporary} — cleanup cannot rewrite the settled
|
|
707
|
+
// executor result, but an exceptional failure remains observable.
|
|
708
|
+
if (tempPath !== null) {
|
|
709
|
+
await unlink(tempPath).catch((cause) => {
|
|
710
|
+
console.error(`EXEC source temporary cleanup failed for '${tempPath}':`, cause);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
578
713
|
this.#activeAborts.get(subscriptionId)?.unlink();
|
|
579
714
|
this.#activeAborts.delete(subscriptionId);
|
|
580
715
|
this.liveSubscriptions().unregister(subscriptionId);
|
|
581
716
|
this.#activeSpawns.delete(subscriptionId);
|
|
582
|
-
// Every worker backgrounds now (§exec-stream) — wake a parked loop on completion so the
|
|
583
|
-
//
|
|
717
|
+
// Every worker backgrounds now ({§exec-stream}) — wake a parked loop on completion so the
|
|
718
|
+
// worker resumes to the turn where the stream's terminal delta surfaces.
|
|
584
719
|
if (ctx.wakeWorkerNotify !== undefined) {
|
|
585
720
|
ctx.wakeWorkerNotify({
|
|
586
721
|
workspaceId: ctx.workspaceId, workerId: ctx.workerId,
|
|
587
|
-
entryId, target: `${runtime}://${pathname}`, subscriptionId,
|
|
722
|
+
entryOwnerId: ctx.workerId, entryId, target: `${runtime}://${pathname}`, subscriptionId, result,
|
|
588
723
|
scheme: runtime,
|
|
589
724
|
summary: `${runtime}://${pathname} completed (${exitLabel}); stdout=${stdoutLength} bytes, stderr=${stderrLength} bytes`,
|
|
590
725
|
...coordinate,
|
|
591
726
|
});
|
|
592
727
|
}
|
|
593
728
|
}
|
|
594
|
-
return
|
|
729
|
+
return result;
|
|
595
730
|
}
|
|
596
|
-
async
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
731
|
+
async resolveEntryAddress(target, ctx) {
|
|
732
|
+
if (target.kind !== "url")
|
|
733
|
+
return null;
|
|
734
|
+
const ownerId = await Owner.resolveStreamOwner(target.hostname, this.coreContext(ctx));
|
|
735
|
+
return ownerId === null
|
|
736
|
+
? Results.failure("scheme:exec", "stream-not-found", 404, "No visible stream exists at the requested address.")
|
|
737
|
+
: { pathname: target.pathname, ownerId };
|
|
602
738
|
}
|
|
603
739
|
async find(statement, ctx) {
|
|
604
740
|
const core = this.coreContext(ctx);
|
|
605
741
|
const owner = await resolveStreamStatement(statement, core);
|
|
606
|
-
if (owner === null)
|
|
607
|
-
return
|
|
608
|
-
|
|
742
|
+
if (owner === null) {
|
|
743
|
+
return Results.failure("scheme:exec", "stream-not-found", 404, "No visible stream exists at the requested address.", {
|
|
744
|
+
content: null, mimetype: null, results: [], itemsTokenTotal: 0, returnedItemsTokenTotal: 0,
|
|
745
|
+
matchingPathCount: 0, matchLocationCount: 0,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return EntryFind.findWorkspaceEntries(owner.statement, core, Exec.manifest, { ownerId: owner.ownerId });
|
|
609
749
|
}
|
|
610
750
|
async readEntry(pathname, ctx) {
|
|
611
751
|
const core = this.coreContext(ctx);
|