You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Read-only installation tokens for a Conglomerate Host's access service, one per served Organization #30
The access service on a Conglomerate Host (root decision 0224, Machines workloads/conglomerate-host/ACCESS-SERVICE.md) needs to read three facts, about once a minute, for each Organization the host serves:
Organization membership: who is an active member, which Teams they are in, who is an Owner;
the Environment assignments in that Organization's Deployment Repo (the lanes' provision.assignment);
the Environments' host keys from that repo's lane readbacks.
On 2026-11-01 the product owner decided that these facts come through Lazurio for GitHub, read only: a short-lived installation token per served Organization, with members: read and contents: read limited to that Organization's Deployment Repo. The host never writes through it.
Today no broker route accepts a Conglomerate Host as a caller. Installation tokens are minted only for GitHub Actions OIDC callers, such as a rollout runner, and are revoked at the end of the run.
Needed
A broker route for a Conglomerate Host caller. It returns the token above for one named Organization, but only when the App is installed there and that Organization is served by the calling host.
How the host proves itself. This is open; the design PR proposes options. One candidate is a per-host key pair whose public part the host's Owner registers with the broker, and whose private part never leaves the host. The rules: no long-lived GitHub token on the host, and no copy of the App private key on it.
Revocation. Removing a host's registration stops new mints immediately; tokens already minted expire on their own.
Why it matters
Without this, the access service can derive no access at all, and nobody's personal Environment reaches their Work Environments (the original symptom). The same route would also serve the rollout gateway's read needs.
Not in scope
Any write permission, and any route for Environments or devices.
Agent: environment 0f732f59-9f07-474d-abcb-2149bb79f9d5 · T3 thread 85fb6f6d-7053-46f1-bc5a-49ed7790e5e5
Shaping: how a Conglomerate Host proves itself to the broker
Facts this rests on (read 2026-11-02):
Every Organization runs its own broker Worker, bound to one GitHub Organization id and one installation. A host that serves three Organizations therefore asks three brokers, and each of them decides for its own Organization.
The broker authenticates two callers today: a Workspace (a static bearer credential, bound to one GitHub Team id, scoped by that Team's live grants) and a GitHub Actions job (OIDC, Worker only). A Conglomerate Host is neither.
The access service needs, per served Organization, one token with members: read and contents: read on that Organization's Deployment Repo. It reads about once a minute; an installation token lives an hour, so roughly one mint per Organization per hour.
Rules from the issue: no long-lived GitHub token on the host, no copy of the App private key on it, audit per mint, removal stops new mints at once.
Option A (recommended): a host key whose public part each served Organization lists in its own broker policy
The host role generates an ed25519 key pair on the host at install time. The private part never leaves the host (root-only file, readable by the service account only); the public part comes back in the host lane's readback, so it is visible in a reviewed record.
Each served Organization adds that public key to its own broker policy in a reviewed pull request (conglomerate_hosts[]: host id, key, the one Deployment Repo id the token is for). Listing the key is that Organization's consent to be served; there is no global list.
The host calls a new Worker route, POST /v1/host/token, with a short signed assertion: audience = this broker's origin, host id, issued-at, expiry ≤ 60 s, a random jti. The broker verifies the signature (jose, already the only dependency), refuses a replayed jti (a deny-only Durable Object, like the OIDC run guard), and mints one token with exactly members: read + contents: read on the listed Deployment Repo. The caller chooses nothing.
Audit: one line per decision with host id, Organization, repository, permissions and expiry, never a token or the assertion.
Revocation: remove the key from the policy and deploy; the next mint is refused. A token already minted lives out its hour (GitHub has no per-token revocation the broker could use without holding it); for an emergency the Organization Owner can suspend the installation.
Failure modes:
Situation
Effect
broker down or refusing
the service keeps its current token until it expires; after that the facts are unavailable and every derived edge ends ≤ 210 s later (fails closed)
host rebuilt, key lost
new key, new policy PR in every served Organization; until then no edges (fails closed)
host disk stolen
the thief can mint read tokens for the listed Deployment Repos and read memberships until the key is removed; no write, no other repository
assertion intercepted
useless after 60 s and after its first use
policy lists a key for the wrong host
that host reads one more Organization's membership and Deployment Repo; review of the broker policy is the boundary, as for OIDC callers
Cost: a broker release (route, parser, replay guard, tests per invariant 8) and a policy deploy per served Organization, by whoever holds that broker's App key custody.
Variant A2: sign with the host's existing SSH host key (SSHSIG with its own namespace) instead of a new key. It saves one key and its readback, but couples broker identity to SSH host key rotation and needs a root helper to sign. Not recommended.
Option B: the host as a read-ceiling Workspace (works with today's broker)
Per served Organization: a GitHub Team with pull on its Deployment Repo, a Workspace bound to it with permission_ceiling: "read", and the Workspace credential copied onto the host.
No broker code change; revoking the Team grant on GitHub stops the next mint at once.
But: a long-lived bearer secret exists in two places and has to travel from each Organization's custody onto the Conglomerate's host, so a leak is replayable until rotated; the read tier also reads the Deployment Repo's Actions runs, checks and pull requests; and "Workspace" means a Team Environment everywhere else, which makes the audit misleading.
Option C: GitHub Actions in each Deployment Repo pushes the facts to the host
Uses the existing OIDC route. But scheduled workflows run at best every 5 minutes and are often delayed, so the 210 s freshness budget cannot hold, and it opens a write path into the host, which ACCESS-SERVICE.md already rejects ("Lanes push facts to the host"). Not recommended.
The choice goes to the product owner; the implementation follows in its own pull request with contract tests first.
Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529
Decided 2026-11-02 by the product owner: option A2, the host's own key that travels by itself. Joining an Organization to a host or rebuilding the host must add no setup step.
Broker side, as the Machines contract now fixes it (the projection is written by the host Organization's tooling, not by this repository):
The policy names once, per Organization, where its host keys are: its own Deployment Repo by immutable id, a ref and a directory. One document per host there carries the host's public key (ed25519:<base64url>) and the Organization's id.
POST /v1/host/token (Worker only): a compact JWS, alg: EdDSA, kid the host id; claims aud = this broker's origin, iss = kid, iat, exp ≤ iat + 60 s, jti. The broker reads the host's document live with its own installation, checks schema and Organization id, verifies the signature, refuses a seen jti (deny-only Durable Object), and mints one token for its Deployment Repo with exactly contents: read + members: read. The caller chooses nothing.
Refusals before any GitHub write; audit line per decision with host, Organization, repository, permissions and expiry, never a token or the JWS.
Invariant 8 tests: unknown host, key from another Organization's document, wrong audience, expired or too long-lived JWS, replay, wrong algorithm, a document outside the directory, a policy without the key (route refused).
The contract and the implementation follow in one pull request here (release 0.15.0).
Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529
Status after the access decisions of 2026-11-14 (register entries 0245 and 0246 in Lazurio/LazurioPlatform).
The route this issue asked for exists: POST /v1/host/token was merged in #31 and released in v0.15.0. What follows is about how far it goes from here.
The target source of facts is Lazurio Auth, not GitHub. An Organization's Deployment Repo holds the boundaries of trust (who is a member, who is an Organization Admin, the Devices of the Organization), Lazurio Machines applies them and Auth holds their use. The access service on a Conglomerate Host is meant to read its facts from Auth.
"Who is an Owner" derives no access. Being an Owner of the GitHub Organization gives no access to any Environment by itself, and the Owner role never stands in for the Admin role for access. The route must not be used to derive an edge or an entry from it.
What runs today. Until membership is declared in the Deployment Repo and applied to Auth, live membership of the GitHub Organization mirrors membership of the Lazurio Organization for accounts linked to GitHub. A read-only route that serves that mirror is transitional. It is kept as it is and not extended with new permissions or callers.
What stays useful. The host's proof to the broker (its own key, a short-lived signed request, no long-lived token on the host), the audit of every mint and immediate revocation by removing the host's registration.
Agent: environment ec61b02f-e7cf-44e6-80a5-d4b92ded247c · T3 thread 08183cce-cdd6-4639-b973-4a2c5bf500a2
Activity
immakermatty commentedon Nov 2, 2026
Shaping: how a Conglomerate Host proves itself to the broker
Facts this rests on (read 2026-11-02):
members: readandcontents: readon that Organization's Deployment Repo. It reads about once a minute; an installation token lives an hour, so roughly one mint per Organization per hour.Option A (recommended): a host key whose public part each served Organization lists in its own broker policy
conglomerate_hosts[]: host id, key, the one Deployment Repo id the token is for). Listing the key is that Organization's consent to be served; there is no global list.POST /v1/host/token, with a short signed assertion: audience = this broker's origin, host id, issued-at, expiry ≤ 60 s, a randomjti. The broker verifies the signature (jose, already the only dependency), refuses a replayedjti(a deny-only Durable Object, like the OIDC run guard), and mints one token with exactlymembers: read+contents: readon the listed Deployment Repo. The caller chooses nothing.Failure modes:
callersCost: a broker release (route, parser, replay guard, tests per invariant 8) and a policy deploy per served Organization, by whoever holds that broker's App key custody.
Variant A2: sign with the host's existing SSH host key (SSHSIG with its own namespace) instead of a new key. It saves one key and its readback, but couples broker identity to SSH host key rotation and needs a root helper to sign. Not recommended.
Option B: the host as a read-ceiling Workspace (works with today's broker)
pullon its Deployment Repo, a Workspace bound to it withpermission_ceiling: "read", and the Workspace credential copied onto the host.Option C: GitHub Actions in each Deployment Repo pushes the facts to the host
The choice goes to the product owner; the implementation follows in its own pull request with contract tests first.
Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529
immakermatty commentedon Nov 2, 2026
Decided 2026-11-02 by the product owner: option A2, the host's own key that travels by itself. Joining an Organization to a host or rebuilding the host must add no setup step.
Broker side, as the Machines contract now fixes it (the projection is written by the host Organization's tooling, not by this repository):
ed25519:<base64url>) and the Organization's id.POST /v1/host/token(Worker only): a compact JWS,alg: EdDSA,kidthe host id; claimsaud= this broker's origin,iss=kid,iat,exp≤iat+ 60 s,jti. The broker reads the host's document live with its own installation, checks schema and Organization id, verifies the signature, refuses a seenjti(deny-only Durable Object), and mints one token for its Deployment Repo with exactlycontents: read+members: read. The caller chooses nothing.The contract and the implementation follow in one pull request here (release 0.15.0).
Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529
Conglomerate Hosts: only Owners and approved writers may write the De…
Conglomerate Hosts: POST /v1/host/token with a key read live from the…
immakermatty commentedon Nov 14, 2026
Status after the access decisions of 2026-11-14 (register entries 0245 and 0246 in Lazurio/LazurioPlatform).
The route this issue asked for exists:
POST /v1/host/tokenwas merged in #31 and released in v0.15.0. What follows is about how far it goes from here.Agent: environment ec61b02f-e7cf-44e6-80a5-d4b92ded247c · T3 thread 08183cce-cdd6-4639-b973-4a2c5bf500a2