@jr2/orchestrator 0.3.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/bin/server.ts +7 -4
- package/console/canvas.ts +27 -14
- package/console/components/app.ts +12 -1
- package/console/components/fleet.ts +52 -6
- package/console/components/machine-pane.ts +128 -2
- package/console/components/nav.ts +1 -1
- package/console/components/schema-form.ts +12 -4
- package/console/json-field.ts +23 -0
- package/console/main.ts +122 -39
- package/console/store.ts +321 -21
- package/console/style.css +111 -0
- package/docs/agents-block.md +5 -0
- package/docs/concepts/agents-and-turns.md +442 -0
- package/docs/concepts/architecture.md +230 -0
- package/docs/concepts/charts.md +143 -0
- package/docs/concepts/composition.md +357 -0
- package/docs/concepts/converge.md +254 -0
- package/docs/concepts/durable-runs.md +189 -0
- package/docs/concepts/effects.md +603 -0
- package/docs/concepts/gates.md +208 -0
- package/docs/concepts/machines-and-workflows.md +289 -0
- package/docs/concepts/pools-and-sources.md +230 -0
- package/docs/concepts/secrets-and-trust.md +222 -0
- package/docs/concepts/the-menu.md +378 -0
- package/docs/concepts/workspaces.md +678 -0
- package/docs/get-started/a-model-decides.md +563 -0
- package/docs/get-started/an-agent-in-a-sandbox.md +381 -0
- package/docs/get-started/connect-a-model.md +248 -0
- package/docs/get-started/create-an-instance.md +194 -0
- package/docs/get-started/have-your-assistant-write-a-machine.md +922 -0
- package/docs/get-started/install.md +126 -0
- package/docs/guides/add-tools-to-the-sandbox.md +295 -0
- package/docs/guides/answer-a-gate.md +304 -0
- package/docs/guides/bind-open-parts.md +223 -0
- package/docs/guides/cancel-a-run.md +146 -0
- package/docs/guides/clean-up.md +206 -0
- package/docs/guides/fix-a-failed-converge.md +361 -0
- package/docs/guides/give-the-cluster-git-credentials.md +222 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
- package/docs/guides/let-an-agent-write-a-chart.md +226 -0
- package/docs/guides/look-inside-a-sandbox.md +174 -0
- package/docs/guides/mirror-the-kit-images.md +176 -0
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
- package/docs/guides/open-the-console.md +91 -0
- package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
- package/docs/guides/push-and-sign-a-branch.md +486 -0
- package/docs/guides/run-a-chart-by-name.md +270 -0
- package/docs/guides/run-a-command-or-call-a-url.md +544 -0
- package/docs/guides/run-one-worker-per-item.md +321 -0
- package/docs/guides/set-an-agents-access-and-dials.md +375 -0
- package/docs/guides/size-a-workspace.md +204 -0
- package/docs/guides/start-a-run-from-a-run.md +237 -0
- package/docs/guides/start-a-run.md +207 -0
- package/docs/guides/upgrade-the-kit-version.md +240 -0
- package/docs/guides/use-a-hosted-provider.md +208 -0
- package/docs/guides/use-a-local-model.md +313 -0
- package/docs/guides/watch-a-run.md +326 -0
- package/docs/guides/write-a-workflow.md +318 -0
- package/docs/index.md +44 -0
- package/docs/llms.txt +216 -0
- package/docs/reference/authoring-surface/agent.md +128 -0
- package/docs/reference/authoring-surface/answer.md +140 -0
- package/docs/reference/authoring-surface/arg.md +60 -0
- package/docs/reference/authoring-surface/basic.md +62 -0
- package/docs/reference/authoring-surface/bearer.md +53 -0
- package/docs/reference/authoring-surface/chartschema.md +23 -0
- package/docs/reference/authoring-surface/chartschemafor.md +48 -0
- package/docs/reference/authoring-surface/command.md +191 -0
- package/docs/reference/authoring-surface/customize.md +64 -0
- package/docs/reference/authoring-surface/defineconfig.md +41 -0
- package/docs/reference/authoring-surface/defineevent.md +114 -0
- package/docs/reference/authoring-surface/dispatch.md +126 -0
- package/docs/reference/authoring-surface/fail.md +83 -0
- package/docs/reference/authoring-surface/file.md +122 -0
- package/docs/reference/authoring-surface/frompick.md +277 -0
- package/docs/reference/authoring-surface/http.md +159 -0
- package/docs/reference/authoring-surface/isfailure.md +58 -0
- package/docs/reference/authoring-surface/jr2setup.md +237 -0
- package/docs/reference/authoring-surface/open.md +74 -0
- package/docs/reference/authoring-surface/openbody.md +86 -0
- package/docs/reference/authoring-surface/paramsof.md +125 -0
- package/docs/reference/authoring-surface/pool.md +186 -0
- package/docs/reference/authoring-surface/push.md +112 -0
- package/docs/reference/authoring-surface/secret.md +118 -0
- package/docs/reference/authoring-surface/sentby.md +83 -0
- package/docs/reference/authoring-surface/source.md +51 -0
- package/docs/reference/authoring-surface/text.md +62 -0
- package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
- package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
- package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
- package/docs/reference/authoring-surface/type-agentslots.md +12 -0
- package/docs/reference/authoring-surface/type-answeraction.md +12 -0
- package/docs/reference/authoring-surface/type-arg.md +19 -0
- package/docs/reference/authoring-surface/type-argnames.md +12 -0
- package/docs/reference/authoring-surface/type-binding.md +62 -0
- package/docs/reference/authoring-surface/type-bindingof.md +12 -0
- package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
- package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
- package/docs/reference/authoring-surface/type-ceilings.md +41 -0
- package/docs/reference/authoring-surface/type-chainlink.md +19 -0
- package/docs/reference/authoring-surface/type-chartaction.md +46 -0
- package/docs/reference/authoring-surface/type-chartagent.md +28 -0
- package/docs/reference/authoring-surface/type-chartevent.md +20 -0
- package/docs/reference/authoring-surface/type-chartguard.md +20 -0
- package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
- package/docs/reference/authoring-surface/type-chartjson.md +14 -0
- package/docs/reference/authoring-surface/type-chartstate.md +46 -0
- package/docs/reference/authoring-surface/type-charttext.md +40 -0
- package/docs/reference/authoring-surface/type-charttransition.md +22 -0
- package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
- package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
- package/docs/reference/authoring-surface/type-childat.md +12 -0
- package/docs/reference/authoring-surface/type-childslots.md +12 -0
- package/docs/reference/authoring-surface/type-commandenv.md +15 -0
- package/docs/reference/authoring-surface/type-commandinput.md +19 -0
- package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
- package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
- package/docs/reference/authoring-surface/type-customize.md +109 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
- package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
- package/docs/reference/authoring-surface/type-duration.md +15 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
- package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
- package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
- package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
- package/docs/reference/authoring-surface/type-eventdef.md +45 -0
- package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
- package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
- package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
- package/docs/reference/authoring-surface/type-failslot.md +14 -0
- package/docs/reference/authoring-surface/type-failure.md +19 -0
- package/docs/reference/authoring-surface/type-failureinput.md +20 -0
- package/docs/reference/authoring-surface/type-filebody.md +41 -0
- package/docs/reference/authoring-surface/type-frominput.md +14 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
- package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
- package/docs/reference/authoring-surface/type-httpinput.md +12 -0
- package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
- package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
- package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
- package/docs/reference/authoring-surface/type-jr2config.md +126 -0
- package/docs/reference/authoring-surface/type-lends.md +38 -0
- package/docs/reference/authoring-surface/type-logicat.md +12 -0
- package/docs/reference/authoring-surface/type-markedif.md +12 -0
- package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
- package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
- package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
- package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
- package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
- package/docs/reference/authoring-surface/type-openeffect.md +12 -0
- package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
- package/docs/reference/authoring-surface/type-openof.md +12 -0
- package/docs/reference/authoring-surface/type-opensecret.md +23 -0
- package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
- package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
- package/docs/reference/authoring-surface/type-poolspec.md +60 -0
- package/docs/reference/authoring-surface/type-pushinput.md +15 -0
- package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
- package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
- package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
- package/docs/reference/authoring-surface/type-quantity.md +14 -0
- package/docs/reference/authoring-surface/type-reached.md +12 -0
- package/docs/reference/authoring-surface/type-reposlot.md +70 -0
- package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
- package/docs/reference/authoring-surface/type-secret.md +25 -0
- package/docs/reference/authoring-surface/type-sender.md +19 -0
- package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
- package/docs/reference/authoring-surface/type-size.md +85 -0
- package/docs/reference/authoring-surface/type-slothandles.md +32 -0
- package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
- package/docs/reference/authoring-surface/type-syncstate.md +12 -0
- package/docs/reference/authoring-surface/type-text.md +19 -0
- package/docs/reference/authoring-surface/type-textpart.md +24 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
- package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
- package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
- package/docs/reference/authoring-surface/type-unknown.md +21 -0
- package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
- package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
- package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
- package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
- package/docs/reference/authoring-surface/type-workspaced.md +94 -0
- package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
- package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
- package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
- package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
- package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
- package/docs/reference/authoring-surface/workspace.md +259 -0
- package/docs/reference/authoring-surface.md +197 -0
- package/docs/reference/cli.md +433 -0
- package/docs/reference/config.md +756 -0
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
- package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
- package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
- package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
- package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
- package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
- package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
- package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
- package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
- package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
- package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
- package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
- package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
- package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
- package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
- package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
- package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
- package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
- package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
- package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
- package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
- package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
- package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
- package/docs/reference/decisions.md +83 -0
- package/docs/reference/glossary.md +648 -0
- package/docs/reference/http-api.md +1348 -0
- package/docs/reference/machines.md +190 -0
- package/docs/reference/mechanism-events.md +72 -0
- package/docs/why.md +206 -0
- package/package.json +6 -4
- package/src/actor.ts +976 -61
- package/src/agent.ts +114 -38
- package/src/ambient.ts +4 -1
- package/src/assembly-checks.ts +347 -0
- package/src/assembly-tar.ts +494 -0
- package/src/assembly.ts +721 -0
- package/src/cause.ts +234 -0
- package/src/chart.ts +2182 -0
- package/src/charts.ts +748 -0
- package/src/config.ts +520 -162
- package/src/content-hash.ts +105 -0
- package/src/custodian.lua +6 -5
- package/src/custodian.ts +143 -39
- package/src/customize.ts +388 -55
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect-checks.ts +557 -0
- package/src/effect.ts +1202 -0
- package/src/effector-wire.ts +674 -0
- package/src/fail.ts +209 -0
- package/src/fingerprint.ts +4 -3
- package/src/gate.ts +8 -0
- package/src/git-clone.ts +65 -0
- package/src/git-push.ts +336 -0
- package/src/harness-client.ts +764 -48
- package/src/held-secrets.ts +82 -26
- package/src/holds.ts +156 -0
- package/src/http-effect.ts +1423 -0
- package/src/http.ts +916 -113
- package/src/images.ts +47 -21
- package/src/index.ts +62 -43
- package/src/instance.ts +505 -59
- package/src/internal.ts +84 -0
- package/src/kube-client.ts +333 -0
- package/src/machine-doc.ts +106 -6
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/mount-digest.ts +53 -0
- package/src/names.ts +66 -8
- package/src/notices.ts +141 -0
- package/src/open-body.ts +1580 -0
- package/src/open-effect.ts +217 -0
- package/src/open.ts +59 -6
- package/src/parts.ts +709 -41
- package/src/party.ts +534 -0
- package/src/pick.ts +761 -0
- package/src/pool.ts +248 -37
- package/src/registration.ts +382 -19
- package/src/repo-fetch.ts +60 -85
- package/src/repos.ts +65 -75
- package/src/rest.ts +133 -0
- package/src/run-host.ts +2383 -115
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +1126 -0
- package/src/sandbox-effector.ts +111 -0
- package/src/sandbox-kube.ts +1721 -0
- package/src/sandbox-watch.ts +260 -0
- package/src/secret-placement.ts +529 -0
- package/src/server.ts +284 -84
- package/src/setup.ts +819 -117
- package/src/size.ts +454 -0
- package/src/snapshot-store.ts +115 -20
- package/src/stub-harness.ts +49 -16
- package/src/sync-client.ts +54 -0
- package/src/timers.ts +191 -0
- package/src/tokens.ts +35 -4
- package/src/vocabulary.ts +135 -15
- package/src/wire.ts +238 -10
- package/src/workspace.ts +1195 -338
- package/src/sandbox-kubectl.ts +0 -1108
package/src/internal.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @jr2/orchestrator/internal — NOT for workflow authors. Everything the kit's own packages share
|
|
2
|
+
// that is not on the Authoring surface (index.ts): the run host, the HTTP API, the Harness wire,
|
|
3
|
+
// the Custodian, the Effector wire, the cluster names, the load walk. The CLI, the e2e suite,
|
|
4
|
+
// and any other kit package that imports this one use it. No stability promise: any
|
|
5
|
+
// name here can change or go in any release, so a workflow or a `jr2.config.ts` that imports it
|
|
6
|
+
// is wrong, whatever compiles.
|
|
7
|
+
//
|
|
8
|
+
// It is the whole of index.ts plus the rest, so a kit file imports one specifier. The Orchestrator
|
|
9
|
+
// does NOT speak MCP (ADR-0013): the Harness presents the Menu to its model itself, and reaches the
|
|
10
|
+
// surface here through the Custodian in its pod.
|
|
11
|
+
|
|
12
|
+
export * from "./index.ts";
|
|
13
|
+
|
|
14
|
+
// The wire contract (ADR-0011): `defineEvent` is on the surface; the rest is the kit's.
|
|
15
|
+
export * from "@jr2/agent-protocol";
|
|
16
|
+
export * from "./agent.ts";
|
|
17
|
+
export * from "./effect.ts";
|
|
18
|
+
export * from "./http-effect.ts";
|
|
19
|
+
export * from "./sandbox-effect.ts";
|
|
20
|
+
export * from "./dispatch-effect.ts";
|
|
21
|
+
export type { EffectBinding, OpenEffect, OpenEffectPart } from "./open-effect.ts";
|
|
22
|
+
export { openPartsText } from "./open-effect.ts";
|
|
23
|
+
export * from "./effector-wire.ts";
|
|
24
|
+
export * from "./pick.ts";
|
|
25
|
+
export * from "./chart.ts";
|
|
26
|
+
export * from "./open-body.ts";
|
|
27
|
+
export * from "./charts.ts";
|
|
28
|
+
export * from "./parts.ts";
|
|
29
|
+
export * from "./size.ts";
|
|
30
|
+
export * from "./customize.ts";
|
|
31
|
+
export * from "./ambient.ts";
|
|
32
|
+
export * from "./config.ts";
|
|
33
|
+
export * from "./repo-identity.ts";
|
|
34
|
+
export * from "./pool.ts";
|
|
35
|
+
export * from "./setup.ts";
|
|
36
|
+
export { sentBy } from "./party.ts";
|
|
37
|
+
export * from "./vocabulary.ts";
|
|
38
|
+
export * from "./tokens.ts";
|
|
39
|
+
export * from "./durability.ts";
|
|
40
|
+
export * from "./snapshot-store.ts";
|
|
41
|
+
export * from "./actor.ts";
|
|
42
|
+
// The `gate` actor: jr2Setup pre-registers it and a state invokes it as `src: "gate"`, so no
|
|
43
|
+
// author writes the value (ADR-0015).
|
|
44
|
+
export * from "./gate.ts";
|
|
45
|
+
// The Failure's brand stays in fail.ts, and so does the logic that throws it: the kit makes a Failure
|
|
46
|
+
// only through `fail()` and `workspace()`, which imports that logic from fail.ts (ADR-0073).
|
|
47
|
+
export { failurePayload, isFailSlot } from "./fail.ts";
|
|
48
|
+
export type { Failure, FailSlot, FailureInput } from "./fail.ts";
|
|
49
|
+
export * from "./workspace.ts";
|
|
50
|
+
export * from "./images.ts";
|
|
51
|
+
export * from "./content-hash.ts";
|
|
52
|
+
export * from "./assembly-tar.ts";
|
|
53
|
+
export * from "./assembly.ts";
|
|
54
|
+
export * from "./assembly-checks.ts";
|
|
55
|
+
export * from "./mount-digest.ts";
|
|
56
|
+
export * from "./model-keys.ts";
|
|
57
|
+
export * from "./held-secrets.ts";
|
|
58
|
+
export * from "./secret-placement.ts";
|
|
59
|
+
export * from "./git-push.ts";
|
|
60
|
+
export * from "./git-clone.ts";
|
|
61
|
+
export * from "./custodian.ts";
|
|
62
|
+
export * from "./sandbox-kube.ts";
|
|
63
|
+
export {
|
|
64
|
+
effectorManifestKey,
|
|
65
|
+
LABEL_EFFECTOR_POD,
|
|
66
|
+
readSandboxEffectorManifest,
|
|
67
|
+
sandboxEffectorKeySecret,
|
|
68
|
+
sandboxEffectorObjectsOf,
|
|
69
|
+
type ProjectedSecret,
|
|
70
|
+
type SandboxEffectorManifest,
|
|
71
|
+
} from "./sandbox-effector.ts";
|
|
72
|
+
export * from "./repos.ts";
|
|
73
|
+
export * from "./repo-fetch.ts";
|
|
74
|
+
// Type-only: what a workflow event delivery looks like (the registration TABLE stays internal
|
|
75
|
+
// even here — ADR-0011).
|
|
76
|
+
export type { ChildErrorMarker, DeliveredEvent, PlacingMarker, RunMarker, TurnMarker } from "./registration.ts";
|
|
77
|
+
export * from "./machine-doc.ts";
|
|
78
|
+
export * from "./run-host.ts";
|
|
79
|
+
export * from "./http.ts";
|
|
80
|
+
export * from "./harness-client.ts";
|
|
81
|
+
export * from "./stub-harness.ts";
|
|
82
|
+
export * from "./instance.ts";
|
|
83
|
+
export * from "./server.ts";
|
|
84
|
+
export * from "./names.ts";
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// The Orchestrator's Kubernetes client (ADR-0063): plain REST on Node's built-in `fetch`, with no
|
|
2
|
+
// dependency. The Orchestrator is always in-cluster (ADR-0019), so the whole of "configuration" is
|
|
3
|
+
// the ServiceAccount the kubelet mounts and the Service the API server publishes:
|
|
4
|
+
//
|
|
5
|
+
// base URL https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT
|
|
6
|
+
// bearer the SA token FILE, read on every request (below)
|
|
7
|
+
// trust the SA `ca.crt`, through NODE_EXTRA_CA_CERTS on the Deployment (deploy.ts) — so
|
|
8
|
+
// this module holds no TLS code at all
|
|
9
|
+
//
|
|
10
|
+
// It speaks for jr2's own kinds alone — Sandbox, Repo, Secret — and every call stays behind the
|
|
11
|
+
// Sandbox and Repo ports, so swapping it for a client library is a contained change if jr2 ever
|
|
12
|
+
// needs exec or many more kinds (ADR-0063's considered options).
|
|
13
|
+
//
|
|
14
|
+
// Its rules, each observed on a real cluster (ADR-0063):
|
|
15
|
+
//
|
|
16
|
+
// - The token is never cached. The kubelet replaced a 10-minute token only about 50 seconds
|
|
17
|
+
// before it expired, so a cached one fails in production and never in a test. A `401` re-reads
|
|
18
|
+
// the file and retries ONCE — a rotation between the read and the request.
|
|
19
|
+
// - Writes are capped (8–16 at once). 200 parallel writes each opened a TLS connection and cost
|
|
20
|
+
// about 130% CPU; a provision burst queues here instead.
|
|
21
|
+
// - A watch asks for bookmarks and `timeoutSeconds=240`: built-in `fetch` aborts a body silent
|
|
22
|
+
// for 300s (`UND_ERR_BODY_TIMEOUT`) and that cannot be changed without undici's dispatcher, so
|
|
23
|
+
// the server closing first — and bookmarks about every 60s — keep a healthy watch clear of it.
|
|
24
|
+
// The watch LOOP that reads this stream lives in sandbox-watch.ts.
|
|
25
|
+
|
|
26
|
+
import { readFile } from "node:fs/promises";
|
|
27
|
+
|
|
28
|
+
// The API server's CA (`SERVICE_ACCOUNT_CA`, names.ts) is not read here: the Deployment points
|
|
29
|
+
// NODE_EXTRA_CA_CERTS at it, and built-in `fetch` trusts it from there.
|
|
30
|
+
|
|
31
|
+
/** Where the kubelet mounts the Pod's ServiceAccount. */
|
|
32
|
+
export const SERVICE_ACCOUNT_DIR = "/var/run/secrets/kubernetes.io/serviceaccount";
|
|
33
|
+
/** The bearer — rotated by the kubelet, so read per request. */
|
|
34
|
+
export const SERVICE_ACCOUNT_TOKEN = `${SERVICE_ACCOUNT_DIR}/token`;
|
|
35
|
+
|
|
36
|
+
/** The field manager every server-side apply names — the Orchestrator's one identity as a writer. */
|
|
37
|
+
export const FIELD_MANAGER = "jr2";
|
|
38
|
+
|
|
39
|
+
/** The write cap when the caller names none: inside ADR-0063's 8–16. */
|
|
40
|
+
const DEFAULT_MAX_WRITES = 12;
|
|
41
|
+
|
|
42
|
+
/** One kind, as its REST path spells it. The core group is `""` (`/api/v1`). */
|
|
43
|
+
export type KubeKind = { group: string; version: string; plural: string };
|
|
44
|
+
|
|
45
|
+
export const SANDBOXES: KubeKind = { group: "core.jr2.dev", version: "v1alpha1", plural: "sandboxes" };
|
|
46
|
+
export const REPOS: KubeKind = { group: "core.jr2.dev", version: "v1alpha1", plural: "repos" };
|
|
47
|
+
export const SECRETS: KubeKind = { group: "", version: "v1", plural: "secrets" };
|
|
48
|
+
export const CONFIGMAPS: KubeKind = { group: "", version: "v1", plural: "configmaps" };
|
|
49
|
+
export const SERVICES: KubeKind = { group: "", version: "v1", plural: "services" };
|
|
50
|
+
|
|
51
|
+
/** The object fields every kind shares — what the client and the watch read off any of them. */
|
|
52
|
+
export type KubeObject = {
|
|
53
|
+
apiVersion?: string;
|
|
54
|
+
kind?: string;
|
|
55
|
+
metadata?: {
|
|
56
|
+
name?: string;
|
|
57
|
+
namespace?: string;
|
|
58
|
+
uid?: string;
|
|
59
|
+
resourceVersion?: string;
|
|
60
|
+
labels?: Record<string, string>;
|
|
61
|
+
annotations?: Record<string, string>;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** One event off a watch stream. `ERROR` carries a `Status` (code 410 for a resourceVersion too
|
|
66
|
+
* old) INSIDE an HTTP 200 — the caller re-lists on any of them. */
|
|
67
|
+
export type WatchEvent<T extends KubeObject = KubeObject> =
|
|
68
|
+
| { type: "ADDED" | "MODIFIED" | "DELETED" | "BOOKMARK"; object: T }
|
|
69
|
+
| { type: "ERROR"; object: { kind?: "Status"; code?: number; reason?: string; message?: string } };
|
|
70
|
+
|
|
71
|
+
/** A refusal from the API server: the HTTP status and the `Status` body's own reason and words. */
|
|
72
|
+
export class KubeError extends Error {
|
|
73
|
+
readonly status: number;
|
|
74
|
+
readonly reason: string | undefined;
|
|
75
|
+
constructor(status: number, reason: string | undefined, message: string) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = "KubeError";
|
|
78
|
+
this.status = status;
|
|
79
|
+
this.reason = reason;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type KubeClientOptions = {
|
|
84
|
+
/** The API server. Default: the in-cluster Service from `KUBERNETES_SERVICE_HOST`/`_PORT`. */
|
|
85
|
+
baseUrl?: string;
|
|
86
|
+
/** The environment the default base URL is read from. Default `process.env`. */
|
|
87
|
+
env?: Record<string, string | undefined>;
|
|
88
|
+
/** The bearer, asked for on EVERY request — never cached (see the header). Default: the SA
|
|
89
|
+
* token file. */
|
|
90
|
+
token?: () => Promise<string>;
|
|
91
|
+
/** Injectable transport: the tests' fake API server. Default: global `fetch`. */
|
|
92
|
+
fetch?: typeof fetch;
|
|
93
|
+
/** How many writes may be in flight at once. Default 12. */
|
|
94
|
+
maxWrites?: number;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** The client the Sandbox, Repo and fetch ports share: one process, one write cap. */
|
|
98
|
+
export interface KubeClient {
|
|
99
|
+
/** The object, or `undefined` when there is none (404). */
|
|
100
|
+
get<T extends KubeObject>(kind: KubeKind, namespace: string, name: string): Promise<T | undefined>;
|
|
101
|
+
/** Every object of the kind in the namespace, with the list's resourceVersion — where a watch
|
|
102
|
+
* starts. A kind the cluster does not serve (no CRD) is a {@link KubeError} with status 404. */
|
|
103
|
+
list<T extends KubeObject>(
|
|
104
|
+
kind: KubeKind,
|
|
105
|
+
namespace: string,
|
|
106
|
+
opts?: { labelSelector?: string },
|
|
107
|
+
): Promise<{ items: T[]; resourceVersion: string }>;
|
|
108
|
+
/** Create; an existing object is a {@link KubeError} with status 409. */
|
|
109
|
+
create<T extends KubeObject>(kind: KubeKind, namespace: string, body: T): Promise<T & KubeObject>;
|
|
110
|
+
/** Delete; true when there was one to delete, false when it was already gone. */
|
|
111
|
+
delete(kind: KubeKind, namespace: string, name: string): Promise<boolean>;
|
|
112
|
+
/** A JSON merge patch — `null` removes a key. `status` patches the status subresource. Answers
|
|
113
|
+
* the object as it stands after the patch. */
|
|
114
|
+
patch<T extends KubeObject>(
|
|
115
|
+
kind: KubeKind,
|
|
116
|
+
namespace: string,
|
|
117
|
+
name: string,
|
|
118
|
+
body: object,
|
|
119
|
+
opts?: { status?: boolean },
|
|
120
|
+
): Promise<T>;
|
|
121
|
+
/** Server-side apply as field manager `jr2`, forced: create-or-update of every field the body
|
|
122
|
+
* names. Answers the object as it stands afterwards (its `uid` included). */
|
|
123
|
+
apply<T extends KubeObject>(kind: KubeKind, namespace: string, body: T): Promise<T & KubeObject>;
|
|
124
|
+
/** One watch request's events, from `resourceVersion`, until the server closes it. Throws on a
|
|
125
|
+
* refused request or a broken stream — the caller decides what each means (sandbox-watch.ts). */
|
|
126
|
+
watch<T extends KubeObject>(
|
|
127
|
+
kind: KubeKind,
|
|
128
|
+
namespace: string,
|
|
129
|
+
opts: { resourceVersion: string; labelSelector?: string; timeoutSeconds: number; signal?: AbortSignal },
|
|
130
|
+
): AsyncIterable<WatchEvent<T>>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The in-cluster base URL, from the Service env the kubelet injects into every pod. */
|
|
134
|
+
export function inClusterBaseUrl(env: Record<string, string | undefined> = process.env): string {
|
|
135
|
+
const host = env.KUBERNETES_SERVICE_HOST;
|
|
136
|
+
const port = env.KUBERNETES_SERVICE_PORT ?? "443";
|
|
137
|
+
if (!host) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"no Kubernetes API server: KUBERNETES_SERVICE_HOST is unset. The Orchestrator drives the cluster " +
|
|
140
|
+
"only from inside it (ADR-0019, ADR-0063) — `jr2 up` deploys it there.",
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
// An IPv6 address needs brackets in a URL.
|
|
144
|
+
return `https://${host.includes(":") ? `[${host}]` : host}:${port}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Build the client. Nothing is read until the first request: a boot never fails on the cluster. */
|
|
148
|
+
export function kubeClient(opts: KubeClientOptions = {}): KubeClient {
|
|
149
|
+
const fetchImpl = opts.fetch ?? fetch;
|
|
150
|
+
const token = opts.token ?? (async () => (await readFile(SERVICE_ACCOUNT_TOKEN, "utf8")).trim());
|
|
151
|
+
const writes = semaphore(opts.maxWrites ?? DEFAULT_MAX_WRITES);
|
|
152
|
+
let base: string | undefined = opts.baseUrl;
|
|
153
|
+
const baseUrl = () => (base ??= inClusterBaseUrl(opts.env));
|
|
154
|
+
|
|
155
|
+
/** One request, with the bearer read fresh and a 401 re-read and retried once. */
|
|
156
|
+
const send = async (
|
|
157
|
+
method: string,
|
|
158
|
+
path: string,
|
|
159
|
+
init: { query?: Record<string, string>; body?: string; contentType?: string; signal?: AbortSignal } = {},
|
|
160
|
+
): Promise<Response> => {
|
|
161
|
+
const url = new URL(path, baseUrl());
|
|
162
|
+
for (const [k, v] of Object.entries(init.query ?? {})) url.searchParams.set(k, v);
|
|
163
|
+
for (let attempt = 0; ; attempt++) {
|
|
164
|
+
const res = await fetchImpl(url, {
|
|
165
|
+
method,
|
|
166
|
+
headers: {
|
|
167
|
+
authorization: `Bearer ${await token()}`,
|
|
168
|
+
accept: "application/json",
|
|
169
|
+
...(init.contentType ? { "content-type": init.contentType } : {}),
|
|
170
|
+
},
|
|
171
|
+
...(init.body !== undefined ? { body: init.body } : {}),
|
|
172
|
+
...(init.signal ? { signal: init.signal } : {}),
|
|
173
|
+
});
|
|
174
|
+
// A rotation landed between the read and the request: the file already holds the new one.
|
|
175
|
+
if (res.status === 401 && attempt === 0) {
|
|
176
|
+
await res.body?.cancel();
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
return res;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
/** The answer as JSON, or a {@link KubeError} carrying the Status body's reason and words. */
|
|
184
|
+
const json = async <T>(res: Response, what: string): Promise<T> => {
|
|
185
|
+
const text = await res.text();
|
|
186
|
+
if (res.ok) return (text ? JSON.parse(text) : {}) as T;
|
|
187
|
+
throw errorOf(res.status, text, what);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** A write: inside the cap until its answer is read, so the cap bounds connections, not calls. */
|
|
191
|
+
const write = <T>(method: string, path: string, what: string, init: Parameters<typeof send>[2]) =>
|
|
192
|
+
writes(async () => json<T>(await send(method, path, init), what));
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
async get<T extends KubeObject>(kind: KubeKind, ns: string, name: string) {
|
|
196
|
+
const res = await send("GET", pathOf(kind, ns, name));
|
|
197
|
+
if (res.status === 404) {
|
|
198
|
+
await res.body?.cancel();
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
return json<T>(res, `get ${kind.plural}/${name}`);
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async list<T extends KubeObject>(kind: KubeKind, ns: string, opts: { labelSelector?: string } = {}) {
|
|
205
|
+
const res = await send("GET", pathOf(kind, ns), {
|
|
206
|
+
query: opts.labelSelector ? { labelSelector: opts.labelSelector } : {},
|
|
207
|
+
});
|
|
208
|
+
const out = await json<{ items?: T[]; metadata?: { resourceVersion?: string } }>(res, `list ${kind.plural}`);
|
|
209
|
+
return { items: out.items ?? [], resourceVersion: out.metadata?.resourceVersion ?? "" };
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
create<T extends KubeObject>(kind: KubeKind, ns: string, body: T) {
|
|
213
|
+
return write<T & KubeObject>("POST", pathOf(kind, ns), `create ${kind.plural}/${body.metadata?.name ?? "?"}`, {
|
|
214
|
+
body: JSON.stringify(body),
|
|
215
|
+
contentType: "application/json",
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
delete(kind: KubeKind, ns: string, name: string) {
|
|
220
|
+
return writes(async () => {
|
|
221
|
+
const res = await send("DELETE", pathOf(kind, ns, name));
|
|
222
|
+
if (res.status === 404) {
|
|
223
|
+
await res.body?.cancel();
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
await json(res, `delete ${kind.plural}/${name}`);
|
|
227
|
+
return true;
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
patch<T extends KubeObject>(kind: KubeKind, ns: string, name: string, body: object, o: { status?: boolean } = {}) {
|
|
232
|
+
return write<T>(
|
|
233
|
+
"PATCH",
|
|
234
|
+
pathOf(kind, ns, name, o.status ? "status" : undefined),
|
|
235
|
+
`patch ${kind.plural}/${name}`,
|
|
236
|
+
{
|
|
237
|
+
body: JSON.stringify(body),
|
|
238
|
+
contentType: "application/merge-patch+json",
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
apply<T extends KubeObject>(kind: KubeKind, ns: string, body: T) {
|
|
244
|
+
const name = body.metadata?.name;
|
|
245
|
+
if (!name) throw new Error(`apply ${kind.plural}: the object names no metadata.name`);
|
|
246
|
+
// JSON is YAML, so the apply content type takes the object as it is.
|
|
247
|
+
return write<T & KubeObject>("PATCH", pathOf(kind, ns, name), `apply ${kind.plural}/${name}`, {
|
|
248
|
+
query: { fieldManager: FIELD_MANAGER, force: "true" },
|
|
249
|
+
body: JSON.stringify(body),
|
|
250
|
+
contentType: "application/apply-patch+yaml",
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
async *watch<T extends KubeObject>(
|
|
255
|
+
kind: KubeKind,
|
|
256
|
+
ns: string,
|
|
257
|
+
o: { resourceVersion: string; labelSelector?: string; timeoutSeconds: number; signal?: AbortSignal },
|
|
258
|
+
): AsyncIterable<WatchEvent<T>> {
|
|
259
|
+
const res = await send("GET", pathOf(kind, ns), {
|
|
260
|
+
query: {
|
|
261
|
+
watch: "true",
|
|
262
|
+
allowWatchBookmarks: "true",
|
|
263
|
+
timeoutSeconds: String(o.timeoutSeconds),
|
|
264
|
+
resourceVersion: o.resourceVersion,
|
|
265
|
+
...(o.labelSelector ? { labelSelector: o.labelSelector } : {}),
|
|
266
|
+
},
|
|
267
|
+
...(o.signal ? { signal: o.signal } : {}),
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok) throw errorOf(res.status, await res.text(), `watch ${kind.plural}`);
|
|
270
|
+
if (!res.body) return;
|
|
271
|
+
// One JSON object per line. A line can straddle two chunks, so the tail waits for the next.
|
|
272
|
+
let buf = "";
|
|
273
|
+
for await (const chunk of res.body.pipeThrough(new TextDecoderStream())) {
|
|
274
|
+
buf += chunk;
|
|
275
|
+
let nl: number;
|
|
276
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
277
|
+
const line = buf.slice(0, nl).trim();
|
|
278
|
+
buf = buf.slice(nl + 1);
|
|
279
|
+
if (line) yield JSON.parse(line) as WatchEvent<T>;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (buf.trim()) yield JSON.parse(buf) as WatchEvent<T>;
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** A `Status` body read into a {@link KubeError}; a body that is not one keeps its raw text. */
|
|
288
|
+
function errorOf(status: number, text: string, what: string): KubeError {
|
|
289
|
+
let reason: string | undefined;
|
|
290
|
+
let message = text;
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(text) as { reason?: string; message?: string };
|
|
293
|
+
reason = parsed.reason;
|
|
294
|
+
message = parsed.message ?? text;
|
|
295
|
+
} catch {
|
|
296
|
+
// not a Status: the raw text is the most there is to say
|
|
297
|
+
}
|
|
298
|
+
return new KubeError(status, reason, `${what} failed (${status}${reason ? ` ${reason}` : ""}): ${message}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** The REST path of a kind in a namespace, optionally one object and one subresource. */
|
|
302
|
+
function pathOf(kind: KubeKind, ns: string, name?: string, sub?: string): string {
|
|
303
|
+
const root = kind.group ? `/apis/${kind.group}/${kind.version}` : `/api/${kind.version}`;
|
|
304
|
+
return (
|
|
305
|
+
`${root}/namespaces/${encodeURIComponent(ns)}/${kind.plural}` +
|
|
306
|
+
(name !== undefined ? `/${encodeURIComponent(name)}` : "") +
|
|
307
|
+
(sub !== undefined ? `/${sub}` : "")
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** At most `n` of the wrapped calls at once; the rest queue in arrival order. */
|
|
312
|
+
function semaphore(n: number): <T>(fn: () => Promise<T>) => Promise<T> {
|
|
313
|
+
let active = 0;
|
|
314
|
+
const queue: Array<() => void> = [];
|
|
315
|
+
return async (fn) => {
|
|
316
|
+
// A released slot passes straight to the next waiter, so a caller arriving in between can
|
|
317
|
+
// never slip past the cap.
|
|
318
|
+
if (active >= n) await new Promise<void>((resolve) => queue.push(resolve));
|
|
319
|
+
else active++;
|
|
320
|
+
try {
|
|
321
|
+
return await fn();
|
|
322
|
+
} finally {
|
|
323
|
+
const next = queue.shift();
|
|
324
|
+
if (next) next();
|
|
325
|
+
else active--;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** True for the API server's "no such object" — the one refusal most callers read as an answer. */
|
|
331
|
+
export const isNotFound = (err: unknown): boolean => err instanceof KubeError && err.status === 404;
|
|
332
|
+
/** True for "already exists" on a create. */
|
|
333
|
+
export const isAlreadyExists = (err: unknown): boolean => err instanceof KubeError && err.status === 409;
|
package/src/machine-doc.ts
CHANGED
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
import type { AnyStateMachine, StateNode, TransitionDefinition } from "xstate";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import type { EventAudience } from "@jr2/agent-protocol";
|
|
21
|
-
import {
|
|
21
|
+
import { argvText, isEffect, targetText, type EffectParts } from "./effect.ts";
|
|
22
|
+
import { isFailSlot } from "./fail.ts";
|
|
23
|
+
import { loadWarningsOf, seatsOf, vocabularyOf, type MachineSeat } from "./vocabulary.ts";
|
|
24
|
+
import { open } from "./open.ts";
|
|
22
25
|
|
|
23
26
|
/** One transition of the Machine, id-addressed at both ends. */
|
|
24
27
|
export type MachineTransitionDoc = {
|
|
@@ -32,9 +35,34 @@ export type MachineTransitionDoc = {
|
|
|
32
35
|
label: string;
|
|
33
36
|
/** Normalized guard name, if guarded. */
|
|
34
37
|
guard?: string;
|
|
38
|
+
/** xstate's own transition `description`, as authored: what this writing does, where one name
|
|
39
|
+
* has writings that differ (ADR-0015's "one def, one meaning"). */
|
|
40
|
+
description?: string;
|
|
35
41
|
kind: "event" | "always" | "after" | "done" | "error";
|
|
36
42
|
};
|
|
37
43
|
|
|
44
|
+
/** An Effect slot's parts, as the doc carries them (ADR-0067): a command's argv (an argv slot reads
|
|
45
|
+
* `<name>`), its timeout, and the Repo Slot whose Worktree is its cwd, when it names one. */
|
|
46
|
+
export type EffectDoc =
|
|
47
|
+
| { kind: "command"; argv: string[]; timeoutMs: number; slot?: string; secrets?: string[]; pin?: true }
|
|
48
|
+
/** An http Effect (ADR-0068): its method, its fixed target, the secrets it names — never a value
|
|
49
|
+
* — and `file` when its body is a Worktree file (ADR-0067). */
|
|
50
|
+
| {
|
|
51
|
+
kind: "http";
|
|
52
|
+
method: string;
|
|
53
|
+
target: string;
|
|
54
|
+
secrets: string[];
|
|
55
|
+
timeoutMs: number;
|
|
56
|
+
idempotent: boolean;
|
|
57
|
+
file?: true;
|
|
58
|
+
}
|
|
59
|
+
/** A push (ADR-0067): which options the author set. */
|
|
60
|
+
| { kind: "push"; sign: boolean; squash: boolean; force: boolean; pin: boolean; timeoutMs: number; slot?: string }
|
|
61
|
+
/** A dispatch (ADR-0071): the Workflow it starts a run of, whether it waits, and the other
|
|
62
|
+
* Orchestrator's origin (`<open>` while a package leaves it Open); no target is this Orchestrator.
|
|
63
|
+
* The token's secret is named on the parts, never here: the target is what a reader routes on. */
|
|
64
|
+
| { kind: "dispatch"; workflow: string; wait: boolean; target?: string; timeoutMs: number };
|
|
65
|
+
|
|
38
66
|
/** One state of the Machine; `states` nests in document order. */
|
|
39
67
|
export type MachineStateDoc = {
|
|
40
68
|
/** Unique state id (custom `id:` respected). */
|
|
@@ -44,7 +72,10 @@ export type MachineStateDoc = {
|
|
|
44
72
|
type: "atomic" | "compound" | "parallel" | "final" | "history";
|
|
45
73
|
/** Initial child state id (compound only). */
|
|
46
74
|
initial?: string;
|
|
47
|
-
invoke
|
|
75
|
+
/** Each invoke's id and slot; an Effect's carries its plain-data parts (ADR-0067) — what it runs,
|
|
76
|
+
* where, and for how long, read without running anything. `fail`: the slot is a `fail()`, so this
|
|
77
|
+
* state can fail the run (ADR-0073). */
|
|
78
|
+
invoke: Array<{ id: string; src: string; effect?: EffectDoc; fail?: true }>;
|
|
48
79
|
tags: string[];
|
|
49
80
|
description?: string;
|
|
50
81
|
states: MachineStateDoc[];
|
|
@@ -98,7 +129,8 @@ export type ChildMachineDoc = {
|
|
|
98
129
|
export type MachineEventDoc = {
|
|
99
130
|
name: string;
|
|
100
131
|
description?: string;
|
|
101
|
-
|
|
132
|
+
/** Absent: each writing's state decides who may send it (ADR-0015) — see the body's `seats`. */
|
|
133
|
+
audience?: EventAudience;
|
|
102
134
|
/** The def's input schema as JSON Schema — what a form or a `jr2 send` prompt generates from. */
|
|
103
135
|
input: unknown;
|
|
104
136
|
};
|
|
@@ -112,6 +144,13 @@ export type MachineBodyDoc = {
|
|
|
112
144
|
/** The events THIS Machine declares, and only this one (ADR-0049): a nested Machine's ride its
|
|
113
145
|
* own `MachineBodyDoc`. Empty for a Machine not built by `jr2Setup`. */
|
|
114
146
|
events: MachineEventDoc[];
|
|
147
|
+
/** Who may send what, per state (ADR-0015): one entry per party on each state that invokes one —
|
|
148
|
+
* the audience rule's answer for this Machine, as `jr2 up` prints it. Empty for a Machine not
|
|
149
|
+
* built by `jr2Setup`. */
|
|
150
|
+
seats: MachineSeat[];
|
|
151
|
+
/** What the build found legal and suspect (ADR-0015) — a mode nothing enters, a pick a Gate above
|
|
152
|
+
* takes from the Agent below, one name written for two parties on one path. */
|
|
153
|
+
warnings: string[];
|
|
115
154
|
};
|
|
116
155
|
|
|
117
156
|
/** The serialized structure of a workflow's Machine. */
|
|
@@ -122,6 +161,10 @@ export type MachineDoc = MachineBodyDoc & {
|
|
|
122
161
|
* to discover the diagram is lying. Absent on the nested `MachineBodyDoc`s: it is a whole-doc
|
|
123
162
|
* question, and the root's walk already covers them. */
|
|
124
163
|
opaqueStates?: string[];
|
|
164
|
+
/** A run's doc only (`runMachine`, ADR-0070): each bound Chart the doc could not draw, so its Open
|
|
165
|
+
* body shows the slot's own subgraph — the body's path in the run, its slot, and why (a stored
|
|
166
|
+
* Chart the door no longer admits is drift, D-B). Absent when every bound Chart is drawn. */
|
|
167
|
+
chartsNotDrawn?: Array<{ path: string; src: string; reason: string }>;
|
|
125
168
|
};
|
|
126
169
|
|
|
127
170
|
/** Normalize a guard to a display name: setup() name, parameterized type, or `"inline"`. */
|
|
@@ -172,6 +215,7 @@ function serializeTransition(t: TransitionDefinition<any, any>): MachineTransiti
|
|
|
172
215
|
kind,
|
|
173
216
|
};
|
|
174
217
|
if (guard !== undefined) doc.guard = guard;
|
|
218
|
+
if (t.description !== undefined) doc.description = t.description;
|
|
175
219
|
return doc;
|
|
176
220
|
}
|
|
177
221
|
|
|
@@ -287,7 +331,12 @@ function serializeState(node: StateNode<any, any>, into: MachineTransitionDoc[],
|
|
|
287
331
|
id: node.id,
|
|
288
332
|
key: node.key,
|
|
289
333
|
type: node.type,
|
|
290
|
-
invoke: node.invoke.map((inv) =>
|
|
334
|
+
invoke: node.invoke.map((inv) => {
|
|
335
|
+
const logic = typeof inv.src === "string" ? scope.actors[inv.src] : undefined;
|
|
336
|
+
if (isFailSlot(logic)) return { id: inv.id, src: srcName(inv.src), fail: true as const };
|
|
337
|
+
if (!isEffect(logic)) return { id: inv.id, src: srcName(inv.src) };
|
|
338
|
+
return { id: inv.id, src: srcName(inv.src), effect: effectDoc(logic.effect) };
|
|
339
|
+
}),
|
|
291
340
|
tags: [...node.tags],
|
|
292
341
|
states: children.map((child) => serializeState(child, into, scope)),
|
|
293
342
|
children: childMachines(node, scope),
|
|
@@ -311,6 +360,18 @@ export function opaqueStates(doc: MachineDoc): string[] {
|
|
|
311
360
|
return ids;
|
|
312
361
|
}
|
|
313
362
|
|
|
363
|
+
/** Every Machine body in a doc — the root's, then each child Machine's, depth-first in state order.
|
|
364
|
+
* A recursive reference is not repeated: its body is already above it. */
|
|
365
|
+
export function machineBodies(doc: MachineBodyDoc): MachineBodyDoc[] {
|
|
366
|
+
const out: MachineBodyDoc[] = [doc];
|
|
367
|
+
const walk = (state: MachineStateDoc): void => {
|
|
368
|
+
for (const child of state.children) if (child.machine) out.push(...machineBodies(child.machine));
|
|
369
|
+
for (const nested of state.states) walk(nested);
|
|
370
|
+
};
|
|
371
|
+
walk(doc.root);
|
|
372
|
+
return out;
|
|
373
|
+
}
|
|
374
|
+
|
|
314
375
|
/** Serialize one Machine — its own state tree and its own transitions. Each level carries its own
|
|
315
376
|
* actor registry (a child machine resolves `src` names against ITS `setup`, not its parent's). */
|
|
316
377
|
function serializeBody(machine: AnyStateMachine, path: Set<AnyStateMachine>): MachineBodyDoc {
|
|
@@ -321,10 +382,10 @@ function serializeBody(machine: AnyStateMachine, path: Set<AnyStateMachine>): Ma
|
|
|
321
382
|
const events = [...(vocabularyOf(machine)?.values() ?? [])].map((def) => ({
|
|
322
383
|
name: def.name,
|
|
323
384
|
...(def.description !== undefined ? { description: def.description } : {}),
|
|
324
|
-
audience: def.audience,
|
|
385
|
+
...(def.audience !== undefined ? { audience: def.audience } : {}),
|
|
325
386
|
input: z.toJSONSchema(def.input),
|
|
326
387
|
}));
|
|
327
|
-
return { id: machine.id, root, transitions, events };
|
|
388
|
+
return { id: machine.id, root, transitions, events, seats: seatsOf(machine), warnings: loadWarningsOf(machine) };
|
|
328
389
|
}
|
|
329
390
|
|
|
330
391
|
/** Serialize a workflow's template Machine into the Console's DTO, child machines and all. */
|
|
@@ -332,3 +393,42 @@ export function serializeMachine(workflow: string, machine: AnyStateMachine): Ma
|
|
|
332
393
|
const doc: MachineDoc = { workflow, ...serializeBody(machine, new Set()) };
|
|
333
394
|
return { ...doc, opaqueStates: opaqueStates(doc) };
|
|
334
395
|
}
|
|
396
|
+
|
|
397
|
+
/** An Effect's plain data as the doc carries it: what it runs, read without running anything. */
|
|
398
|
+
function effectDoc(parts: EffectParts): EffectDoc {
|
|
399
|
+
if (parts.kind === "http") {
|
|
400
|
+
const { method, secrets, timeoutMs, idempotent } = parts;
|
|
401
|
+
return {
|
|
402
|
+
kind: "http",
|
|
403
|
+
method,
|
|
404
|
+
target: targetText(parts),
|
|
405
|
+
secrets: [...secrets],
|
|
406
|
+
timeoutMs,
|
|
407
|
+
idempotent,
|
|
408
|
+
...(parts.file ? { file: true as const } : {}),
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (parts.kind === "push") {
|
|
412
|
+
const { sign, squash, force, pin, timeoutMs, slot } = parts;
|
|
413
|
+
return { kind: "push", sign, squash, force, pin, timeoutMs, ...(slot ? { slot } : {}) };
|
|
414
|
+
}
|
|
415
|
+
if (parts.kind === "dispatch") {
|
|
416
|
+
const { workflow, wait, timeoutMs, target } = parts;
|
|
417
|
+
return {
|
|
418
|
+
kind: "dispatch",
|
|
419
|
+
workflow,
|
|
420
|
+
wait,
|
|
421
|
+
...(target === undefined ? {} : { target: target === open ? "<open>" : target.url }),
|
|
422
|
+
timeoutMs,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const { argv, timeoutMs, slot, secrets, pin } = parts;
|
|
426
|
+
return {
|
|
427
|
+
kind: "command",
|
|
428
|
+
argv: argvText(argv),
|
|
429
|
+
timeoutMs,
|
|
430
|
+
...(slot ? { slot } : {}),
|
|
431
|
+
...(secrets?.length ? { secrets: [...secrets] } : {}),
|
|
432
|
+
...(pin ? { pin } : {}),
|
|
433
|
+
};
|
|
434
|
+
}
|