@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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// The content address jr2 takes of a tree (ADR-0038, ADR-0069). It lives here, not in the CLI,
|
|
2
|
+
// because two ends compute it over one Assembly: `jr2 up` hashes the staged tree, and the door
|
|
3
|
+
// recomputes the hash over the unpacked tree before it accepts a publish. One implementation, so
|
|
4
|
+
// the two ends cannot drift.
|
|
5
|
+
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { readdir, readFile, readlink, stat } from "node:fs/promises";
|
|
8
|
+
import { basename, join, relative } from "node:path";
|
|
9
|
+
|
|
10
|
+
/** The salt of an Assembly's hash (ADR-0069). The host and the door both hash with it. */
|
|
11
|
+
export const ASSEMBLY_HASH_SALT = "assembly";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The name of an Assembly (ADR-0069): a hash over every entry the Assembly's archive carries. The
|
|
15
|
+
* host names its staged tree with it, and the door recomputes it over the unpacked tree. The door
|
|
16
|
+
* then trusts "one hash names one Assembly" three times: `jr2 up` skips a publish whose hash is
|
|
17
|
+
* current, the door answers `unchanged`, and the door reuses an `assemblies/<hash>/` it already
|
|
18
|
+
* holds. So the hash covers what the archive carries, and nothing can change under one hash:
|
|
19
|
+
*
|
|
20
|
+
* - Each directory is a record, so an empty one counts.
|
|
21
|
+
* - Each symlink is a record with its target. `contentHash` skips links (an image tag must not see
|
|
22
|
+
* pnpm's store links twice), but an Assembly's links travel, and a changed target is a changed
|
|
23
|
+
* Assembly.
|
|
24
|
+
* - Each record is framed: a type, then every field as its byte length and its bytes. Without the
|
|
25
|
+
* frame, a file `a` that holds `X0:b\nY` hashes as the files `a` = `X` and `b` = `Y`.
|
|
26
|
+
*
|
|
27
|
+
* The walk is in byte order (`byteOrder`). Modes are not in the hash: nothing at the door reads
|
|
28
|
+
* them, and an umask must not move an Assembly's name. Other entry types (a socket, a FIFO) are not
|
|
29
|
+
* records: the archive refuses them, so they never reach the door. Nothing is excluded: the hash
|
|
30
|
+
* names every byte the archive carries.
|
|
31
|
+
*/
|
|
32
|
+
export async function assemblyHash(dir: string): Promise<string> {
|
|
33
|
+
const h = createHash("sha256");
|
|
34
|
+
h.update(`salt:${ASSEMBLY_HASH_SALT}\n`);
|
|
35
|
+
const field = (bytes: Buffer): void => {
|
|
36
|
+
h.update(`${bytes.length}:`);
|
|
37
|
+
h.update(bytes);
|
|
38
|
+
};
|
|
39
|
+
const walk = async (d: string, rel: string): Promise<void> => {
|
|
40
|
+
const entries = (await readdir(d, { withFileTypes: true })).sort((a, b) => byteOrder(a.name, b.name));
|
|
41
|
+
for (const e of entries) {
|
|
42
|
+
const p = join(d, e.name);
|
|
43
|
+
const path = Buffer.from(rel === "" ? e.name : `${rel}/${e.name}`, "utf8");
|
|
44
|
+
if (e.isSymbolicLink()) {
|
|
45
|
+
h.update("l");
|
|
46
|
+
field(path);
|
|
47
|
+
field(Buffer.from(await readlink(p), "utf8"));
|
|
48
|
+
} else if (e.isDirectory()) {
|
|
49
|
+
h.update("d");
|
|
50
|
+
field(path);
|
|
51
|
+
await walk(p, path.toString("utf8"));
|
|
52
|
+
} else if (e.isFile()) {
|
|
53
|
+
h.update("f");
|
|
54
|
+
field(path);
|
|
55
|
+
field(await readFile(p));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
await walk(dir, "");
|
|
60
|
+
return h.digest("hex").slice(0, 12);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The order a content address walks a directory in: UTF-8 byte order of the entry names, never a
|
|
65
|
+
* locale's collation. `localeCompare` reads the ICU locale from `LANG`/`LC_ALL`, and the two ends
|
|
66
|
+
* of a hash run in different locales: in `cs_CZ`, "ch" sorts after "h", so a host there would
|
|
67
|
+
* hash `node_modules/chalk` after `hono` while the Kit image hashes it first, and every publish
|
|
68
|
+
* would fail the door's check. The Assembly's tar orders its entries by the same rule.
|
|
69
|
+
*/
|
|
70
|
+
export function byteOrder(a: string, b: string): number {
|
|
71
|
+
return Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A short content hash over `paths` (files and/or directories, sorted walk), salted with `salt`.
|
|
76
|
+
* Symlinks are skipped rather than followed: in a pnpm bundle `node_modules/<pkg>` links into the
|
|
77
|
+
* virtual store, whose real files the walk already visits — following would hash the same bytes
|
|
78
|
+
* twice. Each path contributes its entries under its own index prefix, so two directories that
|
|
79
|
+
* happen to hold the same relative filenames stay distinguishable.
|
|
80
|
+
*/
|
|
81
|
+
export async function contentHash(paths: string[], salt: string, exclude: Set<string> = new Set()): Promise<string> {
|
|
82
|
+
const h = createHash("sha256");
|
|
83
|
+
h.update(`salt:${salt}\n`);
|
|
84
|
+
const walk = async (root: string, d: string, index: number): Promise<void> => {
|
|
85
|
+
const entries = (await readdir(d, { withFileTypes: true })).sort((a, b) => byteOrder(a.name, b.name));
|
|
86
|
+
for (const e of entries) {
|
|
87
|
+
if (exclude.has(e.name)) continue;
|
|
88
|
+
const p = join(d, e.name);
|
|
89
|
+
if (e.isDirectory()) await walk(root, p, index);
|
|
90
|
+
else if (e.isFile()) {
|
|
91
|
+
h.update(`${index}:${relative(root, p)}\n`);
|
|
92
|
+
h.update(await readFile(p));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
for (const [index, p] of paths.entries()) {
|
|
97
|
+
const st = await stat(p);
|
|
98
|
+
if (st.isDirectory()) await walk(p, p, index);
|
|
99
|
+
else {
|
|
100
|
+
h.update(`${index}:${basename(p)}\n`);
|
|
101
|
+
h.update(await readFile(p));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return h.digest("hex").slice(0, 12);
|
|
105
|
+
}
|
package/src/custodian.lua
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
-- The Custodian's decisions (ADR-0059), run by Envoy's Lua filter on every listener it owns.
|
|
2
2
|
--
|
|
3
3
|
-- Envoy carries the bytes: the CONNECT listener, TLS on both sides, the upstream pools, streaming.
|
|
4
|
-
-- This script makes every choice about a credential, and nothing else.
|
|
5
|
-
--
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
--
|
|
4
|
+
-- This script makes every choice about a credential, and nothing else. It ships once per Instance
|
|
5
|
+
-- in the `jr2-held` ConfigMap (`program.lua`); each pod's own `custodian.lua` is a generated `HELD`
|
|
6
|
+
-- table (names, headers, paths, bound targets — never a value) that ends by loading this file.
|
|
7
|
+
-- Envoy loads that once per Lua state at start, so the top-level code below runs before any
|
|
8
|
+
-- listener serves, with `HELD` already set: a value that is missing or cannot ride a header stops
|
|
9
|
+
-- the start-up with a line that names the secret, never the value.
|
|
9
10
|
--
|
|
10
11
|
-- Four roles, one per route family, read from the route's metadata:
|
|
11
12
|
--
|
package/src/custodian.ts
CHANGED
|
@@ -5,22 +5,29 @@
|
|
|
5
5
|
// Its engine is Envoy, the upstream distroless image pinned by digest: jr2 deploys it and never
|
|
6
6
|
// builds it (ADR-0038). This module is the jr2 half — everything Envoy is told, and where:
|
|
7
7
|
//
|
|
8
|
-
// custodianBootstrap(...) the static bootstrap
|
|
8
|
+
// custodianBootstrap(...) the static bootstrap, one per pod, from that pod's manifest:
|
|
9
9
|
// no admin listener, no xDS, one internal listener per bound host
|
|
10
|
-
// custodianScript(...) the Lua the bootstrap runs:
|
|
11
|
-
//
|
|
10
|
+
// custodianScript(...) the Lua the bootstrap runs: a generated table of names, headers,
|
|
11
|
+
// paths and targets — never a value — then `dofile` of the program
|
|
12
|
+
// CUSTODIAN_PROGRAM `custodian.lua` as written: in `jr2-held` once per Instance
|
|
13
|
+
//
|
|
14
|
+
// Who renders the two per-pod files is who makes the pod: the Orchestrator at provision, into a
|
|
15
|
+
// ConfigMap the Sandbox CR owns; `jr2 up` for the Instance Harness. `jr2 up` DECLARES — each pod's
|
|
16
|
+
// manifest, the program — and nothing rendered per pod lands in a shared object (a ConfigMap
|
|
17
|
+
// stops at 1 MiB; a per-pod copy of Envoy's files reached it at nine Workspaces).
|
|
12
18
|
// custodianComposition(...) what a Harness pod gains: the Custodian container, its volumes, and
|
|
13
19
|
// the Harness container's env and mounts. BOTH placements call it —
|
|
14
|
-
// `
|
|
20
|
+
// `kubeSandbox` per provision, `instanceHarnessObjects` at converge —
|
|
15
21
|
// so the two pod shapes cannot drift
|
|
16
22
|
//
|
|
17
23
|
// The Custodian is a separate container from the Harness, and that is the point: the Agent
|
|
18
24
|
// executes code in the Harness container and nothing here (ADR-0005). What it holds — the
|
|
19
|
-
// Sandbox token and every held secret — is mounted into this container alone, and
|
|
25
|
+
// Sandbox token and every held secret its pod claims — is mounted into this container alone, and
|
|
20
26
|
// `shareProcessNamespace` stays off, so the Agent cannot read its env, its files or its `/proc`.
|
|
21
27
|
|
|
22
28
|
import { readFileSync } from "node:fs";
|
|
23
29
|
import type { HarnessEnvVar } from "./config.ts";
|
|
30
|
+
import type { ResourceRequirements } from "./size.ts";
|
|
24
31
|
import {
|
|
25
32
|
CREDENTIAL_HEADERS,
|
|
26
33
|
SANDBOX_TOKEN_NAME,
|
|
@@ -33,9 +40,10 @@ import {
|
|
|
33
40
|
CUSTODIAN_CONTROL_PORT,
|
|
34
41
|
CUSTODIAN_HEALTH_PORT,
|
|
35
42
|
CUSTODIAN_PORT,
|
|
43
|
+
CUSTODIAN_PROGRAM_KEY,
|
|
36
44
|
HELD_CONFIGMAP,
|
|
37
|
-
HELD_SECRETS_SECRET,
|
|
38
45
|
HELD_TLS_SECRET,
|
|
46
|
+
heldSecretsSecret,
|
|
39
47
|
} from "./names.ts";
|
|
40
48
|
|
|
41
49
|
/**
|
|
@@ -62,7 +70,10 @@ export function custodianRef(kitRegistry?: string): string {
|
|
|
62
70
|
export const CUSTODIAN_VALUES = "/etc/jr2/custodian/values";
|
|
63
71
|
export const CUSTODIAN_TLS = "/etc/jr2/custodian/tls";
|
|
64
72
|
export const CUSTODIAN_CONFIG = "/etc/jr2/custodian/config";
|
|
65
|
-
/** The
|
|
73
|
+
/** The files the Custodian reads under its config mount: its own pod's rendered bootstrap and
|
|
74
|
+
* script (the two keys of the pod's Custodian ConfigMap — `sandboxCustodianConfigMap`,
|
|
75
|
+
* `INSTANCE_CUSTODIAN_CONFIGMAP`), and the fixed program the script loads, projected from
|
|
76
|
+
* `jr2-held` (`CUSTODIAN_PROGRAM_KEY`). */
|
|
66
77
|
export const CUSTODIAN_BOOTSTRAP_KEY = "envoy.json";
|
|
67
78
|
export const CUSTODIAN_SCRIPT_KEY = "custodian.lua";
|
|
68
79
|
|
|
@@ -206,30 +217,72 @@ function meta(fields: Record<string, string>): Json {
|
|
|
206
217
|
return { filter_metadata: { [LUA_FILTER]: fields } };
|
|
207
218
|
}
|
|
208
219
|
|
|
209
|
-
/**
|
|
210
|
-
*
|
|
211
|
-
|
|
220
|
+
/** Envoy's flags for a request it did not send to the target (ADR-0059): the connection failed, no
|
|
221
|
+
* host was healthy, the DNS lookup failed. */
|
|
222
|
+
export const NOT_SENT_FLAGS = ["UF", "UH", "DF"] as const;
|
|
223
|
+
|
|
224
|
+
/** Envoy's flags for a request that may have reached the target before the stream ended (ADR-0059):
|
|
225
|
+
* the target closed the connection or reset the stream, Envoy reset it, a protocol error on either
|
|
226
|
+
* side (a body framing error can come after the headers went). `UMSDR` (the maximum stream
|
|
227
|
+
* duration) ends a stream that was sent. `URX` (the retry limit) comes with the last attempt's own
|
|
228
|
+
* flag (`UF,URX`, `UC,URX`), so alone it says nothing of the send; the mapper for
|
|
229
|
+
* {@link NOT_SENT_FLAGS} comes first, so `UF,URX` still reads "could not be reached". These routes
|
|
230
|
+
* set no retry policy and no `max_stream_duration`, so Envoy sets neither flag on them; they keep
|
|
231
|
+
* the union equal to ORCHESTRATOR_UNREACHABLE's. */
|
|
232
|
+
export const MAY_HAVE_SENT_FLAGS = ["UC", "DPE", "UMSDR", "URX", "UPE", "UR", "LR"] as const;
|
|
233
|
+
|
|
234
|
+
/** One of the Custodian's own 502s for a target that gave no answer (§5.4): the target, what
|
|
235
|
+
* jr2 knows about the request, and Envoy's reason — never a retry without verification. */
|
|
236
|
+
function noAnswer(target: string, flags: readonly string[], words: string): Json {
|
|
212
237
|
return {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
text_format_source: {
|
|
219
|
-
inline_string: `jr2 custodian: ${target} could not be reached: %RESPONSE_CODE_DETAILS% %UPSTREAM_TRANSPORT_FAILURE_REASON%\n`,
|
|
220
|
-
},
|
|
221
|
-
},
|
|
238
|
+
filter: { response_flag_filter: { flags: [...flags] } },
|
|
239
|
+
status_code: 502,
|
|
240
|
+
body_format_override: {
|
|
241
|
+
text_format_source: {
|
|
242
|
+
inline_string: `jr2 custodian: ${target} ${words}: %RESPONSE_CODE_DETAILS% %UPSTREAM_TRANSPORT_FAILURE_REASON%\n`,
|
|
222
243
|
},
|
|
223
|
-
|
|
244
|
+
},
|
|
224
245
|
};
|
|
225
246
|
}
|
|
226
247
|
|
|
248
|
+
const NOT_REACHED = "could not be reached";
|
|
249
|
+
const MAY_HAVE_REACHED = "did not answer; the request may have reached it";
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* An intercepted target that gave no answer (ADR-0059). Two mappers, because the words must be
|
|
253
|
+
* true: after a `NOT_SENT_FLAGS` failure the target did not get the request, and after a
|
|
254
|
+
* `MAY_HAVE_SENT_FLAGS` one it may have it — so an Agent that sends a POST again can write twice,
|
|
255
|
+
* and the text must not tell it the first one went nowhere. One reply can carry several flags,
|
|
256
|
+
* and Envoy uses the first mapper that matches, in order: the `NOT_SENT_FLAGS` mapper is first, so
|
|
257
|
+
* a reply with `UF`, `UH` or `DF` reads "could not be reached" whatever else it carries. Without
|
|
258
|
+
* the mappers, Envoy answers each of these flags itself (503 for most, 502 for `UPE`) with a body
|
|
259
|
+
* that names no target, which a caller can read as the target's answer. No caller reads the flags;
|
|
260
|
+
* the Orchestrator route's go to the Harness, which tells "never reached" from "not known"
|
|
261
|
+
* (ADR-0002).
|
|
262
|
+
*/
|
|
263
|
+
function unreachable(target: string): Json {
|
|
264
|
+
return {
|
|
265
|
+
mappers: [noAnswer(target, NOT_SENT_FLAGS, NOT_REACHED), noAnswer(target, MAY_HAVE_SENT_FLAGS, MAY_HAVE_REACHED)],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** The tunnel's 502 (ADR-0059): every flag, one text. Envoy answers a `CONNECT` with 200 only after
|
|
270
|
+
* its TCP connection to the target is up, and no byte of the Agent's request goes before that 200.
|
|
271
|
+
* So a 502 on the tunnel always comes before the request reached the target, whatever its flag. */
|
|
272
|
+
function tunnelUnreachable(target: string): Json {
|
|
273
|
+
return { mappers: [noAnswer(target, [...NOT_SENT_FLAGS, ...MAY_HAVE_SENT_FLAGS], NOT_REACHED)] };
|
|
274
|
+
}
|
|
275
|
+
|
|
227
276
|
/** The Orchestrator unreachable, said so the Harness can tell it from an answer and retry the
|
|
228
277
|
* surface read the way ADR-0042 asks — the one route on this pod that retries. */
|
|
229
278
|
const ORCHESTRATOR_UNREACHABLE: Json = {
|
|
230
279
|
mappers: [
|
|
231
280
|
{
|
|
232
|
-
|
|
281
|
+
// Envoy's own answers for a stream it reset (`UPE`, `UR`, `LR`: 503, or 502 for `UPE`) are said
|
|
282
|
+
// here too, so the Harness reads them as "not known" and not as a stopping Orchestrator's 503
|
|
283
|
+
// (ADR-0002). One text for
|
|
284
|
+
// both lists: the Harness reads the flags, not the words.
|
|
285
|
+
filter: { response_flag_filter: { flags: [...NOT_SENT_FLAGS, ...MAY_HAVE_SENT_FLAGS] } },
|
|
233
286
|
status_code: 502,
|
|
234
287
|
headers_to_add: [
|
|
235
288
|
{ header: { key: "x-jr2-custodian", value: "unreachable" }, append_action: "OVERWRITE_IF_EXISTS_OR_ADD" },
|
|
@@ -322,7 +375,7 @@ function egressReplies(): Json {
|
|
|
322
375
|
},
|
|
323
376
|
},
|
|
324
377
|
},
|
|
325
|
-
...(
|
|
378
|
+
...(tunnelUnreachable("%REQ(:AUTHORITY)%").mappers as Json[]),
|
|
326
379
|
],
|
|
327
380
|
};
|
|
328
381
|
}
|
|
@@ -356,8 +409,8 @@ function isIpLiteral(host: string): boolean {
|
|
|
356
409
|
* The Custodian's static bootstrap (ADR-0059): Envoy's whole configuration, rendered once per
|
|
357
410
|
* converge by `jr2 up`, identical for every Harness pod of the Instance.
|
|
358
411
|
*
|
|
359
|
-
* control 127.0.0.1:8081 the Menu and the ask, plain HTTP, to the Orchestrator's Service —
|
|
360
|
-
*
|
|
412
|
+
* control 127.0.0.1:8081 the Menu, a pick's answer and the ask, plain HTTP, to the Orchestrator's Service —
|
|
413
|
+
* four routes and nothing else, each swapping the Sandbox token's
|
|
361
414
|
* Stand-in for the token (ADR-0013, ADR-0053)
|
|
362
415
|
* health 0.0.0.0:15021 GET /healthz, for the kubelet, which cannot reach loopback
|
|
363
416
|
* egress 127.0.0.1:15001 CONNECT only — present when a held secret exists; a bound target
|
|
@@ -401,6 +454,11 @@ export function custodianBootstrap(manifest: HeldManifest, orchestrator: Orchest
|
|
|
401
454
|
route("events", "POST", "/agents/[^/]+/events", {
|
|
402
455
|
retry_policy: { retry_on: "reset-before-request,connect-failure", num_retries: 2 },
|
|
403
456
|
}),
|
|
457
|
+
// An outstanding pick's answer, long-polled (ADR-0024): a read, so any reset
|
|
458
|
+
// is retried — an Orchestrator restart mid-poll costs a retry, not the pick.
|
|
459
|
+
route("deliveries", "GET", "/agents/[^/]+/deliveries/[^/]+", {
|
|
460
|
+
retry_policy: { retry_on: "reset,connect-failure", num_retries: 2 },
|
|
461
|
+
}),
|
|
404
462
|
route("fetch", "POST", "/fetch", {
|
|
405
463
|
retry_policy: { retry_on: "reset-before-request,connect-failure", num_retries: 2 },
|
|
406
464
|
}),
|
|
@@ -715,8 +773,11 @@ export function custodianBootstrap(manifest: HeldManifest, orchestrator: Orchest
|
|
|
715
773
|
|
|
716
774
|
// ---- the script ---------------------------------------------------------------------------------
|
|
717
775
|
|
|
718
|
-
/** `custodian.lua` beside this module — the fixed half of the script.
|
|
719
|
-
|
|
776
|
+
/** `custodian.lua` beside this module — the fixed half of the script, as written. `jr2 up` puts it
|
|
777
|
+
* in `jr2-held` once (`CUSTODIAN_PROGRAM_KEY`); every pod's own script loads it from the config
|
|
778
|
+
* mount. The per-pod half is the table alone, so a pod's rendered files stay small and nothing
|
|
779
|
+
* fixed is copied per Workspace. */
|
|
780
|
+
export const CUSTODIAN_PROGRAM: string = readFileSync(new URL("./custodian.lua", import.meta.url), "utf8");
|
|
720
781
|
|
|
721
782
|
/** A Lua string literal. Every string that reaches here passed `heldSecretsOf`'s rules (names,
|
|
722
783
|
* header tokens, printable paths), so escaping the two characters Lua gives meaning to is enough. */
|
|
@@ -729,14 +790,15 @@ function luaList(values: readonly string[]): string {
|
|
|
729
790
|
}
|
|
730
791
|
|
|
731
792
|
/**
|
|
732
|
-
* The script the Custodian runs: a generated `HELD` table
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
793
|
+
* The script the Custodian runs: a generated `HELD` table, then `dofile` of the fixed program
|
|
794
|
+
* (`CUSTODIAN_PROGRAM`, mounted beside it from `jr2-held`). The table names every secret this pod
|
|
795
|
+
* holds — the Sandbox token first, then the manifest's — with the headers and paths each may ride
|
|
796
|
+
* and the targets each is bound to. It never holds a value: the program reads those from the
|
|
797
|
+
* mounted files at start.
|
|
736
798
|
*/
|
|
737
799
|
export function custodianScript(manifest: HeldManifest): string {
|
|
738
800
|
const lines = [
|
|
739
|
-
"-- Generated by `jr2 up` from held
|
|
801
|
+
"-- Generated by `jr2 up` from its pod's held manifest (ADR-0059). Names, headers, paths, targets; never a value.",
|
|
740
802
|
`HELD = { secrets = {}, targets = {}, hosts = ${targetsOf(manifest).length} }`,
|
|
741
803
|
`HELD.credential = ${luaList(CREDENTIAL_HEADERS)}`,
|
|
742
804
|
`HELD.secrets[1] = { name = ${lua(SANDBOX_TOKEN_NAME)}, headers = { "authorization" } }`,
|
|
@@ -750,7 +812,10 @@ export function custodianScript(manifest: HeldManifest): string {
|
|
|
750
812
|
const refs = t.secrets.map((s) => `HELD.secrets[${manifest.secrets.indexOf(s) + 2}]`);
|
|
751
813
|
lines.push(`HELD.targets[${lua(t.key)}] = { ${refs.join(", ")} }`);
|
|
752
814
|
}
|
|
753
|
-
|
|
815
|
+
// `dofile`, not a copy: Envoy's Lua opens the standard libraries (the program already reads its
|
|
816
|
+
// values with `io`), and the program's top-level code runs here, after the table it reads.
|
|
817
|
+
lines.push(`dofile(${lua(`${CUSTODIAN_CONFIG}/${CUSTODIAN_PROGRAM_KEY}`)})`);
|
|
818
|
+
return `${lines.join("\n")}\n`;
|
|
754
819
|
}
|
|
755
820
|
|
|
756
821
|
// ---- the composition ----------------------------------------------------------------------------
|
|
@@ -758,14 +823,30 @@ export function custodianScript(manifest: HeldManifest): string {
|
|
|
758
823
|
export type CustodianCompositionOptions = {
|
|
759
824
|
/** The Custodian's resolved ref (`custodianRef`, from the image map). */
|
|
760
825
|
image: string;
|
|
826
|
+
/** The pod whose held objects these are (ADR-0059): `INSTANCE_HARNESS_HELD_POD`, or a Workspace's
|
|
827
|
+
* `workspaceHeldPod` — its manifest and its literal values' Secret. */
|
|
828
|
+
pod: string;
|
|
829
|
+
/** The ConfigMap with THIS pod's rendered `envoy.json` and `custodian.lua` — written by whoever
|
|
830
|
+
* makes the pod: the Orchestrator at provision (`sandboxCustodianConfigMap`, owned by the CR),
|
|
831
|
+
* `jr2 up` for the Instance Harness (`INSTANCE_CUSTODIAN_CONFIGMAP`). */
|
|
832
|
+
config: string;
|
|
761
833
|
/** Where the Sandbox token lives: a Sandbox's `<name>-token` Secret, or the Instance Harness's key
|
|
762
834
|
* of `jr2-instance`. Mounted into the Custodian alone (ADR-0013). */
|
|
763
835
|
token: { secret: string; key: string };
|
|
764
836
|
/** This pod's Sandbox — the name the ask is addressed by (ADR-0053). Absent on the Instance
|
|
765
837
|
* Harness, which mounts no Repo and answers an ask by saying so. */
|
|
766
838
|
sandbox?: string;
|
|
839
|
+
/** The names of the Instance objects that this Custodian reads in place of the defaults. The
|
|
840
|
+
* defaults are the Instance's own: `pod`'s literal values (`heldSecretsSecret(pod)`),
|
|
841
|
+
* `jr2-held-tls`, `jr2-ca`, and the program in `jr2-held`.
|
|
842
|
+
* The check pod (ADR-0019) reads its own copies, so a check pod writes nothing that a running pod
|
|
843
|
+
* of the Instance reads. */
|
|
844
|
+
reads?: { literals: string; tls: string; trust: string; program: string };
|
|
767
845
|
/** The Instance ships a `harness.caBundle` (ADR-0020). */
|
|
768
846
|
caBundle: boolean;
|
|
847
|
+
/** The Custodian container's resources (ADR-0060) — the placement's to choose: a Sandbox's fixed
|
|
848
|
+
* slice of its Size (requests = limits), the Instance Harness's kit-sized request. */
|
|
849
|
+
resources?: ResourceRequirements;
|
|
769
850
|
};
|
|
770
851
|
|
|
771
852
|
type Container = Record<string, unknown>;
|
|
@@ -804,6 +885,12 @@ const CUSTODIAN_SECURITY = {
|
|
|
804
885
|
export function custodianComposition(manifest: HeldManifest, opts: CustodianCompositionOptions): CustodianComposition {
|
|
805
886
|
const held = manifest.secrets.length > 0;
|
|
806
887
|
const literal = manifest.secrets.filter((s) => s.source.kind === "literal").map((s) => s.name);
|
|
888
|
+
const {
|
|
889
|
+
literals = heldSecretsSecret(opts.pod),
|
|
890
|
+
tls = HELD_TLS_SECRET,
|
|
891
|
+
trust: trustMap = CA_CONFIGMAP,
|
|
892
|
+
program = HELD_CONFIGMAP,
|
|
893
|
+
} = opts.reads ?? {};
|
|
807
894
|
|
|
808
895
|
const harnessEnv: HarnessEnvVar[] = [
|
|
809
896
|
{ name: "JR2_CUSTODIAN_URL", value: CUSTODIAN_URL },
|
|
@@ -837,9 +924,7 @@ export function custodianComposition(manifest: HeldManifest, opts: CustodianComp
|
|
|
837
924
|
defaultMode: 0o440,
|
|
838
925
|
sources: [
|
|
839
926
|
{ secret: { name: opts.token.secret, items: [{ key: opts.token.key, path: SANDBOX_TOKEN_NAME }] } },
|
|
840
|
-
...(literal.length
|
|
841
|
-
? [{ secret: { name: HELD_SECRETS_SECRET, items: literal.map((n) => ({ key: n, path: n })) } }]
|
|
842
|
-
: []),
|
|
927
|
+
...(literal.length ? [{ secret: { name: literals, items: literal.map((n) => ({ key: n, path: n })) } }] : []),
|
|
843
928
|
...manifest.secrets.flatMap((s) =>
|
|
844
929
|
s.source.kind === "secret"
|
|
845
930
|
? [{ secret: { name: s.source.secret, items: [{ key: s.source.key, path: s.name }] } }]
|
|
@@ -848,17 +933,35 @@ export function custodianComposition(manifest: HeldManifest, opts: CustodianComp
|
|
|
848
933
|
],
|
|
849
934
|
},
|
|
850
935
|
},
|
|
851
|
-
|
|
936
|
+
// This pod's own bootstrap and script, under the names Envoy is started with, beside the fixed
|
|
937
|
+
// program the script loads — the one thing every pod shares, from `jr2-held`.
|
|
938
|
+
{
|
|
939
|
+
name: "custodian-config",
|
|
940
|
+
projected: {
|
|
941
|
+
sources: [
|
|
942
|
+
{
|
|
943
|
+
configMap: {
|
|
944
|
+
name: opts.config,
|
|
945
|
+
items: [
|
|
946
|
+
{ key: CUSTODIAN_BOOTSTRAP_KEY, path: CUSTODIAN_BOOTSTRAP_KEY },
|
|
947
|
+
{ key: CUSTODIAN_SCRIPT_KEY, path: CUSTODIAN_SCRIPT_KEY },
|
|
948
|
+
],
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
{ configMap: { name: program, items: [{ key: CUSTODIAN_PROGRAM_KEY, path: CUSTODIAN_PROGRAM_KEY }] } },
|
|
952
|
+
],
|
|
953
|
+
},
|
|
954
|
+
},
|
|
852
955
|
...(held
|
|
853
956
|
? [
|
|
854
|
-
{ name: "custodian-tls", secret: { secretName:
|
|
957
|
+
{ name: "custodian-tls", secret: { secretName: tls, defaultMode: 0o440 } },
|
|
855
958
|
{
|
|
856
959
|
name: "custodian-trust",
|
|
857
|
-
configMap: { name:
|
|
960
|
+
configMap: { name: trustMap, items: [{ key: "upstream.crt", path: "upstream.crt" }] },
|
|
858
961
|
},
|
|
859
962
|
]
|
|
860
963
|
: []),
|
|
861
|
-
...(trust ? [{ name: "ca", configMap: { name:
|
|
964
|
+
...(trust ? [{ name: "ca", configMap: { name: trustMap } }] : []),
|
|
862
965
|
];
|
|
863
966
|
|
|
864
967
|
const custodianContainer: Container = {
|
|
@@ -887,6 +990,7 @@ export function custodianComposition(manifest: HeldManifest, opts: CustodianComp
|
|
|
887
990
|
periodSeconds: 2,
|
|
888
991
|
failureThreshold: 15,
|
|
889
992
|
},
|
|
993
|
+
...(opts.resources !== undefined ? { resources: opts.resources } : {}),
|
|
890
994
|
securityContext: CUSTODIAN_SECURITY,
|
|
891
995
|
};
|
|
892
996
|
|