Skip to content

Org credentials: delete the box credential wire, one org plane behind an allowlist, agent-proposed grants with human approval - #183

Merged
pythonlearner1025 merged 13 commits into
mainfrom
org-credentials-agent-api
Sep 2, 2026
Merged

Org credentials: delete the box credential wire, one org plane behind an allowlist, agent-proposed grants with human approval#183
pythonlearner1025 merged 13 commits into
mainfrom
org-credentials-agent-api

Conversation

@pythonlearner1025

Copy link
Copy Markdown
Member

What this does

Implements plans/ORG-CREDENTIALS.md (design locked 2026-09-02) end to end, in nine commits, one per wave:

  1. plans — the design and the interactive approval-dialog mock (plans/mockups/grant-approval.html).
  2. box — the blitz-cred credential 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-token prints 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; jq joins the pinned image packages.
  3. control-plane — migration 0047 creates org_credentials + org_credential_grants and drops workspace_credentials (a stated, unmigrated deletion). core/org-credentials.ts holds 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.ts is 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/CreateWorkspaceRequest lose their credential fields.
  4. agent rules — the CP-served rules doc teaches curl + api-token against /agent/*, GET /agent/api as the always-current endpoint list, the org store, and the grant-proposal loop; the last blitz-cred verb mentions retire.
  5. grant proposalscore/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.
  6. webapp — Settings → Credentials panel, the workspace Credentials tab as a filtered view, and the grant-approval dialog built to the mock (one merged list per credential, inline diff rows with kind tags, per-row edit/skip, live approve count, close-to-defer).
  7. OpenAPIGET /agent/api serves a document generated from the schema types plus a route manifest (ts-json-schema-generator; nothing hand-written), checked in at packages/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.
  8. connections — migration 0048 drops connections.root_ciphertext / usable_by; legacyRootMint, usableByAllows, the static root minter, the proxy root branch, the orgCredential flag, and PUT/DELETE /connections/:name are deleted. Connections keep only what is provider-shaped (catalog, per-user grants, GitHub app, manifest enablement).
  9. gates — lint baseline lowered (no-unknown-parameters 31 → 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-slop diff 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.sh green, 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 to main.
  • Go (packages/broker, packages/box/gateway): build, vet, test all ok.
  • npm run openapi:generate is idempotent; the artifact is current.

Deliberate data deletions (read before merging)

  • Migration 0047 drops every stored workspace credential value. Nothing is migrated: same-name collisions across workspaces made an automatic lift ambiguous, the store was days old, and the org dotenv import re-creates a workspace's keys in one paste.
  • Migration 0048 drops every org root stored on a connection row. Paste once into the org Credentials panel.

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 per docs/BOX-IMAGE.md (the image gains jq, the new blitz-cred, and blitz-git-credential), cut the client-prod tag, roll cloud-VM boxes via blitz-box-update and recreate microVM incarnations (volumes survive). A straggler box's blitz-cred get fails loudly with a 404; the fix is updating it, never a server-side shim.

For the reviewer

  • New dev dependencies (control-plane): ts-json-schema-generator (brings a nested typescript@5.9 because the repo's TS 7 native compiler has no JS API) and @cfworker/json-schema (ajv compiles with new Function, which workerd forbids).
  • Commit attribution via the GitHub token (git user.name/email auto-config, gitidentity.go) died with the old wire and nothing replaces it — the plan's survivor list did not keep it.
  • Pre-existing latent bug surfaced, not fixed (out of scope): connections.name is still UNIQUE (migration 0003) while ensureCatalogConnection writes name = provider, so the same provider cannot be declared in two orgs.
  • A recycled or unknown proposal id answers 404 on the agent poll; the rules tell the agent to read it as expired.

🤖 Generated with Claude Code

lody-ai and others added 9 commits September 2, 2026 00:17
…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 \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

pythonlearner1025 and others added 4 commits September 2, 2026 08:15
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>
#189 removed the credential label inputs from the create dialog and the
workspace credentials tab; both live inside code this PR deletes, so every
hunk resolves to this branch and the tree is unchanged from 5b34733.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pythonlearner1025
pythonlearner1025 merged commit 1121811 into main Sep 2, 2026
10 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants