@jr2/orchestrator 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +86 -9
- package/docs/concepts/architecture.md +41 -28
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +7 -5
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +4 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +50 -22
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +5 -4
- package/docs/concepts/pools-and-sources.md +187 -11
- package/docs/concepts/secrets-and-trust.md +73 -33
- package/docs/concepts/the-menu.md +11 -0
- package/docs/concepts/workspaces.md +6 -5
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/guides/add-tools-to-the-sandbox.md +55 -11
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +24 -8
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/fix-a-failed-converge.md +18 -6
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +9 -6
- package/docs/guides/look-inside-a-sandbox.md +101 -31
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-one-worker-per-item.md +15 -9
- package/docs/guides/set-an-agents-access-and-dials.md +2 -0
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +1 -1
- package/docs/guides/watch-a-run.md +127 -121
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +17 -7
- package/docs/llms.txt +81 -15
- package/docs/reference/authoring-surface/agent.md +1 -1
- package/docs/reference/authoring-surface/answer.md +1 -1
- package/docs/reference/authoring-surface/arg.md +1 -1
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +1 -1
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +1 -1
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +1 -1
- package/docs/reference/authoring-surface/file.md +1 -1
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +1 -1
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +1 -1
- package/docs/reference/authoring-surface/jr2setup.md +13 -3
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +5 -3
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +1 -1
- package/docs/reference/authoring-surface/pool.md +77 -9
- package/docs/reference/authoring-surface/push.md +1 -1
- package/docs/reference/authoring-surface/readyof.md +1 -1
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +1 -1
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +56 -1
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +14 -1
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
- package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-binding.md +1 -1
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandoutput.md +1 -1
- package/docs/reference/authoring-surface/type-customize.md +16 -1
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +1 -1
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-framepath.md +1 -1
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +1 -1
- package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +1 -1
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +16 -7
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +1 -1
- package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
- package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
- package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
- package/docs/reference/authoring-surface/workspace.md +1 -1
- package/docs/reference/authoring-surface/worktree.md +1 -1
- package/docs/reference/authoring-surface.md +88 -167
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +235 -2
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
- package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +9 -2
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +18 -14
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +32 -18
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +116 -34
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +7 -6
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -18
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +4 -3
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +5 -4
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +7 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +51 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +17 -13
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +6 -4
- package/docs/reference/glossary.md +96 -42
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +976 -149
- package/docs/why.md +2 -1
- package/package.json +10 -3
- package/src/actor.ts +36 -10
- package/src/agent.ts +50 -0
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +52 -7
- package/src/config.ts +550 -1
- package/src/conversations.ts +453 -0
- package/src/custodian.ts +8 -2
- package/src/customize.ts +143 -4
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +2 -1
- package/src/effect.ts +60 -7
- package/src/effector-wire.ts +272 -27
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +10 -4
- package/src/held-secrets.ts +72 -23
- package/src/holds.ts +2 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +76 -38
- package/src/http.ts +889 -94
- package/src/index.ts +16 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +22 -3
- package/src/machine-doc.ts +26 -1
- package/src/mcp-effect.ts +424 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +64 -2
- package/src/notices.ts +138 -10
- package/src/open-body.ts +25 -11
- package/src/open-effect.ts +17 -4
- package/src/open.ts +4 -2
- package/src/parts.ts +87 -9
- package/src/party.ts +2 -1
- package/src/pick.ts +2 -2
- package/src/pool.ts +1142 -163
- package/src/registration.ts +203 -13
- package/src/run-host.ts +1509 -171
- package/src/run-list.ts +191 -0
- package/src/sandbox-kube.ts +53 -5
- package/src/secret-placement.ts +239 -33
- package/src/server.ts +53 -4
- package/src/setup.ts +72 -32
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +263 -22
- package/src/wire.ts +17 -0
- package/src/workspace.ts +50 -6
- package/src/worktree.ts +3 -3
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# The Console unlocks with a Caller's credential, and shows what its scopes allow
|
|
2
|
+
|
|
3
|
+
[ADR-0014](0014-observation-is-open-run-state-is-not.md) built the HTTP surface on one premise: the visualizer page is a
|
|
4
|
+
browser, and a browser holds no token — so it got a projection instead of a credential, and "give the page a token" was
|
|
5
|
+
explicitly rejected. That page is now the **Console**: it starts runs, answers Gates and cancels runs, which are writes
|
|
6
|
+
on the guarded surface. This ADR records how a browser earns those writes without moving the open band. The person
|
|
7
|
+
presents a credential; the page never ships with one.
|
|
8
|
+
|
|
9
|
+
## Decision
|
|
10
|
+
|
|
11
|
+
- **The person presents a Caller's credential; the page never ships with one.** The Console's nav carries a password
|
|
12
|
+
field, and a login button for each JWT issuer that the Instance declares with `login: true`
|
|
13
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). Empty, the page is
|
|
14
|
+
precisely ADR-0014's observer: open structure and observation, no control rendered. With a credential, the page sends
|
|
15
|
+
`Authorization: Bearer` on the guarded surface. The credential is any Caller's: the Instance token, a named Caller
|
|
16
|
+
token, or a JWT. The login gets a JWT from the issuer with OIDC (the authorization code flow with PKCE, as a public
|
|
17
|
+
client), and the page then uses it as it uses a typed token. The page as _served_ stays open and inert; ADR-0014's
|
|
18
|
+
rejection stands, because what it rejected was baking the credential into the page.
|
|
19
|
+
- **The login's facts come from an open route, and its client id is the issuer's `audience`.** Before a login, the page
|
|
20
|
+
reads `GET /console/config`: `{ login: [{ issuer, clientId }] }`, from the issuers of the current Assembly that have
|
|
21
|
+
`login: true`. The route is in the open band (ADR-0014), because the page reads it before it holds a credential, and
|
|
22
|
+
both values are public: a public client has no secret, and the issuer's URL is in every JWT that it signs. The client
|
|
23
|
+
id is the issuer entry's `audience`, because the JWT that the login gives names its client as its audience, and that
|
|
24
|
+
is what the JWT authenticator checks. A login is opt-in for each issuer, because an issuer for machines (a CI
|
|
25
|
+
provider's tokens) has no login page. The page reads the issuer's discovery document, asks for `openid`, `profile`,
|
|
26
|
+
`email` and `groups` where the issuer lists each, and sends the ID token as the bearer: an access token's audience and
|
|
27
|
+
form differ between providers. The return address is always the Console's root (`/`), so the client registers one
|
|
28
|
+
redirect URI; the page then puts the address back to where the login began. The verifier, the state and that address
|
|
29
|
+
wait in `sessionStorage` until the return, and a return whose state does not match is refused. PKCE needs
|
|
30
|
+
`crypto.subtle`, so a login needs the Console on https or on localhost, and the issuer must let the Console's origin
|
|
31
|
+
call its discovery document and its token endpoint (CORS).
|
|
32
|
+
- **The page shows only what the Caller's scopes allow.** On entry the page asks `GET /whoami` for the Caller record:
|
|
33
|
+
its name, the name that jr2 shows, and its scopes for each Workflow. It renders a control only where a scope opens its
|
|
34
|
+
route: the start form with `start` for that Workflow, Gate cards with `answer`, cancel with `cancel`, a run's full
|
|
35
|
+
state with `read`, and the Steering box with `steer`. The page re-fetches a run on a frame only for a Caller with
|
|
36
|
+
`answer` or `read` on its Workflow. The nav shows who the page acts as: the name that jr2 shows, else `<via>/<name>`,
|
|
37
|
+
with the scopes in its title, and a sign-out button in place of the field. The server still checks each request
|
|
38
|
+
(ADR-0014); the page hides a control so that a person does not meet a 403, not as a guard.
|
|
39
|
+
- **The credential lives in `sessionStorage`** — survives a reload, dies with the tab, never a cookie (no ambient
|
|
40
|
+
credential, no CSRF surface). A typed token and a login's JWT live there alike. A later 401 (an expired JWT, a rotated
|
|
41
|
+
token) drops the UI back to observer mode rather than failing per widget.
|
|
42
|
+
- **Gate discovery is a frame-triggered guarded read, not an open-band extension.** A Gate exists exactly while its
|
|
43
|
+
invoking state is entered ([ADR-0011](0011-workflow-defined-events.md)), and every entry/exit lands a status frame on
|
|
44
|
+
the open workflow feed (ADR-0022) — so a Console whose Caller has `answer` reads `GET /gates` on entry and again on
|
|
45
|
+
each frame, and folds the Gates into its store. `GET /gates` lists only the Gates that the Caller may answer, across
|
|
46
|
+
every Workflow (ADR-0078), so the Gate cards are the person's inbox and never show a Gate the person cannot answer.
|
|
47
|
+
Synchronized, no polling loop, and the observation projection is untouched. A delivery's success is likewise confirmed
|
|
48
|
+
by the next frame emptying the card, never by local bookkeeping.
|
|
49
|
+
- **The rail lists ended runs too.** It lists the live runs and, under them, the ended runs of the selected Workflow
|
|
50
|
+
from `GET /runs?status=ended&workflow=<name>` (ADR-0009), each with its status, and a `drifted` run with its reason
|
|
51
|
+
(ADR-0030). An ended run opens as a live one does: its graph, its last status and its conversations, for as long as
|
|
52
|
+
jr2 keeps the run (ADR-0065). A run on an older Assembly carries the mark "older Assembly · <hash>" and is drawn on
|
|
53
|
+
its own Machine, live or ended: an ended run's row names the Machine it ran on
|
|
54
|
+
([ADR-0069](0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md)). A `drifted` run has no
|
|
55
|
+
Machine to draw, so it shows its drift reason in place of the graph. The Console shows each outstanding pick as
|
|
56
|
+
"outstanding since".
|
|
57
|
+
- **A conversation opens in a bottom drawer.** A full-width drawer across the bottom of the page shows the run's
|
|
58
|
+
conversations ([ADR-0023](0023-the-harness-prints-the-conversation.md)). A person can change its height, and the page
|
|
59
|
+
keeps the height for that viewer. Closed, it is a bar of tabs, one for each Agent slot, with a dot on a slot whose
|
|
60
|
+
Turn is live. Open, it shows the selected slot's conversation and follows a live Turn, with the Steering box beside it
|
|
61
|
+
for a Caller with `steer`. It reads the conversation routes, so it needs `read`. The right drawer keeps Attention and
|
|
62
|
+
Activity.
|
|
63
|
+
- **Page addresses content-negotiate; JSON is the default dialect.** `/` and `/workflows/:name` serve the Console shell
|
|
64
|
+
to `Accept: text/html` and JSON to everything else — so the workflow-detail resource keeps its natural address (the
|
|
65
|
+
run-input schema's home, [ADR-0033](0033-a-machine-declares-the-input-that-starts-it.md)) and no future page has to
|
|
66
|
+
squat on a mangled sibling path. Nothing existing moves: EventSource asks `text/event-stream`, and the Harness's Menu
|
|
67
|
+
and the CLI never ask for HTML. `/viz/*` is deleted (greenfield, no redirects); assets move to `/assets/*`, which also
|
|
68
|
+
retires the "a workflow named `assets` is shadowed" hazard.
|
|
69
|
+
|
|
70
|
+
## Considered options
|
|
71
|
+
|
|
72
|
+
- **`jr2 ui` — the CLI opens the browser through an authenticated local proxy.** A convenience layer worth building _on
|
|
73
|
+
top of_ the token field later, not a substitute: it has no answer for the deployed jr2 Application behind an ingress,
|
|
74
|
+
where the browser talks to the Service directly and the CLI is not in the path.
|
|
75
|
+
- **The Instance token alone.** Rejected (ADR-0078): a person who must answer Gates and holds no cluster RBAC would then
|
|
76
|
+
hold every act on every run. A scoped Caller's credential is what that person holds.
|
|
77
|
+
- **The conversation in the right drawer.** Rejected: a conversation needs the page's width, and the right drawer keeps
|
|
78
|
+
Attention and Activity.
|
|
79
|
+
- **Announce gates on the open observation feed** (even just `{runId, gate}`). Rejected: it widens the projection for a
|
|
80
|
+
convenience the credential already solves, and the open band has an unresolved exposure question as it stands (spawn
|
|
81
|
+
ids — the ADR-0014 open call). The projection stays the one place the line is drawn.
|
|
82
|
+
- **A `/ui` path prefix, or moving the API under `/api`.** Rejected for negotiation: the first is ceremony on the
|
|
83
|
+
address a human types, the second churns the Harness's Menu, the Custodian's route allowlist, the CLI, and the
|
|
84
|
+
ADR-0009/0013 route tables for cosmetics.
|
|
85
|
+
|
|
86
|
+
## Consequences
|
|
87
|
+
|
|
88
|
+
- **No new band.** The server-side diff of the Console itself is: negotiate two addresses, serve the shell, retire
|
|
89
|
+
`/viz/*`, and serve `GET /console/config` in the open band. The scopes, `GET /whoami` and the login's issuers are
|
|
90
|
+
ADR-0078's.
|
|
91
|
+
- **Possession follows the credential.** The Instance token stays one `kubectl get secret` away for whoever holds
|
|
92
|
+
cluster RBAC; a named token is what its holder was given; a login is what the identity provider's groups grant.
|
|
93
|
+
- **The XSS caveat is the real one and is bounded by what the page is:** a credential in `sessionStorage` is readable by
|
|
94
|
+
scripts on the page, so the Console must keep serving only its own assets — no CDN, no third-party script, exactly the
|
|
95
|
+
constraint the viz page already lives under.
|
|
96
|
+
- **GLOSSARY.md**: **Console** is a glossary term (visualizer, dashboard, UI, viz on its Avoid list).
|
|
97
|
+
- **The Console's UX gets a browser tier** — `@console`-tagged Cucumber scenarios holding a Playwright page, in
|
|
98
|
+
`@jr2/e2e`, excluded from the default profile ([ADR-0010](0010-bdd-acceptance-tests.md) as amended). What the page
|
|
99
|
+
_believes_ stays unit-tested in `console-store.test.ts`, and the login's pure half in `console-login.test.ts`; the
|
|
100
|
+
browser tier asserts what a user does and sees, and the login against Dex in a container (ADR-0078). The login
|
|
101
|
+
scenario is also tagged `@dex`, because it needs docker, which the rest of the tier does not.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
`POST /workflows/:name/runs` takes a JSON body and hands it to the machine as xstate `input` — unvalidated, and
|
|
4
4
|
undescribed: the module contract is `export const machine` alone (ADR-0011/0015), so nothing at runtime says what a run
|
|
5
5
|
of a given workflow should be started _with_. The Console's start form
|
|
6
|
-
([ADR-0032](0032-the-console-unlocks-with-
|
|
6
|
+
([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)) forced the question, but the gap predates it —
|
|
7
7
|
`jr2 run` has the same blindness, and a typo'd input surfaces as a confusing mid-run failure instead of a refusal at the
|
|
8
8
|
door. Meanwhile every event a machine _accepts_ already carries a zod schema, attached to the machine itself
|
|
9
9
|
([ADR-0015](0015-authoring-surface-absorbs-the-mechanism.md)'s vocabulary-on-the-machine). The input that starts a
|
|
@@ -93,8 +93,9 @@ machine is the one piece of its vocabulary that pattern missed.
|
|
|
93
93
|
- **The door takes an idempotency key, out of band.** A start may carry one in a header, never in the body (a body field
|
|
94
94
|
would be one every door had to reserve); a repeat with the same key answers with the run the first one made. A
|
|
95
95
|
`dispatch` Effect sends its ledger row's id ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), so a
|
|
96
|
-
start whose answer was lost is sent once where it may have arrived (ADR-0067) and makes one run.
|
|
97
|
-
|
|
96
|
+
start whose answer was lost is sent once where it may have arrived (ADR-0067) and makes one run. With a second header,
|
|
97
|
+
a start is a key-only ask: the door answers the run under the key, or that no run has it, and starts nothing.
|
|
98
|
+
`jr2 run` and the Console send none.
|
|
98
99
|
|
|
99
100
|
## Considered options
|
|
100
101
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# The Console renders with Preact, and the server erases the types
|
|
2
2
|
|
|
3
|
-
The Console ([ADR-0032](0032-the-console-unlocks-with-
|
|
4
|
-
reached ~1,500 lines, much of it hand-rolled DOM reconciliation (`memo()`, `paint()`, manual list diffing) —
|
|
5
|
-
library exists to own. Three standing constraints shaped the replacement: the repo is zero-build (Node 24
|
|
6
|
-
nothing compiles), the Console serves only its own assets (ADR-0032), and `@jr2/orchestrator` is a library
|
|
7
|
-
its files from disk wherever the process starts. A fourth arrived with this decision: Console source is
|
|
8
|
-
everything else.
|
|
3
|
+
The Console ([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)) outgrew its framework-free form:
|
|
4
|
+
`main.js` reached ~1,500 lines, much of it hand-rolled DOM reconciliation (`memo()`, `paint()`, manual list diffing) —
|
|
5
|
+
work a view library exists to own. Three standing constraints shaped the replacement: the repo is zero-build (Node 24
|
|
6
|
+
strips types; nothing compiles), the Console serves only its own assets (ADR-0032), and `@jr2/orchestrator` is a library
|
|
7
|
+
that serves its files from disk wherever the process starts. A fourth arrived with this decision: Console source is
|
|
8
|
+
TypeScript like everything else.
|
|
9
9
|
|
|
10
10
|
## Decision
|
|
11
11
|
|
|
@@ -81,10 +81,11 @@ every build) and promotes incidental image properties into contract items.
|
|
|
81
81
|
because jr2's file supplies one; such an image sets `GIT_CONFIG_SYSTEM=/etc/gitconfig` itself, and jr2 then adds
|
|
82
82
|
nothing. With the variable set, `git config --system <key> <value>` writes jr2's file on the read-only volume and
|
|
83
83
|
fails (exit 4); without it, an Agent that runs as root wrote `/etc/gitconfig`. A `--global` or repo-local write works
|
|
84
|
-
as before. A
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`
|
|
84
|
+
as before. A raw `kubectl exec` shell does not get the variable, as it does not get the PATH append;
|
|
85
|
+
`jr2 workspace shell` starts through the spawner, as an Agent's `bash` does (ADR-0009). jr2's other git is out of
|
|
86
|
+
scope: the sync runs under `GIT_CONFIG_NOSYSTEM` (ADR-0067); the Sandbox Effector is another container with its own
|
|
87
|
+
committer rules (ADR-0068), and a credentialed `command` Effect runs there with `PATH`, `HOME` and `TMPDIR` alone, so
|
|
88
|
+
it gets no default identity.
|
|
88
89
|
|
|
89
90
|
- **The contract is the floor, and the preflight proves it.** What is inherent to "jr2's runtime in your filesystem" and
|
|
90
91
|
nothing more: a **glibc** base no older than the one jr2's node was built against (musl is out entirely), **`git`** on
|
|
@@ -47,27 +47,27 @@ because they are one problem: **what a Machine depends on that is not inside it.
|
|
|
47
47
|
ADR-0011 adopted for Vocabulary.
|
|
48
48
|
- **Customization is `customize(machine, parts)`**, a plain function in the family of `workspace()` and `pool()`,
|
|
49
49
|
returning a plain `StateMachine`. `parts` has the declaration's own shape, recursively partial:
|
|
50
|
-
`{ agents?: { <slot>?: Partial<AgentDefinition> }, effects?: { <slot>?: { url } | { argv } | { secret } }, image?, user?, repos?: { <slot>?: Binding }, actors?: { <child>?: parts } }`
|
|
50
|
+
`{ agents?: { <slot>?: Partial<AgentDefinition> }, effects?: { <slot>?: { url } | { argv } | { secret } | { server } | { tool } }, image?, user?, repos?: { <slot>?: Binding }, actors?: { <child>?: parts } }`
|
|
51
51
|
— the two image seats together, since ADR-0005 gives the User Container the same static option, and the Repo Slots
|
|
52
52
|
beside them, since ADR-0051 makes a Repo a `workspace()` slot a composer binds. `effects` binds an Effect's Open url,
|
|
53
|
-
argv or secret name, once — an Effect with an Open secret and an Open url or argv takes one
|
|
54
|
-
`{ url, secret }` or `{ argv, secret }`
|
|
55
|
-
([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)): the
|
|
56
|
-
again with the bound part and substituted as an Agent override is. Internally an Agent override
|
|
57
|
-
`machine.provide({ actors: { researcher: agent(merged) } })`, with the override layered over the stock
|
|
58
|
-
child override is the same call one level down, recursing — each level is exactly the one-level reach
|
|
59
|
-
`provide` has, held by the composer who owns the child object, never host-side injection. jr2's
|
|
60
|
-
transparent: `customize(research, { agents })` reaches the body through the `body` slot (and `pool()`'s
|
|
61
|
-
any depth), so a consumer never writes `body`. The images and the Repo bindings are the parts `provide`
|
|
62
|
-
(xstate copies only implementations, and every part is keyed on the config it passes through by
|
|
63
|
-
fields clone the wrapper's config and rebuild it with the same implementations, re-attaching what
|
|
64
|
-
carried. A bound argv's load warnings are such a part too: `customize()` rebuilds the Machine the author
|
|
65
|
-
same way, with the vocabulary, the seats and the door it carried and the warnings the walk gives for the
|
|
66
|
-
so no attachment is keyed on anything but the config. Only DECLARED parts can be retuned, and the compiler
|
|
67
|
-
says so: the slots are read off xstate's own `TActor` parameter, so an Agent, a Repo Slot, or a child the
|
|
68
|
-
not carry is a type error (ADR-0050), and the runtime refuses the same call naming the Machine's own
|
|
69
|
-
a `.with()` method on the machine — it needs a jr2-owned machine type over xstate's, which ADR-0015
|
|
70
|
-
vanishes after any `.provide()`; a callable-machine hybrid — verified to work, reads as a trick.
|
|
53
|
+
argv, MCP server and tool, or secret name, once — an Effect with an Open secret and an Open url or argv takes one
|
|
54
|
+
object with both keys, `{ url, secret }` or `{ argv, secret }`, and an `mcp` Effect with an Open server and tool takes
|
|
55
|
+
`{ server, tool }` ([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)): the
|
|
56
|
+
slot's logic is built again with the bound part and substituted as an Agent override is. Internally an Agent override
|
|
57
|
+
is xstate's `machine.provide({ actors: { researcher: agent(merged) } })`, with the override layered over the stock
|
|
58
|
+
definition; a child override is the same call one level down, recursing — each level is exactly the one-level reach
|
|
59
|
+
ADR-0015 found `provide` has, held by the composer who owns the child object, never host-side injection. jr2's
|
|
60
|
+
wrappers are transparent: `customize(research, { agents })` reaches the body through the `body` slot (and `pool()`'s
|
|
61
|
+
`worker`, at any depth), so a consumer never writes `body`. The images and the Repo bindings are the parts `provide`
|
|
62
|
+
cannot carry (xstate copies only implementations, and every part is keyed on the config it passes through by
|
|
63
|
+
reference), so those fields clone the wrapper's config and rebuild it with the same implementations, re-attaching what
|
|
64
|
+
the original carried. A bound argv's load warnings are such a part too: `customize()` rebuilds the Machine the author
|
|
65
|
+
wrote the same way, with the vocabulary, the seats and the door it carried and the warnings the walk gives for the
|
|
66
|
+
bound argv, so no attachment is keyed on anything but the config. Only DECLARED parts can be retuned, and the compiler
|
|
67
|
+
is what says so: the slots are read off xstate's own `TActor` parameter, so an Agent, a Repo Slot, or a child the
|
|
68
|
+
Machine does not carry is a type error (ADR-0050), and the runtime refuses the same call naming the Machine's own
|
|
69
|
+
slots. Rejected: a `.with()` method on the machine — it needs a jr2-owned machine type over xstate's, which ADR-0015
|
|
70
|
+
avoided, and it vanishes after any `.provide()`; a callable-machine hybrid — verified to work, reads as a trick.
|
|
71
71
|
- **A package exports a Machine, nothing beside it.** The door (ADR-0033), the vocabulary, the Agents, and the image all
|
|
72
72
|
ride the exported object. The three uses:
|
|
73
73
|
|
|
@@ -17,7 +17,12 @@ a definition lives, and the gate that enforces it.
|
|
|
17
17
|
an Effect names it with `secret("name")`, and the Instance declares it in the top-level `secrets` list
|
|
18
18
|
([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). The name is a string, not a type: the
|
|
19
19
|
walk after the typecheck refuses an Effect that names a secret the list does not hold, before anything is built.
|
|
20
|
-
`jr2.config.ts` holds reach and credentials (`harness`, `secrets`, `git.credentials`, `registry
|
|
20
|
+
`jr2.config.ts` holds reach and credentials (`harness`, `secrets`, `git.credentials`, `registry`, and the Callers'
|
|
21
|
+
`callers`, `hooks` and `cors`,
|
|
22
|
+
[ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). A Machine names no Hook: a
|
|
23
|
+
Gate's or a Source's handlers are keyed by the Hook's kind ([ADR-0011](0011-workflow-defined-events.md)), and only a
|
|
24
|
+
Hook's address names it. A Hook kind is code that the config and the Workflows import by name, so it is neither
|
|
25
|
+
declared nor discovered: it comes in by `import`, as a nested Machine does, and `jr2 up` bundles it into the Assembly.
|
|
21
26
|
- **The slot key types a Repo.** A `workspace()` declares its Repos as slots —
|
|
22
27
|
`repos: { target: open, docs: { url, ref } }` — and the keys are a phantom on the wrapper type, read through
|
|
23
28
|
`pool()`'s `worker` and `workspace()`'s `body` as the image is (ADR-0049). A
|
|
@@ -31,48 +31,63 @@ the one part ADR-0049/0051 left a package unable to leave unsaid: **the model.**
|
|
|
31
31
|
who never reads the file spends money on a model they did not choose, silently, which is the failure class `jr2 up`
|
|
32
32
|
exists to make loud. Rejected: a Machine factory `task({ model })` — ADR-0049 settled that a package exports a Machine
|
|
33
33
|
and retunes with `customize`, and a factory reopens that.
|
|
34
|
-
- **Open widens to an Effect's url, argv and secret, bound once.** A package cannot know the host
|
|
35
|
-
calls, the program its `command` runs
|
|
34
|
+
- **Open widens to an Effect's url, argv, MCP server and tool, and secret, bound once.** A package cannot know the host
|
|
35
|
+
its `http` Effect calls, the program its `command` runs, the MCP server and tool its `mcp` Effect calls
|
|
36
36
|
([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)), or the secret its
|
|
37
37
|
composer spends, so it may leave each Open: `http({ method, url: open, auth, input, request })`,
|
|
38
|
-
`http({ method: "PUT", url: open, body: file(path) })`, `command(open, { env, pin })`,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
`
|
|
43
|
-
|
|
44
|
-
(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
and
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
38
|
+
`http({ method: "PUT", url: open, body: file(path) })`, `command(open, { env, pin })`,
|
|
39
|
+
`mcp.call(open, open, { input })`, and `secret(open)` in place of a secret's name. The url, the argv, an `mcp`
|
|
40
|
+
Effect's server and tool, and the secret's name are the Open parts of an Effect that exist; everything else the
|
|
41
|
+
definition says is the package's as written — the method, the headers, the auth and the secrets they name, the input,
|
|
42
|
+
`request`, the env, `pin` — and is checked when the Machine is built. `secret(open)` is taken by `bearer`, `basic`
|
|
43
|
+
(the user or the password), a header's value, and a command's env, as its value or its `.path`; in a url it is refused
|
|
44
|
+
when the Machine is built ("leave the whole url Open"), since a url that carries a secret is left Open whole (below).
|
|
45
|
+
An Effect takes one Open secret: a second `secret(open)` in one definition is refused when the Machine is built ("an
|
|
46
|
+
Effect takes one Open secret"); the one `secret(open)` may stand in several places, a value and its `.path`. An Effect
|
|
47
|
+
that names an Open secret is placed as one that names a secret is — a credentialed command in the Sandbox Effector, an
|
|
48
|
+
`http` where its body places it — before the name is known. The composer binds the parts with
|
|
49
|
+
`customize(machine, { effects: { <slot>: { url } } })`, `{ argv }`, `{ secret }`, `{ server }` or `{ tool }`, and an
|
|
50
|
+
Effect with two Open parts takes one object with both keys (`{ url, secret }`, `{ argv, secret }`,
|
|
51
|
+
`{ server, tool }`): a url is a string or a `text` with a secret in its path, an argv is the usual list, and a secret
|
|
52
|
+
is a name that obeys `secret()`'s own rule — it is the secret's file in the Effector's mount (ADR-0068) — and a server
|
|
53
|
+
is a name of the `mcp` block of `jr2.config.ts`, whose secret is the server's own, so an `mcp` Effect takes no
|
|
54
|
+
`secret(open)`
|
|
55
|
+
([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). The
|
|
56
|
+
type of `effects` is the intersection of the Open parts' bindings, so a binding that leaves one of them unbound is a
|
|
57
|
+
compile error, as an unbound url is; at run time, a binding that leaves one unbound gives an Effect still Open in it,
|
|
58
|
+
which a later `customize()` may bind. Each is bound ONCE, to a value: never a mapper over the door, so a host is fixed
|
|
59
|
+
in the definition or bound before any run, and no run chooses one, nor a secret. Binding builds the Effect again with
|
|
60
|
+
its constructor, with the bound name in every place `secret(open)` stood, so a bound part meets every rule the
|
|
61
|
+
constructor checks — a literal host, no secret in a query, a non-empty argv, a secret's name — and the bound Effect is
|
|
62
|
+
the one the same definition written with that name gives; `jr2 up` reads it as written: the placement, the secrets it
|
|
63
|
+
names, and the Effector's fence ([ADR-0068](0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)) are the
|
|
64
|
+
bound ones, and the bound secret is placed only in that Effect's Effector, as a named secret is. Only an Open part is
|
|
65
|
+
offered: a part the author fixed is no key of `effects` and is refused at the call, and a part bound once is fixed
|
|
66
|
+
from then on, so a second composer cannot move it. `effects` reaches through `workspace()`'s body, `pool()`'s worker
|
|
67
|
+
and a composed Machine's `actors`, as `agents` does, typed off the slot's logic. An Open argv's slots are the
|
|
68
|
+
composer's, so the load walk counts every pick param as read by it when the Machine is built, and `customize()` runs
|
|
69
|
+
the walk's pick-param check again once the argv is bound — a param the bound argv does not read is refused there, and
|
|
70
|
+
a warning it gives for the bound argv is the bound Machine's, as it is the same Machine's written with that argv: the
|
|
71
|
+
bound Machine gets a config of its own, which carries those warnings
|
|
72
|
+
([ADR-0049](0049-a-machine-carries-its-parts-and-composes-by-invoke.md)), so a copy `provide()` makes of it — a run's
|
|
73
|
+
— keeps them, and the Open Machine keeps its own. The compiler's check does not reach a bound argv: an Open argv's
|
|
74
|
+
input is one string per key, so nothing at build or at `customize()` checks that the invoke supplies each slot the
|
|
75
|
+
bound argv names, and a slot it does not supply faults when the Effect starts — its state's `onError`, with nothing
|
|
76
|
+
run. The `jr2 up` walk reports an Open Effect beside Open Repo Slots and Agents and refuses the converge naming the
|
|
77
|
+
`effects` line — `{ effects: { <slot>: { secret: "<name>" } } }` for an Open secret, one object with both keys for an
|
|
78
|
+
Effect with two Open parts, `{ effects: { <slot>: { server: "<name>" } } }`, `{ tool: "<tool>" }` or both for an `mcp`
|
|
79
|
+
Effect, with only its Open keys; the Effect refuses to run while Open, as the second fence — its state's `onError`,
|
|
80
|
+
with nothing sent and no ledger row. The Open part is the whole url, the whole argv, an MCP server's name or a tool's
|
|
81
|
+
name, or the secret's name.
|
|
82
|
+
- **Open widens to a hooked Gate's look, which only warns.** A package writes the Hook handlers of its Gate slots for
|
|
83
|
+
each forge that it supports, because a forge's payload is public, so a handler is never Open. It imports the Hook kind
|
|
84
|
+
of each forge that it hears, for the payload types, and it has `@jr2/orchestrator`, which holds the kinds, as a peer
|
|
85
|
+
dependency ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). Which Hooks of
|
|
86
|
+
those kinds exist is the Instance's config. A look needs a url and a token, so a package leaves `look: open`, and the
|
|
87
|
+
composer binds it once, to a look or `"none"`, with `customize(machine, { gates: { <slot>: { look } } })`, which can
|
|
88
|
+
also add, replace or remove a handler by kind and event key ([ADR-0011](0011-workflow-defined-events.md)). An Open
|
|
89
|
+
look that nobody binds is the one Open part that `jr2 up` does not refuse: it warns, as it warns about a hooked Gate
|
|
90
|
+
with no look, because the Gate still works when a person answers it with `jr2 send`.
|
|
76
91
|
- **The per-run Dial is the escape hatch, not the default.** A shipped Machine's door may carry `model` and
|
|
77
92
|
`thinkingLevel` (ADR-0018's two Dials) and fold them into each invoke's input; the bound definition is what `jr2 up`
|
|
78
93
|
preflights, the door value overrides it for one run's Turns. Nothing in jr2 changes for this — it is the Machine's own
|
|
@@ -16,10 +16,10 @@ version, images, gate, trigger.
|
|
|
16
16
|
an npmjs credential is the release job, and it holds no token: it publishes with npm trusted publishing (OIDC from
|
|
17
17
|
GitHub Actions), so nothing long-lived exists to leak. A `pnpm -r publish` on a dev box reaches npmjs and fails
|
|
18
18
|
`ENEEDAUTH`, which is the guard — an absence that cannot be edited past, where the manifest line was a presence that
|
|
19
|
-
had to be. `publishConfig.access: public` stays on
|
|
20
|
-
it. `scripts/dist-publish.sh` passes `--registry` again, and `publish.test.ts` inverts: it asserts that
|
|
21
|
-
manifest names a registry, so the old guard cannot be smuggled back in one package and silently pin a
|
|
22
|
-
localhost.
|
|
19
|
+
had to be. `publishConfig.access: public` stays on every public package, because a scoped package's first publish is
|
|
20
|
+
402 without it. `scripts/dist-publish.sh` passes `--registry` again, and `publish.test.ts` inverts: it asserts that
|
|
21
|
+
**no** public manifest names a registry, so the old guard cannot be smuggled back in one package and silently pin a
|
|
22
|
+
release to localhost.
|
|
23
23
|
- **Lockstep, and `main` carries the next version.** One number across `packages/*` and `templates/default`, because
|
|
24
24
|
[ADR-0019](0019-one-converging-command-against-the-current-context.md)'s one release train already reads a single
|
|
25
25
|
`KIT_VERSION`, ADR-0043's scaffold pins two packages at one exact number, and `workspace:*` packs to an exact
|
|
@@ -50,11 +50,11 @@ version, images, gate, trigger.
|
|
|
50
50
|
([ADR-0072](0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md)). It does not wait for the
|
|
51
51
|
npm approval, so the site can be ahead of the live packages until the maintainer approves them.
|
|
52
52
|
- **The job stages; the maintainer approves.** Each trusted publisher is configured for `npm stage publish` only, so the
|
|
53
|
-
tag produces
|
|
54
|
-
them live. This is the one human act a release keeps, and it is kept on purpose: the job's identity is
|
|
55
|
-
repository and the workflow file, so a stolen GitHub session or a bad edit to that file could otherwise
|
|
56
|
-
the project's name; staged, it can stage and nothing more. It is not a runbook step — nothing has to be
|
|
57
|
-
order, the staged tarballs wait.
|
|
53
|
+
tag produces one staged tarball per public package and one 2FA approval per package (`npm stage approve`, dependencies
|
|
54
|
+
first) makes them live. This is the one human act a release keeps, and it is kept on purpose: the job's identity is
|
|
55
|
+
bound to the repository and the workflow file, so a stolen GitHub session or a bad edit to that file could otherwise
|
|
56
|
+
publish under the project's name; staged, it can stage and nothing more. It is not a runbook step — nothing has to be
|
|
57
|
+
remembered in order, the staged tarballs wait.
|
|
58
58
|
- **Two executors of one train.** `scripts/publish.sh` is the irreversible half — images, then packages, dependencies
|
|
59
59
|
first, live versions skipped — and the job runs it with `--stage`. A maintainer runs it as `just publish` from the
|
|
60
60
|
pushed tag when a 0.x release cannot wait the job's hour: `npm publish` with a 2FA prompt per package in place of an
|
|
@@ -94,14 +94,15 @@ version, images, gate, trigger.
|
|
|
94
94
|
configuration" no longer holds; the rest of that list does.
|
|
95
95
|
- **Bootstrap, once:** create the `jr2` org on npmjs (a scope is a user or an org), publish the first version by hand
|
|
96
96
|
with a token — npm attaches a trusted publisher only to a package that already exists — then configure the trusted
|
|
97
|
-
publisher on
|
|
98
|
-
the
|
|
99
|
-
|
|
97
|
+
publisher on each public package and revoke the token. Each new public package needs the same step once: publish its
|
|
98
|
+
first version by hand before the first tag that holds it, or that tag's `npm stage publish` of it fails. After that no
|
|
99
|
+
token that publishes on its own exists anywhere: the job's identity stages, a maintainer's login needs its second
|
|
100
|
+
factor. Likewise once: make the three GHCR packages public after the first push, because GHCR creates them private.
|
|
100
101
|
- `publish.test.ts` asserts no registry, `access: public`, and `engines`; `dist-publish.sh` drops its manifest read-back
|
|
101
102
|
and passes `--registry`; the `@dist` fixture takes its registry from `JR2_DIST_PORT`'s default rather than the
|
|
102
103
|
manifest; `kit-push.sh`'s cli-vs-orchestrator cross-check widens to every manifest; `just release` is new;
|
|
103
104
|
`.github/workflows/` gains `ci.yml` and `release.yml`.
|
|
104
105
|
- A release costs a runner kind cluster and roughly ten minutes of docker. Accepted: it is the only time the installed
|
|
105
106
|
path must be proven on the exact tree that ships.
|
|
106
|
-
- The
|
|
107
|
+
- The public tarballs gain the npm-page fields (`description`, `repository` with `directory`, `homepage`, `bugs`,
|
|
107
108
|
`keywords`) and a README each; the root README's "only scaffolding exists" line goes.
|
package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md
CHANGED
|
@@ -52,6 +52,13 @@ ADR-0018 and GLOSSARY.md said `cwd` is identity, never per Turn. The decided thi
|
|
|
52
52
|
- **Fan-out is safe by construction.** A Pool spawns each worker with the item id as its actor id (pool.ts), so three
|
|
53
53
|
`task` children each mint their own `…/backlog.worker.<item>/coder`. The packaged Machine never learns it is under a
|
|
54
54
|
Pool.
|
|
55
|
+
- **Each segment of the actor path is encoded, then the segments are joined with `.`.** An actor id can be any text
|
|
56
|
+
(an item id is the source's), and with plain `.` joints the paths `a.b`, `c` and `a`, `b.c` were one conversation. A
|
|
57
|
+
letter, a digit, `-` and `_` stay as they are; each other character becomes `~` and two upper-case hex digits for
|
|
58
|
+
each of its UTF-8 bytes (`F.12` is `F~2E12`). So a segment has no `.` and no `/`, the iid's two joints, and nothing
|
|
59
|
+
that a URL path or a header must escape. The root Machine's path is `root`. Two segments are escaped although each
|
|
60
|
+
of their characters may stay: `root` is `~72oot`, and the empty id is `~`. So no other path is `root` or empty, and
|
|
61
|
+
two paths never have one spelling.
|
|
55
62
|
- **States of one Machine share by saying `continue`.** States differ by Menu — a coder in `implement` offers
|
|
56
63
|
`finish`, the same coder in `fix` offers `finish` and `dispute` — and the same conversation is wanted across them. A
|
|
57
64
|
state that wants a clean context leaves `continue` off. A Turn joins only a conversation that an earlier Turn
|
|
@@ -42,7 +42,7 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
|
|
|
42
42
|
bearer. A 401 comes before a 404, so a refused caller does not learn which conversations exist. A refused admission
|
|
43
43
|
creates no conversation. The Harness requires `JR2_HARNESS_TOKEN_SHA256` at boot and fails loudly without it.
|
|
44
44
|
- **The echo uses the same bearer.** `JR2_ECHO_TOKEN_SHA256` is removed. The Instance token no longer enters any pod,
|
|
45
|
-
which is what [ADR-0032](0032-the-console-unlocks-with-
|
|
45
|
+
which is what [ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md) and
|
|
46
46
|
[Secrets and trust](../../concepts/secrets-and-trust.md) say.
|
|
47
47
|
- **A refused stream is a fault, not a reconnect.** The bearer is derived, so a Harness that refuses it once refuses it
|
|
48
48
|
on every retry. `wait` settles a 401 or 403 as a `SettlementFault` and does not park the Turn forever.
|
|
@@ -52,9 +52,11 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
|
|
|
52
52
|
Orchestrator pods (`app: jr2-orchestrator`, `jr2.dev/instance: <name>`). They are converged unconditionally, so a
|
|
53
53
|
Harness pod is never created before its policy exists. The Instance Effector gets a third of the same form,
|
|
54
54
|
`jr2-instance-effector`, applied with it in one List (ADR-0068).
|
|
55
|
-
- **Starting a run needs
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
- **Starting a run needs a Caller with `start`.** `POST /workflows/:name/runs` moves from `authenticated` to the guarded
|
|
56
|
+
band, where a Caller's `start` scope opens it
|
|
57
|
+
([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). A Sandbox token is no
|
|
58
|
+
Caller: its scope is its own agent surface. The Custodian's route allowlist does not forward this route, but that is
|
|
59
|
+
not a control.
|
|
58
60
|
|
|
59
61
|
## Considered options
|
|
60
62
|
|
|
@@ -84,10 +86,14 @@ and we add an ingress NetworkPolicy that admits only the Orchestrator to a Harne
|
|
|
84
86
|
transport failure, not a status, because a 401 would mean the bearer stopped it and the policy did not.
|
|
85
87
|
- **Port-forward and probes are not affected.** `kubectl port-forward` enters the pod's network namespace through the
|
|
86
88
|
kubelet, and the kubelet's probes come from the node. On kind, neither is filtered by an ingress policy. So the CLI,
|
|
87
|
-
the Console, and
|
|
88
|
-
|
|
89
|
+
the Console, `jr2 workspace forward` and `jr2 workspace shell --user` into the User Container
|
|
90
|
+
([ADR-0005](0005-sandbox-pod-composition.md), [ADR-0009](0009-cli-and-instance-interface.md)) keep working: they reach
|
|
91
|
+
the pod by port-forward and exec through the kube API, and no Service routes to the User Container. If routable access
|
|
92
|
+
to the User Container is added later (ADR-0005 leaves it open), it must add its own ingress rule.
|
|
89
93
|
- **The `@kind` history reads present the bearer.** The steps derive it from the instance Secret's signing key, as the
|
|
90
|
-
CLI reads the Instance token: over the kube API, with RBAC as the gate. A human
|
|
94
|
+
CLI reads the Instance token: over the kube API, with RBAC as the gate. A human reads a conversation from the
|
|
95
|
+
Orchestrator's record with `jr2 logs`, behind `read` ([ADR-0023](0023-the-harness-prints-the-conversation.md)), and
|
|
96
|
+
needs no bearer of a Harness.
|
|
91
97
|
- **Model keys are not in the Harness container.** `JR2_PROVIDER_API_KEY` and every other held secret live in the
|
|
92
98
|
Custodian, and the Harness container's env holds their Stand-ins (ADR-0059). `jr2 up` refuses a known model key in
|
|
93
99
|
`harness.env` or in an `envFrom` Secret.
|