@jr2/orchestrator 0.4.1 → 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/console/components/app.ts +2 -0
- package/console/components/fleet.ts +14 -1
- package/console/components/machine-pane.ts +41 -2
- package/console/main.ts +13 -1
- package/console/store.ts +102 -6
- package/console/style.css +21 -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 +5 -3
- package/src/actor.ts +541 -79
- package/src/agent.ts +114 -38
- package/src/ambient.ts +2 -1
- package/src/assembly-checks.ts +189 -7
- package/src/cause.ts +234 -0
- package/src/chart.ts +169 -37
- package/src/charts.ts +34 -6
- package/src/config.ts +430 -178
- package/src/custodian.ts +74 -22
- package/src/customize.ts +188 -46
- package/src/dispatch-effect.ts +945 -0
- package/src/door-compat.ts +217 -0
- package/src/durability.ts +11 -4
- package/src/effect.ts +341 -44
- package/src/effector-wire.ts +160 -11
- package/src/fail.ts +209 -0
- package/src/harness-client.ts +541 -47
- package/src/http-effect.ts +495 -53
- package/src/http.ts +427 -16
- package/src/index.ts +60 -82
- package/src/instance.ts +68 -8
- package/src/internal.ts +84 -0
- package/src/machine-doc.ts +21 -3
- package/src/mechanism-routes.ts +168 -0
- package/src/model-keys.generated.ts +53 -0
- package/src/model-keys.ts +18 -58
- package/src/notices.ts +66 -13
- package/src/open-body.ts +159 -23
- package/src/open-effect.ts +81 -8
- package/src/open.ts +59 -6
- package/src/parts.ts +184 -13
- package/src/party.ts +68 -2
- package/src/pick.ts +463 -11
- package/src/pool.ts +247 -36
- package/src/registration.ts +83 -4
- package/src/repos.ts +2 -1
- package/src/run-host.ts +565 -31
- package/src/run-output.ts +38 -0
- package/src/sandbox-effect.ts +273 -38
- package/src/sandbox-kube.ts +3 -1
- package/src/secret-placement.ts +52 -17
- package/src/server.ts +3 -0
- package/src/setup.ts +239 -56
- package/src/size.ts +121 -9
- package/src/snapshot-store.ts +54 -10
- package/src/stub-harness.ts +44 -16
- package/src/timers.ts +14 -1
- package/src/vocabulary.ts +69 -14
- package/src/wire.ts +90 -16
- package/src/workspace.ts +697 -175
package/src/http-effect.ts
CHANGED
|
@@ -83,20 +83,33 @@ import { httpFileWith, sandboxEffectorPort } from "./sandbox-effect.ts";
|
|
|
83
83
|
|
|
84
84
|
// --- Naming a secret -----------------------------------------------------------------------------
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
// Naming it in an Effect is what places it in that Effect's Effector (ADR-0068). On the wire it is
|
|
87
|
+
// a name.
|
|
88
|
+
/** What `secret("name")` gives: an Effect secret, by its name in the `secrets` list of
|
|
89
|
+
* `jr2.config.ts`. Put it in an Effect's `auth`, a header, a `text` or a command's `env`. Its
|
|
90
|
+
* `.path` is the path of a file that holds the value. The value stays in the Effector, so the
|
|
91
|
+
* Machine and the Agent never see it. */
|
|
89
92
|
export type Secret = { readonly secret: string; readonly path: { readonly secret: string; readonly path: true } };
|
|
90
93
|
|
|
91
|
-
/**
|
|
94
|
+
/** What the `text` template gives: a string of literal text and Effect secrets, in order. The
|
|
95
|
+
* Effector builds the string, so the Machine never sees the value of a secret. */
|
|
92
96
|
export type Text = { readonly text: readonly TextPart[] };
|
|
93
97
|
|
|
94
98
|
declare const openSecretMark: unique symbol;
|
|
95
99
|
/** The TYPE half of an Open secret (ADR-0054): whatever carries `secret(open)` — the secret, its
|
|
96
100
|
* `.path`, a `text` or a `basic` pair holding it, a `bearer` of it — carries this mark, so the
|
|
97
|
-
* Effect's constructor states the Open part "secret" and `customize()` offers `{ secret }`.
|
|
101
|
+
* Effect's constructor states the Open part "secret" and `customize()` offers `{ secret }`.
|
|
102
|
+
* Phantom.
|
|
103
|
+
*
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
98
106
|
export type OpenSecretMark = { readonly [openSecretMark]: true };
|
|
99
107
|
|
|
108
|
+
/** A named secret, never `secret(open)`: where a definition's Open part is a whole that holds a
|
|
109
|
+
* secret — a dispatch's target (ADR-0071) — the secret is bound with it, to a name, so the mark
|
|
110
|
+
* `secret(open)` carries may not be present. One type for the definition and the binding. */
|
|
111
|
+
export type NamedSecret = Secret & { readonly [K in keyof OpenSecretMark]?: never };
|
|
112
|
+
|
|
100
113
|
/** `secret(open)`: a secret a packaged Machine names in its auth, a header or a command's env, and
|
|
101
114
|
* leaves for its composer to name (ADR-0054). */
|
|
102
115
|
export type OpenSecret = {
|
|
@@ -116,9 +129,97 @@ const SECRET_NAME = /^[A-Za-z0-9._-]{1,253}$/;
|
|
|
116
129
|
* (a value and its `.path`) is told from one that names two. */
|
|
117
130
|
const openSecretOf = new WeakMap<object, object>();
|
|
118
131
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Name an Effect secret: `secret("jira")`. The name is the `name` of an entry in the `secrets`
|
|
134
|
+
* list of `jr2.config.ts`. The Effector reads the value and puts it into the call, or into the env
|
|
135
|
+
* of a `command`. The Agent never reads it, and the Orchestrator never holds it.
|
|
136
|
+
*
|
|
137
|
+
* Use it in `bearer`, `basic` or `text`, or in the `env` of a `command`. In an `env`,
|
|
138
|
+
* `secret("x")` gives the value, and `secret("x").path` gives the path of a file that holds the
|
|
139
|
+
* value.
|
|
140
|
+
*
|
|
141
|
+
* `secret(open)` leaves the name to the composer of a packaged Machine, who binds it one time:
|
|
142
|
+
* `customize(machine, { effects: { <slot>: { secret: "<name>" } } })`. An Effect can have one Open
|
|
143
|
+
* secret, in its `auth`, a header or its `env`. A url cannot have one: leave the whole url Open
|
|
144
|
+
* (`url: open`) instead.
|
|
145
|
+
*
|
|
146
|
+
* It refuses, when you call it:
|
|
147
|
+
* - a name that is not letters, digits, `.`, `_` and `-`, that starts with `.`, or that is longer
|
|
148
|
+
* than 253 characters: `secret("<name>"): a secret's name is letters, digits, ".", "_" and "-",
|
|
149
|
+
* and does not start with "." — it is the file the Effector reads it from (ADR-0068)`
|
|
150
|
+
* - an Effect that names `secret(open)` in two places that are two secrets: `an Effect takes one
|
|
151
|
+
* Open secret — it names secret(open) in two places that are two secrets; name one secret(open)
|
|
152
|
+
* and use it in each place, or name the others with secret("<name>") (ADR-0054)`
|
|
153
|
+
* - `secret(open)` in a url: ``secret(open) in a url: a url's secret is not an Open part — leave
|
|
154
|
+
* the whole url Open (`url: open`) and let the composer bind it with its secret in its path
|
|
155
|
+
* (ADR-0054)``
|
|
156
|
+
*
|
|
157
|
+
* And at `jr2 up`:
|
|
158
|
+
* - a name that the `secrets` list does not have: ``workflow "<workflow>": the <kind> Effect
|
|
159
|
+
* "<slot>" names secret("<name>"), which the `secrets` list does not hold — add it: secrets: [{
|
|
160
|
+
* name: "<name>", value: process.env.<NAME> }] (ADR-0068)``
|
|
161
|
+
* - a value of fewer than 8 characters: `secrets[<i>] (<name>) is an Effect secret shorter than 8
|
|
162
|
+
* characters — the Effector replaces every match of a secret in what it returns, and a value
|
|
163
|
+
* this short would match ordinary text. Use the whole credential; the value itself is not shown
|
|
164
|
+
* (ADR-0068)`
|
|
165
|
+
*
|
|
166
|
+
* @param name - The name of an entry in the `secrets` list.
|
|
167
|
+
* @returns The Effect secret, for an Effect. Its `.path` names the file of the secret, for the
|
|
168
|
+
* `env` of a `command`.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* import { command, fail, jr2Setup, secret, workspace, type Workspaced } from "@jr2/orchestrator";
|
|
173
|
+
*
|
|
174
|
+
* type Input = Workspaced<{}, "app">;
|
|
175
|
+
*
|
|
176
|
+
* const body = jr2Setup({
|
|
177
|
+
* types: {} as { context: Input; input: Input },
|
|
178
|
+
* actors: {
|
|
179
|
+
* // The command reads the file at $KUBECONFIG. The Agent never reads the secret.
|
|
180
|
+
* deploy: command(["kubectl", "apply", "-f", "k8s/"], {
|
|
181
|
+
* env: { KUBECONFIG: secret("kube").path },
|
|
182
|
+
* }),
|
|
183
|
+
* fail: fail(),
|
|
184
|
+
* },
|
|
185
|
+
* }).createMachine({
|
|
186
|
+
* id: "deploy",
|
|
187
|
+
* context: ({ input }) => input,
|
|
188
|
+
* on: { "workspace.lost": ".lost" },
|
|
189
|
+
* initial: "deploying",
|
|
190
|
+
* states: {
|
|
191
|
+
* deploying: { invoke: { src: "deploy", onDone: "done" } },
|
|
192
|
+
* done: { type: "final" },
|
|
193
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
194
|
+
* },
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* export const machine = workspace(body, {
|
|
198
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
199
|
+
* spec: () => ({ branch: "deploy" }),
|
|
200
|
+
* });
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
204
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
205
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
206
|
+
* @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
|
|
207
|
+
*/
|
|
121
208
|
export function secret(name: string): Secret;
|
|
209
|
+
/**
|
|
210
|
+
* Leave an Effect secret Open: `secret(open)`, in a Machine that you publish as a package. The
|
|
211
|
+
* composer binds its name one time: `customize(machine, { effects: { <slot>: { secret: "<name>"
|
|
212
|
+
* } } })`. {@link secret} tells the rest, and what it refuses.
|
|
213
|
+
*
|
|
214
|
+
* @param name - `open`.
|
|
215
|
+
* @returns The Open secret, for an Effect. Its `.path` names the file of the secret, for the `env`
|
|
216
|
+
* of a `command`.
|
|
217
|
+
*
|
|
218
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
219
|
+
* @see docs/guides/bind-open-parts.md
|
|
220
|
+
* @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
|
|
221
|
+
*/
|
|
222
|
+
export function secret(name: typeof open): OpenSecret;
|
|
122
223
|
export function secret(name: string | typeof open): Secret | OpenSecret {
|
|
123
224
|
if ((name as unknown) === open) {
|
|
124
225
|
// An Open secret (ADR-0054): the composer binds its name once, with `customize()`'s
|
|
@@ -144,7 +245,11 @@ export function secret(name: string | typeof open): Secret | OpenSecret {
|
|
|
144
245
|
return s;
|
|
145
246
|
}
|
|
146
247
|
|
|
147
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* What a value that may hold `secret(open)` gives: marked when it does.
|
|
250
|
+
*
|
|
251
|
+
* @internal
|
|
252
|
+
*/
|
|
148
253
|
type MarkedIf<V, T> = [Extract<V, OpenSecretMark>] extends [never] ? T : T & OpenSecretMark;
|
|
149
254
|
|
|
150
255
|
/** The values of a record type, or never. */
|
|
@@ -154,7 +259,10 @@ type ValuesOf<R> = R extends Readonly<Record<string, infer V>> ? V : never;
|
|
|
154
259
|
export type OpenSecretIn<V> = [Extract<V, OpenSecretMark>] extends [never] ? never : "secret";
|
|
155
260
|
|
|
156
261
|
/** The Open parts an http definition's types state (ADR-0054): its url, when it is `open`, and its
|
|
157
|
-
* secret, when its auth or a header holds `secret(open)`.
|
|
262
|
+
* secret, when its auth or a header holds `secret(open)`.
|
|
263
|
+
*
|
|
264
|
+
* @internal
|
|
265
|
+
*/
|
|
158
266
|
export type HttpOpenParts<U, A, H> = (U extends typeof open ? "url" : never) | OpenSecretIn<A | ValuesOf<H>>;
|
|
159
267
|
|
|
160
268
|
/** A secret's wire part: `{ secret: name }` — for an Open secret, a part that stands for it. */
|
|
@@ -165,7 +273,46 @@ function partOf(s: Secret): TextPart {
|
|
|
165
273
|
return part;
|
|
166
274
|
}
|
|
167
275
|
|
|
168
|
-
/**
|
|
276
|
+
/**
|
|
277
|
+
* Make a string from literal text and Effect secrets, as a tagged template:
|
|
278
|
+
* `` text`Token ${secret("x")}` ``. The Effector puts in the value of each secret when it makes
|
|
279
|
+
* the call. Use it for a url with a secret in its path, or for a header value. Use it also for
|
|
280
|
+
* `auth`, where it is the whole `Authorization` header, or for a part of `basic`.
|
|
281
|
+
*
|
|
282
|
+
* It refuses a `${…}` that is not a `secret(...)` or a string: `` text`…`: a value is a
|
|
283
|
+
* secret("name") or a string, got <value> ``.
|
|
284
|
+
*
|
|
285
|
+
* @param strings - The literal text of the template.
|
|
286
|
+
* @param values - The `${…}` values, each a `secret(...)` or a string.
|
|
287
|
+
* @returns The string, which the Effector makes.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* import { http, jr2Setup, secret, text } from "@jr2/orchestrator";
|
|
292
|
+
*
|
|
293
|
+
* export const machine = jr2Setup({
|
|
294
|
+
* actors: {
|
|
295
|
+
* notify: http({
|
|
296
|
+
* method: "POST",
|
|
297
|
+
* url: text`https://api.telegram.org/bot${secret("telegram")}/sendMessage`,
|
|
298
|
+
* headers: { "x-run-source": text`jr2` },
|
|
299
|
+
* request: () => ({ json: { chat_id: "12345", text: "The run is done." } }),
|
|
300
|
+
* }),
|
|
301
|
+
* },
|
|
302
|
+
* }).createMachine({
|
|
303
|
+
* id: "notify",
|
|
304
|
+
* initial: "notifying",
|
|
305
|
+
* states: {
|
|
306
|
+
* notifying: { invoke: { src: "notify", onDone: "done" } },
|
|
307
|
+
* done: { type: "final" },
|
|
308
|
+
* },
|
|
309
|
+
* });
|
|
310
|
+
* ```
|
|
311
|
+
*
|
|
312
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
313
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
314
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
315
|
+
*/
|
|
169
316
|
export function text<const V extends Array<Secret | OpenSecret | string>>(
|
|
170
317
|
strings: TemplateStringsArray,
|
|
171
318
|
...values: V
|
|
@@ -189,15 +336,87 @@ export function text(strings: TemplateStringsArray, ...values: Array<Secret | st
|
|
|
189
336
|
return { text: parts };
|
|
190
337
|
}
|
|
191
338
|
|
|
192
|
-
/**
|
|
339
|
+
/**
|
|
340
|
+
* Make the `auth` of an `http` Effect that sends `Authorization: Bearer <value>`, with the value
|
|
341
|
+
* of an Effect secret. In a packaged Machine, the secret can be `secret(open)`.
|
|
342
|
+
*
|
|
343
|
+
* It refuses a value that is not a secret: `bearer(…) takes a secret("name")`.
|
|
344
|
+
*
|
|
345
|
+
* @param s - The secret: `secret("name")`, or `secret(open)`.
|
|
346
|
+
* @returns The `auth` of an `http` Effect.
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* import { bearer, http, jr2Setup, secret } from "@jr2/orchestrator";
|
|
351
|
+
*
|
|
352
|
+
* export const machine = jr2Setup({
|
|
353
|
+
* actors: {
|
|
354
|
+
* status: http({
|
|
355
|
+
* method: "GET",
|
|
356
|
+
* url: "https://api.github.com/repos/you/app",
|
|
357
|
+
* auth: bearer(secret("github")),
|
|
358
|
+
* }),
|
|
359
|
+
* },
|
|
360
|
+
* }).createMachine({
|
|
361
|
+
* id: "status",
|
|
362
|
+
* initial: "reading",
|
|
363
|
+
* states: {
|
|
364
|
+
* reading: { invoke: { src: "status", onDone: "done" } },
|
|
365
|
+
* done: { type: "final" },
|
|
366
|
+
* },
|
|
367
|
+
* });
|
|
368
|
+
* ```
|
|
369
|
+
*
|
|
370
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
371
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
372
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
373
|
+
* @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
|
|
374
|
+
*/
|
|
193
375
|
export function bearer<const S extends Secret>(s: S): MarkedIf<S, { readonly bearer: Secret }>;
|
|
194
376
|
export function bearer(s: Secret): { readonly bearer: Secret } {
|
|
195
377
|
if (!isSecret(s)) throw new Error('bearer(…) takes a secret("name")');
|
|
196
378
|
return { bearer: s };
|
|
197
379
|
}
|
|
198
380
|
|
|
199
|
-
/**
|
|
200
|
-
*
|
|
381
|
+
/**
|
|
382
|
+
* Make the `auth` of an `http` Effect that sends `Authorization: Basic <value>`, where the value
|
|
383
|
+
* is the base64 of `user:password`. The Effector puts in the value of each secret.
|
|
384
|
+
*
|
|
385
|
+
* Each of `user` and `password` is a string, a `secret(...)` or a `text`. In a packaged Machine,
|
|
386
|
+
* one of them can hold `secret(open)`. It refuses another value: `` expected a string, a
|
|
387
|
+
* secret("name") or a text`…`, got <value> ``.
|
|
388
|
+
*
|
|
389
|
+
* @param user - The user name: a string, a `secret(...)` or a `text`.
|
|
390
|
+
* @param password - The password: a string, a `secret(...)` or a `text`.
|
|
391
|
+
* @returns The `auth` of an `http` Effect.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```ts
|
|
395
|
+
* import { basic, http, jr2Setup, secret } from "@jr2/orchestrator";
|
|
396
|
+
*
|
|
397
|
+
* export const machine = jr2Setup({
|
|
398
|
+
* actors: {
|
|
399
|
+
* issue: http({
|
|
400
|
+
* method: "GET",
|
|
401
|
+
* url: "https://jira.example.com/rest/api/2/issue/APP-1",
|
|
402
|
+
* auth: basic("jr2-bot@example.com", secret("jira-token")),
|
|
403
|
+
* }),
|
|
404
|
+
* },
|
|
405
|
+
* }).createMachine({
|
|
406
|
+
* id: "issue",
|
|
407
|
+
* initial: "reading",
|
|
408
|
+
* states: {
|
|
409
|
+
* reading: { invoke: { src: "issue", onDone: "done" } },
|
|
410
|
+
* done: { type: "final" },
|
|
411
|
+
* },
|
|
412
|
+
* });
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
416
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
417
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
418
|
+
* @see docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md
|
|
419
|
+
*/
|
|
201
420
|
export function basic<const U extends string | Secret | Text, const P extends string | Secret | Text>(
|
|
202
421
|
user: U,
|
|
203
422
|
password: P,
|
|
@@ -285,29 +504,129 @@ export type FromInput<S, T> = T | ((input: S extends z.ZodType ? z.infer<S> : vo
|
|
|
285
504
|
/** `file(path, { slot?, segments?, contentType? })`: a Worktree file as an `http` body. */
|
|
286
505
|
export type FileBody<S extends z.ZodType | undefined = undefined> = {
|
|
287
506
|
readonly file: {
|
|
288
|
-
/** The file, relative to the Repo Slot's Worktree
|
|
507
|
+
/** The file, relative to the Repo Slot's Worktree, or to `/work` in a Workspace with no Repo
|
|
508
|
+
* Slot. */
|
|
289
509
|
readonly path: FromInput<S, string>;
|
|
290
|
-
/** The Repo Slot whose Worktree holds it
|
|
510
|
+
/** The Repo Slot whose Worktree holds it. Write it only when the Workspace has more than one.
|
|
511
|
+
*/
|
|
291
512
|
readonly slot?: string;
|
|
292
|
-
/** Path segments
|
|
513
|
+
/** Path segments that jr2 adds to the end of the url, as `path` of `request` does for a json
|
|
514
|
+
* or text body. */
|
|
293
515
|
readonly segments?: FromInput<S, readonly string[]>;
|
|
294
|
-
/** The
|
|
295
|
-
*
|
|
516
|
+
/** The content type of the body. Write it or a `content-type` header, not both. With neither,
|
|
517
|
+
* it is `application/octet-stream`. */
|
|
296
518
|
readonly contentType?: string;
|
|
297
519
|
};
|
|
298
520
|
};
|
|
299
521
|
|
|
300
522
|
/**
|
|
301
|
-
*
|
|
302
|
-
* the Sandbox Effector of the Workspace whose body invokes it
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
* functions of the Effect
|
|
523
|
+
* Make a Worktree file the body of an `http` Effect: `body: file("dist/report.pdf")`. The Effect
|
|
524
|
+
* then runs in the Sandbox Effector of the Workspace whose body invokes it. That Effector opens the
|
|
525
|
+
* file in the Worktree and sends it. In a Workspace with no Repo Slot, the path is relative to
|
|
526
|
+
* `/work`, the folder that its steps share. Use it with `PUT`, `POST` or `PATCH`. `path` and
|
|
527
|
+
* `segments` can be functions of the input of the Effect.
|
|
528
|
+
*
|
|
529
|
+
* Some paths and files give the output `refused`, and then nothing is sent. The path has a symlink
|
|
530
|
+
* in it, has `..`, or is absolute. Or the file is a FIFO, a directory, or a file with more than one
|
|
531
|
+
* link.
|
|
532
|
+
*
|
|
533
|
+
* It refuses, when you call it:
|
|
534
|
+
* - a path that is not a string or a function: `file(path): the path is a string, or a function of
|
|
535
|
+
* the Effect's input that returns one`
|
|
536
|
+
*
|
|
537
|
+
* And when `http` takes the definition:
|
|
538
|
+
* - a method other than `PUT`, `POST` and `PATCH`: `http({ method: "<method>", body: file(...) }):
|
|
539
|
+
* a file body is sent with PUT, POST, PATCH (ADR-0068)`
|
|
540
|
+
* - a `request` beside it: ``http({ body: file(...), request }): with a file body the run's part
|
|
541
|
+
* is file()'s — its path, from the input if it must be, and its `segments` — so write no
|
|
542
|
+
* `request` (ADR-0068)``
|
|
543
|
+
* - a `content-type` header and `contentType`: `http({ headers, body: file(path, { contentType })
|
|
544
|
+
* }): "content-type" is set by file()'s contentType — write one of them (ADR-0067)`
|
|
545
|
+
*
|
|
546
|
+
* And at `jr2 up`, an Effect that no `workspace()` invokes: `workflow "<workflow>": the http Effect
|
|
547
|
+
* "<slot>" sends a Worktree file as its body, and no workspace() invokes it — outside a Workspace
|
|
548
|
+
* there is no Worktree to read; invoke it from a workspace()'s body, or send a json or text body
|
|
549
|
+
* (ADR-0068)`.
|
|
550
|
+
*
|
|
551
|
+
* @param path - The file, relative to the Worktree of the Repo Slot, or to `/work` in a Workspace
|
|
552
|
+
* with no Repo Slot: a string, or a function of the input of the Effect that gives one.
|
|
553
|
+
* @param options - Optional. Each field says what it does.
|
|
554
|
+
* @returns The `body` of an `http` Effect.
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* import {
|
|
559
|
+
* bearer,
|
|
560
|
+
* command,
|
|
561
|
+
* fail,
|
|
562
|
+
* file,
|
|
563
|
+
* http,
|
|
564
|
+
* jr2Setup,
|
|
565
|
+
* secret,
|
|
566
|
+
* workspace,
|
|
567
|
+
* type Workspaced,
|
|
568
|
+
* } from "@jr2/orchestrator";
|
|
569
|
+
*
|
|
570
|
+
* type Input = Workspaced<{}, "app">;
|
|
571
|
+
*
|
|
572
|
+
* const body = jr2Setup({
|
|
573
|
+
* types: {} as { context: Input; input: Input },
|
|
574
|
+
* actors: {
|
|
575
|
+
* build: command(["npm", "run", "report"]),
|
|
576
|
+
* upload: http({
|
|
577
|
+
* method: "PUT",
|
|
578
|
+
* url: "https://files.example.com/reports",
|
|
579
|
+
* auth: bearer(secret("files")),
|
|
580
|
+
* body: file("dist/report.pdf", {
|
|
581
|
+
* segments: ["report.pdf"],
|
|
582
|
+
* contentType: "application/pdf",
|
|
583
|
+
* }),
|
|
584
|
+
* }),
|
|
585
|
+
* fail: fail(),
|
|
586
|
+
* },
|
|
587
|
+
* }).createMachine({
|
|
588
|
+
* id: "report",
|
|
589
|
+
* context: ({ input }) => input,
|
|
590
|
+
* on: { "workspace.lost": ".lost" },
|
|
591
|
+
* initial: "building",
|
|
592
|
+
* states: {
|
|
593
|
+
* building: { invoke: { src: "build", onDone: "uploading" } },
|
|
594
|
+
* uploading: { invoke: { src: "upload", onDone: "done" } },
|
|
595
|
+
* done: { type: "final" },
|
|
596
|
+
* lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
|
|
597
|
+
* },
|
|
598
|
+
* });
|
|
599
|
+
*
|
|
600
|
+
* export const machine = workspace(body, {
|
|
601
|
+
* repos: { app: { url: "https://github.com/you/app.git" } },
|
|
602
|
+
* spec: () => ({ branch: "report" }),
|
|
603
|
+
* });
|
|
604
|
+
* ```
|
|
605
|
+
*
|
|
606
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
607
|
+
* @see docs/concepts/effects.md
|
|
608
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
306
609
|
*/
|
|
307
610
|
export function file<S extends z.ZodType | undefined = undefined>(
|
|
308
611
|
path: FromInput<S, string>,
|
|
309
|
-
options: {
|
|
612
|
+
options: {
|
|
613
|
+
/** The Repo Slot whose Worktree holds the file. Write it only when the Workspace has more than
|
|
614
|
+
* one. */
|
|
615
|
+
slot?: string;
|
|
616
|
+
/**
|
|
617
|
+
* Path segments that jr2 adds to the end of the url, as `path` does in `request` for a json or
|
|
618
|
+
* text body. A list of strings, or a function of the input of the Effect that gives one.
|
|
619
|
+
*/
|
|
620
|
+
segments?: FromInput<S, readonly string[]>;
|
|
621
|
+
/**
|
|
622
|
+
* The content type of the body. Default `application/octet-stream`. Do not write it beside a
|
|
623
|
+
* `content-type` header.
|
|
624
|
+
*/
|
|
625
|
+
contentType?: string;
|
|
626
|
+
} = {},
|
|
310
627
|
): FileBody<S> {
|
|
628
|
+
// The Sandbox Effector opens the file beneath the Worktree with no symlink at any component
|
|
629
|
+
// (ADR-0067, ADR-0068); the refusals it makes are output, never a fault.
|
|
311
630
|
if (typeof path !== "string" && typeof path !== "function") {
|
|
312
631
|
throw new Error("file(path): the path is a string, or a function of the Effect's input that returns one");
|
|
313
632
|
}
|
|
@@ -326,12 +645,16 @@ const FILE_METHODS: readonly HttpMethod[] = ["PUT", "POST", "PATCH"];
|
|
|
326
645
|
|
|
327
646
|
// --- The definition ------------------------------------------------------------------------------
|
|
328
647
|
|
|
329
|
-
/** What `request` returns: the
|
|
648
|
+
/** What the `request` of an `http` definition returns: the part of the request that changes for
|
|
649
|
+
* each run. The definition fixes the rest. */
|
|
330
650
|
export type HttpRequestParts = {
|
|
331
|
-
/**
|
|
332
|
-
*
|
|
651
|
+
/**
|
|
652
|
+
* Path segments that jr2 adds to the end of the url. jr2 encodes each segment. A segment that
|
|
653
|
+
* is `.`, `..` or empty gives the output `refused`, and nothing is sent.
|
|
654
|
+
*/
|
|
333
655
|
path?: readonly string[];
|
|
334
|
-
/** A JSON body.
|
|
656
|
+
/** A JSON body. Do not give `text` too. When it starts, the Effect refuses both: ``http:
|
|
657
|
+
* `request` returned both json and text``. */
|
|
335
658
|
json?: unknown;
|
|
336
659
|
/** A text body. */
|
|
337
660
|
text?: string;
|
|
@@ -339,33 +662,97 @@ export type HttpRequestParts = {
|
|
|
339
662
|
|
|
340
663
|
/** An `http` Effect's definition. */
|
|
341
664
|
export type HttpDefinition<S extends z.ZodType | undefined = undefined> = {
|
|
665
|
+
/**
|
|
666
|
+
* The HTTP method: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`. It refuses
|
|
667
|
+
* another: `http({ method: "<method>" }): the method is one of GET, HEAD, POST, PUT, PATCH,
|
|
668
|
+
* DELETE, OPTIONS`.
|
|
669
|
+
*/
|
|
342
670
|
method: HttpMethod;
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
*
|
|
671
|
+
// The scheme and host are literal so that no param reaches the host (ADR-0068); an Open url is
|
|
672
|
+
// bound once by the composer, never per run (ADR-0054).
|
|
673
|
+
/**
|
|
674
|
+
* The url: `http://` or `https://`, then a literal host. No input, param or Effect secret can
|
|
675
|
+
* change where the call goes. An Effect secret can be only in the path:
|
|
676
|
+
* `` text`https://api.telegram.org/bot${secret("tg")}/sendMessage` ``.
|
|
677
|
+
*
|
|
678
|
+
* Or `open`: a packaged Machine that cannot know the host leaves the url Open, and the composer
|
|
679
|
+
* binds it one time with `customize(machine, { effects: { <slot>: { url } } })`.
|
|
680
|
+
*
|
|
681
|
+
* It refuses:
|
|
682
|
+
* - a scheme or host that is not literal: `` http({ url }): the url is an http:// or https:// url
|
|
683
|
+
* whose scheme and host are literal — a secret may sit only in its path, after a "/":
|
|
684
|
+
* text`https://api.example.com/bot${secret("tg")}/send` (ADR-0068) ``
|
|
685
|
+
* - an Effect secret after `?` or `#`: `` http({ url }): secret("<name>") sits in the url's query
|
|
686
|
+
* or fragment — a secret may sit only in its path; send it in `auth` or a header instead:
|
|
687
|
+
* headers: { "x-api-key": text`${secret("...")}` } (ADR-0068) ``
|
|
688
|
+
* - a host that is not a host: `http({ url }): "<scheme>://<host>" is not a host`
|
|
689
|
+
* - a user name or password in the url: ``http({ url }): the url carries userinfo — use `auth` ``
|
|
690
|
+
*/
|
|
347
691
|
url: string | Text | typeof open;
|
|
348
|
-
/**
|
|
692
|
+
/**
|
|
693
|
+
* Headers that are the same for each run. Each name is lowercase. A value is a string, or a
|
|
694
|
+
* `text` with an Effect secret.
|
|
695
|
+
*
|
|
696
|
+
* It refuses:
|
|
697
|
+
* - a name that is not lowercase, or that frames or routes the request (`host`,
|
|
698
|
+
* `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`,
|
|
699
|
+
* `keep-alive`, `proxy-*`): `http({ headers }): "<name>" is not a header an Effect sets — a
|
|
700
|
+
* lowercase name that does not frame or route the request (ADR-0068)`
|
|
701
|
+
* - an `authorization` header beside `auth`: ``http({ headers }): "authorization" is set by
|
|
702
|
+
* `auth` — write one of them``
|
|
703
|
+
*/
|
|
349
704
|
headers?: Readonly<Record<string, string | Text>>;
|
|
705
|
+
/**
|
|
706
|
+
* The `Authorization` header: `bearer(secret("x"))`, `basic(user, password)`, or a `text` that
|
|
707
|
+
* is the whole value.
|
|
708
|
+
*/
|
|
350
709
|
auth?: { readonly bearer: Secret } | { readonly basic: { user: Text; password: Text } } | Text;
|
|
351
|
-
/**
|
|
710
|
+
/**
|
|
711
|
+
* The zod schema of the input of the Effect: what the `input` of the `invoke`, or `fromPick`,
|
|
712
|
+
* gives it. jr2 parses the input with it before the call. When it starts, input that does not
|
|
713
|
+
* agree with it makes the Effect fail (its `onError` route): `http "<slot>": its input does not
|
|
714
|
+
* match the definition's input schema — <path>: <message>`, one `<path>: <message>` for each
|
|
715
|
+
* problem, joined by `; `.
|
|
716
|
+
*/
|
|
352
717
|
input?: S;
|
|
353
|
-
/**
|
|
718
|
+
/**
|
|
719
|
+
* A function that makes the part of the request that changes for each run, from the input:
|
|
720
|
+
* `path` segments, and a `json` or `text` body. It cannot change the host, a header or an
|
|
721
|
+
* Effect secret. Do not write it beside `body`.
|
|
722
|
+
*/
|
|
354
723
|
request?: (input: S extends z.ZodType ? z.infer<S> : void) => HttpRequestParts;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
*
|
|
724
|
+
// A file body is an ADR-0067 Effect: the Sandbox Effector of the Workspace sends it.
|
|
725
|
+
/**
|
|
726
|
+
* A Worktree file as the body: `file(path, { slot?, segments?, contentType? })`. The Effect then
|
|
727
|
+
* runs in the Sandbox Effector of its Workspace. Do not write `request` beside it: the `segments`
|
|
728
|
+
* of `file` are the path. It refuses another body: ``http({ body }): a body is file(path, {
|
|
729
|
+
* slot?, segments?, contentType? }) — json and text come from `request` ``.
|
|
730
|
+
*/
|
|
358
731
|
body?: FileBody<S>;
|
|
359
|
-
/**
|
|
732
|
+
/**
|
|
733
|
+
* How long jr2 waits for the answer: a number and a unit, such as `"30s"` or `"2m"`. Default
|
|
734
|
+
* `"30s"`. With no answer in that time, the output is `unknown`. It refuses a value that is not
|
|
735
|
+
* a positive duration: `http: timeout "<timeout>" is not a positive duration (like "30s",
|
|
736
|
+
* "2m")`.
|
|
737
|
+
*/
|
|
360
738
|
timeout?: Duration;
|
|
361
|
-
/**
|
|
739
|
+
/**
|
|
740
|
+
* The largest number of bytes of a text response body that the output keeps. Default 65536.
|
|
741
|
+
* When jr2 cuts the body, `truncated` is `true`. It refuses a value that is not a positive whole
|
|
742
|
+
* number: `http({ maxBody }): a positive number of bytes`.
|
|
743
|
+
*/
|
|
362
744
|
maxBody?: number;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
745
|
+
// The restore rule (ADR-0067): a restore that finds no replica holding the call sends it again
|
|
746
|
+
// only when it is marked; unmarked, it settles `unknown`.
|
|
747
|
+
/**
|
|
748
|
+
* `true` when it is safe to send the call two times: it has its own idempotency key, or it only
|
|
749
|
+
* reads. Default `false`. After an Orchestrator restart, jr2 cannot always tell if the call went
|
|
750
|
+
* out. Then it sends an idempotent call again, and gives any other call the output `unknown`.
|
|
751
|
+
*/
|
|
366
752
|
idempotent?: boolean;
|
|
367
753
|
};
|
|
368
754
|
|
|
755
|
+
/** @internal */
|
|
369
756
|
export type HttpInput<S> = S extends z.ZodType ? z.infer<S> : void;
|
|
370
757
|
|
|
371
758
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -504,8 +891,8 @@ export function openHttpParts<S extends z.ZodType | undefined>(def: HttpDefiniti
|
|
|
504
891
|
/**
|
|
505
892
|
* The unbound logic of an http definition with Open parts (ADR-0054) — its url, an Open secret in
|
|
506
893
|
* its auth or a header, or both — or undefined when every part is fixed. Binding builds the Effect
|
|
507
|
-
* again, whole, with `rebuild` (its own constructor) over the definition with the bound parts, so
|
|
508
|
-
* bound one is checked like any definition; a binding that names one of two Open parts gives an
|
|
894
|
+
* again, whole, with `rebuild` (its own constructor) over the definition with the bound parts, so
|
|
895
|
+
* the bound one is checked like any definition; a binding that names one of two Open parts gives an
|
|
509
896
|
* Effect still Open in the other.
|
|
510
897
|
*/
|
|
511
898
|
export function openHttpOf<S extends z.ZodType | undefined>(
|
|
@@ -879,8 +1266,8 @@ async function drive<I, O>(
|
|
|
879
1266
|
/**
|
|
880
1267
|
* The state that invoked this Effect exited (D1) — or the host is parking the run. Parking keeps
|
|
881
1268
|
* the call for the restore to find. An exit stops the polling and records the abort, "outcome
|
|
882
|
-
* unknown": an http Effect has no stop route, so the Effector finishes the call and drops an
|
|
883
|
-
* reads. The Machine never hears it.
|
|
1269
|
+
* unknown": an http Effect has no stop route, so the Effector finishes the call and drops an
|
|
1270
|
+
* outcome nobody reads. The Machine never hears it.
|
|
884
1271
|
*/
|
|
885
1272
|
function stopped(self: AnyActorRef, system: AnyActorSystem, effectId: string): void {
|
|
886
1273
|
const run = running.get(self);
|
|
@@ -955,12 +1342,65 @@ function parseDuration(d: Duration): number {
|
|
|
955
1342
|
}
|
|
956
1343
|
|
|
957
1344
|
/**
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
* `
|
|
1345
|
+
* Make an `http` Effect: a call to a URL, with Effect secrets that the Agent never reads. Put it
|
|
1346
|
+
* in `actors`, and invoke it by its key: `actors: { comment: http({ ... }) }`, then `invoke: {
|
|
1347
|
+
* src: "comment" }`.
|
|
1348
|
+
*
|
|
1349
|
+
* It runs in the Instance Effector, which `jr2 up` deploys when a Machine has an `http` Effect.
|
|
1350
|
+
* With `body: file(...)`, it runs in the Sandbox Effector of the Workspace whose body invokes it.
|
|
1351
|
+
* The Effector makes the call. The Orchestrator holds no secret.
|
|
1352
|
+
*
|
|
1353
|
+
* Every outcome is output (`HttpOutput`): `response`, `unreachable`, `refused` or `unknown`. A 4xx
|
|
1354
|
+
* or 5xx status is output too. jr2 sends the call at most one time, unless you mark it
|
|
1355
|
+
* `idempotent`. When its state exits while the call runs, jr2 stops waiting for it. The Effector
|
|
1356
|
+
* can still send it.
|
|
1357
|
+
*
|
|
1358
|
+
* Each field of {@link HttpDefinition} tells what it refuses. `createMachine` also refuses an
|
|
1359
|
+
* Effect written directly in an `invoke`: `machine "<id>": state "<state>" invokes an Effect
|
|
1360
|
+
* inline — a Part is a key of actors, so it has a name that the ledger, the report and
|
|
1361
|
+
* customize() share: actors: { <name>: http(...) }, then invoke: { src: "<name>" }`.
|
|
1362
|
+
*
|
|
1363
|
+
* @param def - The definition. Each field tells what it does.
|
|
1364
|
+
* @returns An Effect, for `actors`.
|
|
1365
|
+
*
|
|
1366
|
+
* @example
|
|
1367
|
+
* ```ts
|
|
1368
|
+
* import { z } from "zod";
|
|
1369
|
+
* import { bearer, http, jr2Setup, secret } from "@jr2/orchestrator";
|
|
1370
|
+
*
|
|
1371
|
+
* const comment = http({
|
|
1372
|
+
* method: "POST",
|
|
1373
|
+
* url: "https://jira.example.com/rest/api/2/issue",
|
|
1374
|
+
* auth: bearer(secret("jira")),
|
|
1375
|
+
* input: z.object({ key: z.string(), body: z.string() }),
|
|
1376
|
+
* request: ({ key, body }) => ({ path: [key, "comment"], json: { body } }),
|
|
1377
|
+
* });
|
|
1378
|
+
*
|
|
1379
|
+
* export const machine = jr2Setup({
|
|
1380
|
+
* types: {} as { context: { key: string }; input: { key: string } },
|
|
1381
|
+
* actors: { comment },
|
|
1382
|
+
* }).createMachine({
|
|
1383
|
+
* id: "comment",
|
|
1384
|
+
* context: ({ input }) => input,
|
|
1385
|
+
* initial: "commenting",
|
|
1386
|
+
* states: {
|
|
1387
|
+
* commenting: {
|
|
1388
|
+
* invoke: {
|
|
1389
|
+
* src: "comment",
|
|
1390
|
+
* input: ({ context }) => ({ key: context.key, body: "The build passed." }),
|
|
1391
|
+
* onDone: "done",
|
|
1392
|
+
* },
|
|
1393
|
+
* },
|
|
1394
|
+
* done: { type: "final" },
|
|
1395
|
+
* },
|
|
1396
|
+
* });
|
|
1397
|
+
* ```
|
|
1398
|
+
*
|
|
1399
|
+
* @see docs/concepts/effects.md
|
|
1400
|
+
* @see docs/guides/run-a-command-or-call-a-url.md
|
|
1401
|
+
* @see docs/concepts/secrets-and-trust.md
|
|
1402
|
+
* @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
|
|
1403
|
+
* @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
|
|
964
1404
|
*/
|
|
965
1405
|
export function http<
|
|
966
1406
|
S extends z.ZodType | undefined = undefined,
|
|
@@ -973,6 +1413,8 @@ export function http<
|
|
|
973
1413
|
export function http<S extends z.ZodType | undefined = undefined>(
|
|
974
1414
|
def: HttpDefinition<S>,
|
|
975
1415
|
): EffectLogic<HttpInput<S>, HttpOutput> {
|
|
1416
|
+
// The Orchestrator signs the start, and the Effector builds the request: the definition's parts,
|
|
1417
|
+
// and `request`'s from the input (ADR-0068).
|
|
976
1418
|
if (def.body !== undefined) return httpFileWith(sandboxEffectorPort, def);
|
|
977
1419
|
return httpWith(
|
|
978
1420
|
(target) => createEffectorClient({ key: target.key, transport: nodeEffectorTransport(target.service) }),
|