Repository navigation
Peers: a peer-scoped key that reads, launches, and controls only the runs a peer launched - #129
Merged
Merged
Conversation
…runs a peer launched
plans/federation.md §3, step 4, strut's half. A peer record on another
strut used to hold this strut's whole deployment key.
- STRUT_PEER_KEY: presented like STRUT_API_KEY (Bearer or ?key=), but the
request it carries is peer-scoped (scopeFromKey, the default of the new
createStrut({ resolveScope }), the twin of resolveActor; a host such as
mcp maps its own credential instead). x-strut-actor is honored with it,
so a peer's launch is billed to the person it names.
- The one gate: a peer may make any GET/HEAD, launch a run
(POST /workflows/:name[/:version]/run), and cancel / pause / resume.
Everything else is 403, a route a host mounts later included.
requireApiKey lets the peer key read.
- "A run it launched" is recorded, not inferred from the forwarded actor:
a peer's launch is stamped origin: "peer" on run.start (kept by a
resume), which the control routes check. A run just launched is also
marked on its controller, because a caller's cancel right after its
launch reaches the peer before run.start is in the log (5 of 5 test
runs without the mark).
- Launch is not narrowed by workflow (decided 2026-10-09, the plan as
written). A peer that can launch a workflow whose agent has bash reaches
whatever the server's user can. Recorded in the plan, AGENTS.md and
API.md.
Tests: the route sweep with the peer key (every GET passes, the five
launch/control POSTs pass, every other route is 403), a host's
resolveScope over an open gate, auth units (read vs write, actor, unset,
dev mode), run control (a peer's own run cancelled right after launch and
resumed, a run the deployment launched refused for cancel/pause/resume and
durable resume, the version launch stamped), and the two-strut suite
calling and cancelling over HTTP on the peer key alone.
This was referenced Oct 9, 2026
Merged
Evanfeenstra
added a commit
to stakwork/stakgraph
that referenced
this pull request
Oct 9, 2026
…rut (#1754) * lab: a lab:peer token for another strut — strut's peer scope; bump strut The mcp half of strut plans/federation.md §3, step 4. A strut that has this swarm on file as a peer used to hold an api JWT or the raw API_TOKEN, either of which is admin of the lab. - /mint-token takes `scope`: "api" (the default, unchanged) or "lab:peer", which defaults to 60 days. An unknown scope is a 400, and only the raw API_TOKEN mints, as before. The handler moved beside signApiToken (repo/events.ts, `mintToken`) so it can be tested. - verifyApiToken accepts `api` unless the caller says otherwise, so a lab:peer token opens nothing outside /lab. - labAuth takes a lab:peer JWT as Bearer only (never ?key=), and stashes strut's `peer` scope beside the actor (actor.ts). The actor is the token's `sub`, else the caller's x-strut-actor. The dictation upgrade refuses a peer. - createLabStrut passes resolveScope: resolveLabScope. Strut enforces what a peer may do: every read, a launch, cancel / pause / resume of a run a peer launched, 403 for the rest. Pins strut at stakwork/strut#129's head (2aac37e), which adds createStrut({ resolveScope }); re-pin to its merge commit once it lands. Tests (mount.test.ts): - labAuth: Bearer vs ?key=, the actor, other credentials still full - a lab:peer token outside the lab - a real strut behind labAuth and the real bridge: a read passes, a secret write is 403, a launch is stamped origin "peer" with the token's person, and the peer cancels its own run but not hive's - mintToken: defaults, the 60 days, an unknown scope, no renewal by JWT test:node: 888 pass, 7 skipped, 0 fail. * bump strut to the #129 merge commit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Until now a peer record (
plans/federation.md§3) held the other strut's whole deployment key. The local strut that called swarm38'sexploreon 2026-10-09 did exactly that, and the plan says a laptop must never hold one. This is strut's half of step 4: a narrower credential and the scope behind it.What
STRUT_PEER_KEY. It is presented likeSTRUT_API_KEY(Bearer or?key=), but the request it is on ispeer-scoped. The scope comes fromcreateStrut({ resolveScope }), the twin ofresolveActor; its default,scopeFromKey, sayspeerfor a request carrying the peer key. A host that authenticates requests itself maps its own credential there instead (mcp'slab:peerJWT, next).What a peer may do. The one gate allows any
GET/HEAD, a launch (POST /workflows/:name[/:version]/run), and cancel / pause / resume. Anything else is 403, a route a host mounts later included.x-strut-actoris honored with the peer key, so a peer's launch is billed to the person it names (strut/run-workflowsends its run's principal).Only its own runs. The control routes refuse a peer any run no peer launched. That is recorded, not inferred from the forwarded actor: a peer's launch is stamped
origin: "peer"onrun.start, and a resume keeps it. A run just launched is also marked on its controller, because a caller's cancel right after its launch reaches the peer beforerun.startis written (5 of 5 test runs without the mark).Not narrowed by workflow (decided 2026-10-09, the plan as written). A peer can launch any workflow, so one whose agent has
bash(the lab's seededjob) reaches whatever the server's user can. The scope does keep a peer from:The gap is written into the plan,
AGENTS.mdandspecs/API.md.With no peer key and no
resolveScope, nothing changes for an existing deployment.Verified
1382 unit tests pass on the branch merged with main; tsc is clean. New:
resolveScopeover an open gate (mcp's shape).The other half
mcp:
/mint-tokenwithscope: "lab:peer"labAuthaccepting it, withx-strut-actorbeside itresolveScopehook that maps itStrut already enforces everything else. PR to follow.