Org credentials: delete the box credential wire, one org plane behind an allowlist, agent-proposed grants with human approval - #183
Merged
Conversation
…ant proposals Design locked 2026-09-02: workspace credentials deleted (org-only static plane with an org/workspace/member allowlist), blitz-cred credential verbs replaced by a generated OpenAPI agent plane + curl, agent-proposed grant changes behind a human approval dialog (mock included). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e fixtures' loaders blitz-cred keeps enroll/register/token/watch and gains exactly one credential-shaped primitive: api-token, which prints a machine bearer the CP currently accepts (validate-by-use against GET /agent/api, one flock-serialized refresh on 401). The git credential helper becomes a POSIX-sh script over POST /agent/credentials/github/token + jq; every failure path answers nothing so public clones keep working. jq joins the pinned image packages. plans/ORG-CREDENTIALS.md §3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t plane is plain HTTP Migration 0047 creates org_credentials + org_credential_grants and drops workspace_credentials (a deliberate, unmigrated deletion — the store was days old and the org dotenv import re-creates a workspace's keys in one paste). core/org-credentials.ts holds the store and the one access function: org admins read and write everything, write implies read, grants cover the org, a workspace, or a membership, reads need a resolved active membership. core/agent-routes.ts is the box-authed /agent/* plane — list, two-tier token resolution (connection grant, then org credential), put, dotenv — with no closed-set wire for a box to bake in. Session routes under /orgs/:id/credentials*. WorkspaceView and CreateWorkspaceRequest lose their credential fields; the webapp keeps typechecking with the tab removed (UI lands in a later change). plans/ORG-CREDENTIALS.md §3-§7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed verb mentions The CP-served rules doc now teaches curl + `blitz-cred api-token` against /agent/*, GET /agent/api as the always-current endpoint list, the org credential store (put with a comment, dotenv with dryRun), and the grant-proposal loop verbatim from plans/ORG-CREDENTIALS.md §7a. The proxy denial messages and a handful of comments stop naming verbs that no longer exist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…plies core/grant-proposals.ts is an in-memory store (no row, no migration, ~1 h TTL enforced on read) behind four routes: the machine proposes an explicit change list with a reason and polls; the session feed lists what targets the acting member (everything, for org admins) and resolve applies the EDITED set under the same write-authority check as any grant write. Changes are validated at propose time (authority and subjects, whole proposal refused naming the offenders) and revalidated at apply time (a revoked credential or a subject that went invalid drops out), so an approval never half-applies. A recycled proposal answers 404, which the rules tell the agent to read as expired. plans/ORG-CREDENTIALS.md §7a. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ant-approval dialog Settings gains a Credentials panel beside Connections: rows with name, comment, created-by and grant chips; add and rotate through one org-level form whose grants editor picks workspaces and active members, offers the org-wide toggle, and warns loudly when org-wide is write. The workspace details Credentials tab is a filtered view over the same store (a new key defaults to read on this workspace). The grant-approval dialog is built to plans/mockups/grant-approval.html: one merged list per credential, kept grants plain, additions and removals as inline diff rows with kind tags, per-row edit and skip, a live approve count, and close-to-defer; pending proposals surface in the requests panel with a Review action. plans/ORG-CREDENTIALS.md §7a, §9. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ted OpenAPI document The document is generated from the schema package's types plus a route manifest that sits beside the registrations (core/agent-api-manifest.ts); nothing is hand-written. ts-json-schema-generator turns the wire types into components; the artifact is checked in at packages/schema/openapi/agent-api.json and served verbatim, box-authed. Two gates keep it honest: a bidirectional route-coverage test (registered /agent/* == documented == manifest) and a conformance test that drives every route's happy path through the real harness and validates the responses against the document, plus a node test that demands byte-identical regeneration. plans/ORG-CREDENTIALS.md §4, §8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… org credentials Migration 0048 drops connections.root_ciphertext and usable_by (existing org roots are a stated, unmigrated deletion — paste once into the org Credentials panel). legacyRootMint, usableByAllows, the static root minter, the proxy's root branch and the orgCredential wire flag are deleted; authorize() is isWorkspaceMember ∧ manifestAllows. PUT/DELETE /connections/:name and their /integrations aliases go with them: the root was PUT's only mandatory field and the org-root section was DELETE's only caller. Connections keep what is provider-shaped — the catalog, per-user grants, the GitHub app, manifest enablement. plans/ORG-CREDENTIALS.md §6a. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…redential deletions no-unknown-parameters fell 31 → 23 with the box wire and the workspace store gone (anti-slop 74 → 66); CLAUDE.md's known-debt section, the TODO(deslop-tier-c) count (16 was stale; 6 on main and here), the max-lines list (core/machines.ts was already over on main — corrected, not grown) and the drift-sweep reference counts now match reality. The core-imports pin counts the real module set (112). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| # transport failure. jq given curl's empty stdout also prints nothing. | ||
| token=$( | ||
| curl -sS --max-time 30 -X POST -H "Authorization: Bearer $bearer" \ | ||
| "$origin/agent/credentials/github/token" 2>/dev/null \ |
Member
Author
There was a problem hiding this comment.
why is there a special route for github token??? i thought github token is received through the connections path via the user oauth on the blitzos platform gh app installed in their acc, which mints a temp token. if not through the connections path, then user can save a gh PAT in the credentials. but if given that PAT, just use it directly, whats with the special treatmnet?
Member
Author
There was a problem hiding this comment.
isn't this redundant with agent-routes? could we, instead of having two separate routes (one for agents and one for human use) just unify to 1 api?
Resolves the four conflicts with #150 (settings redesign) and #165: - CLAUDE.md: keep the PR's 2026-09-02 reference counts (anti-slop 66, 23/27/12/4) and the 6a `/agent/api` sweep step; take main's wording for the eight-file max-lines footnote (same fact, main's phrasing). - WorkspaceDetailsDialog.tsx: the workspace CredentialsTab the PR deleted stays deleted; main's only change inside it (a `--danger` revoke button) has nothing left to apply to. - settings/ConnectionsPanel.tsx: keep main's PanelHeader import, drop the OrgConnectionsSection import (the PR deleted that file with the org-root slot). - shell/SecondaryRoutes.tsx: keep main's comment on the shared dialog layer; both sides already render {dialogs} on the settings branch. Follow-through outside the conflict hunks: main's settings design gallery (preview/fixtures.tsx, preview-client.ts, settings-gallery.tsx) mocked the workspace-credential API and the connection org-root flag this PR removes. The gallery now seeds org credentials and a pending grant proposal, mocks the org credential + grant-proposal client methods, imports org-credentials.css, passes the new SettingsPage props, and opens the grant-approval dialog from Requests → Review. OrgCredentialsPanel uses the shared PanelHeader primitive main introduced (identical markup). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 2, 2026
pythonlearner1025
added a commit
that referenced
this pull request
Sep 3, 2026
…1218111) Every VM canary created since 2026-09-02 ~22:55Z failed its bootstrap with "manifest imageTag does not match BOX_IMAGE_TAG" and landed in `error`, Brandon's member machine in workspace blitzos among them. The bucket's box-image/manifest.json was replaced by a publish of blitz-box:11218111 (main at #183) while this file still pinned 23b2c70-lody, and the golden snapshot carries neither, so every new VM took the download path and hit the mismatch. The pin now names what the bucket serves. Verified before pinning by replaying the bootstrap's manifest path against the live bucket: all nine parts match their digests, the concatenation matches totalSha256 (25d9306f…), and the archive is valid gzip. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pythonlearner1025
added a commit
that referenced
this pull request
Sep 3, 2026
… machine fails its bootstrap (#203) * bootstrap: a manifest tag mismatch names both tags The only word the machine row carried was "manifest imageTag does not match BOX_IMAGE_TAG", which says a drift happened and not between what. Now the failure reads "manifest imageTag <served> does not match BOX_IMAGE_TAG <pinned>", so the next one is diagnosable from the row alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * ci(canary): pin the box image the bucket actually serves (blitz-box:11218111) Every VM canary created since 2026-09-02 ~22:55Z failed its bootstrap with "manifest imageTag does not match BOX_IMAGE_TAG" and landed in `error`, Brandon's member machine in workspace blitzos among them. The bucket's box-image/manifest.json was replaced by a publish of blitz-box:11218111 (main at #183) while this file still pinned 23b2c70-lody, and the golden snapshot carries neither, so every new VM took the download path and hit the mismatch. The pin now names what the bucket serves. Verified before pinning by replaying the bootstrap's manifest path against the live bucket: all nine parts match their digests, the concatenation matches totalSha256 (25d9306f…), and the archive is valid gzip. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
pythonlearner1025
added a commit
that referenced
this pull request
Sep 4, 2026
Brings the quick-action strip up to date with main after #159, #164, #171, #173, #181, #183, #197 and #207. Thirteen files conflicted; the resolutions, so a reviewer can check them one by one: - `WebAppHeader.tsx`: deleted with the native tab strip on main (#159); the PR's edits to it are moot. Its two exports moved where main put them (`NewTabMenu`, `SessionTypeIcon`). - `CloudApp.tsx`: main's strip deletion wins over the PR's drag wiring (`useWorkspaceTabDrag`, `moveTab`, `splitTab`, the drag ghost); the PR's side-panel binding, Connections host tab and mobile panel sheet stay; main's `workspace-chat-memory` stays. `filesHostRegion`, `ShareToDriveDialog` and `FolderAttachmentView` went with the Files pane the PR retires — their only callers were inside it. - `router.tsx`: the PR's side-panel hooks sit above main's mobile early return, because hooks must. - `SessionRailSidebar.tsx`, `SessionRail.tsx`: main's rows with the close button, minus the `filePath` the PR removed with file tabs. - `lody-surface-tabs.test.tsx`: main moved the seam pin into `lody-seam-pin.test.ts`; the PR's anchors for the three new files and the four session-detail lines are ported there. - `shell-smoke.test.tsx`: main's "the split is a placement" assertions, driven by the Connections icon the PR's strip draws; the drag test goes with the drag. - `workspace-panes.test.ts`: `moveTab` is gone, so the backgrounded panel case builds its side-pane document by hand. - `SessionTypeIcon.tsx`: `WebAppTabModel.panel` narrows to `'connections'`, which is the one panel the PR leaves. Main already used seam numbers 10 and 11 for other patches, so the PR's two seams are 19 and 20 everywhere: the ledger, the seam-pin test, the baselines README, the upstream PR drafts and every code comment. The merge runbook's file table keeps the PR's rows with the new numbers and now says plainly that the ledger is the authority for the count. Gates on the merged tree: webapp typecheck, lint gate, and the full webapp vitest run (113 files, 948 tests) pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
What this does
Implements
plans/ORG-CREDENTIALS.md(design locked 2026-09-02) end to end, in nine commits, one per wave:plans/mockups/grant-approval.html).blitz-credcredential verbs (list|get|env|import|put|git-helper), their Go wire decoders, and the three fixture corpora are deleted (~3,900 lines). One schema-free primitive remains:blitz-cred api-tokenprints a machine bearer the CP currently accepts (validate-by-use, one flock-serialized refresh on 401). The git credential helper is a POSIX-sh script over the agent API +jq;jqjoins the pinned image packages.org_credentials+org_credential_grantsand dropsworkspace_credentials(a stated, unmigrated deletion).core/org-credentials.tsholds the store and the one access function: org admins read and write everything; any member creates and receives write; write ⊇ read; grants cover the org, a workspace, or a membership; reads need a resolved active membership.core/agent-routes.tsis the box-authed/agent/*plane (list, two-tier token resolution — personal connection grant, then org credential — put, dotenv) with no closed-set wire for a box to bake in. Session routes under/orgs/:id/credentials*.WorkspaceView/CreateWorkspaceRequestlose their credential fields.api-tokenagainst/agent/*,GET /agent/apias the always-current endpoint list, the org store, and the grant-proposal loop; the lastblitz-credverb mentions retire.core/grant-proposals.ts: an in-memory store (no row, ~1 h TTL) behind four routes. The machine proposes an explicit change list with a reason and polls; the session feed lists what targets the acting member; resolve applies the edited set under the same write-authority check as any grant write. Changes are validated at propose time and revalidated at apply time, so an approval never half-applies.GET /agent/apiserves a document generated from the schema types plus a route manifest (ts-json-schema-generator; nothing hand-written), checked in atpackages/schema/openapi/agent-api.json, pinned by a bidirectional route-coverage test, a conformance test that drives every route and validates responses against the doc, and a byte-identical regeneration test.connections.root_ciphertext/usable_by;legacyRootMint,usableByAllows, the static root minter, the proxy root branch, theorgCredentialflag, andPUT/DELETE /connections/:nameare deleted. Connections keep only what is provider-shaped (catalog, per-user grants, GitHub app, manifest enablement).no-unknown-parameters31 → 23; anti-slop 74 → 66), CLAUDE.md debt numbers and max-lines list corrected to reality.Verification
npm run typecheck: green, all workspaces.npm run lint:gate: passed at the new baseline (66 anti-slop / 0 blitz-house);tools/oxlint/anti-slopdiff empty.npm test: control-plane vitest 673 passed (python/bash/host/scripts/openapi sub-suites all green), box guest tests 108 passed, house-rule and microvm node tests green,provision-broker.test.shgreen, webapp 773 passed. Two lody real-daemon suites timed out only while another worktree ran the same suites on the machine-wide harness lock; both pass in isolation (9/9) and are byte-identical tomain.packages/broker,packages/box/gateway): build, vet, test all ok.npm run openapi:generateis idempotent; the artifact is current.Deliberate data deletions (read before merging)
Post-merge runbook (not done here — cutting a tag from a box is off-limits)
The legacy
/workspaces/self/*credential routes are gone, so field boxes must cycle onto the new image in the same release train: rebake canary perdocs/BOX-IMAGE.md(the image gainsjq, the newblitz-cred, andblitz-git-credential), cut the client-prod tag, roll cloud-VM boxes viablitz-box-updateand recreate microVM incarnations (volumes survive). A straggler box'sblitz-cred getfails loudly with a 404; the fix is updating it, never a server-side shim.For the reviewer
ts-json-schema-generator(brings a nestedtypescript@5.9because the repo's TS 7 native compiler has no JS API) and@cfworker/json-schema(ajv compiles withnew Function, which workerd forbids).git user.name/emailauto-config,gitidentity.go) died with the old wire and nothing replaces it — the plan's survivor list did not keep it.connections.nameis stillUNIQUE(migration 0003) whileensureCatalogConnectionwritesname = provider, so the same provider cannot be declared in two orgs.expired.🤖 Generated with Claude Code