Skip to content

Add @corbits/access-tools: principals and scoped grants for agents (CL-7467) - #656

Merged
TheGreatAxios merged 12 commits into
mainfrom
cl-7467-access-tools
Sep 6, 2026
Merged

TheGreatAxios merged 12 commits into
mainfrom
cl-7467-access-tools

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Myra (the assistant workflow) can create new specialist agents, but had
no way to give them scoped access -- reconcileSeedGrants was the only
grant writer. This adds @corbits/access-tools, a tool package with:

  • list_principals -- list principals (users, agents, workflow runs) in
    the calling agent's tenant.
  • list_grants -- list grants, optionally filtered by principalId or
    resource.
  • grant_access (approval: "ask") -- grant a principal one or more
    actions on a resource; a human approves each grant before it lands.
  • revoke_access (approval: "ask") -- revoke a grant by id.

The package's own routes.ts adds a workflow-run-authenticated mirror
of the tenant's principal/grant surface at /api/workflow-access,
authorized through Interchange's real grant store and condition
registry (createRequireGrant/@intx/authz) -- the same check every
session-authenticated route runs, not a bundle-local approximation.

Delegation ceiling (review fix)

POST /grants and DELETE /grants/:grantId previously only checked
that the caller held grant:*/create (or manage), never that the
requested (resource, action) pairs were within what the caller's own
principal already holds -- a self-escalation hole. Both routes now call
@intx/authz's authorize() per requested pair, against the SAME
grant store createRequireGrant's middleware checks, and refuse with a
403 naming the first disallowed pair. Revoke applies the identical
ceiling to the grant's own (resource, action) before deleting it.

Myra-only grant scoping (review fix)

The four access-tools grants (principal:* read, grant:*
read/create/manage) previously lived in SEED_GRANTS, which
reconcileSeedGrants plants on every tenant's shared principal --
letting that principal read and mint grants for every principal in the
tenant, not just the specialist agents Myra stands up herself. They are
now planted only on the assistant workflow's own run principal, via
plantAssistantAccessToolsGrants in packages/seeding/src/seed.ts.
That principal id is derived with deriveRunPrincipalId(tenantId, deploymentId) -- the same deterministic derivation
@intx/hub-api's workflow-run-trigger uses to mint a run principal
on a deployment's first materialized trigger. It stays stable across a
relaunch because it's keyed to the deployment's own top-level run id,
not to a fresh per-invocation run id. The grants are planted only after
confirmDeploymentAnswers has actually triggered the assistant
deployment (confirmDeployments: true), since that trigger is what
commits the run-principal row these grants reference; the connect
flow's confirmDeployments: false path defers this to the tenant's
next workbench seed run.

Wired in:

  • apps/hub/src/index.ts -- mounts the routes, reusing the same
    chatGrantStore/chatConditionRegistry every other extension checks
    against.
  • apps/sidecar/src/workflow-substrate-factory/step-env.ts -- threads
    hubAccessUrl onto the step env, same hub origin as every other
    manager-tools bundle.
  • packages/tool-registry-publish/src/registry.ts -- publishes the
    package into the corbits-tools registry.
  • packages/seeding/src/seed.ts -- plants the four access-tools grants
    on Myra's own run principal instead of SEED_GRANTS.

No vendor edits -- @intx/authz, @intx/db, and @intx/hub-api's own
createRequireGrant/idResource/authorize exports are consumed as
published dependencies.

Test plan

  • bunx tsc --noEmit -p packages/access-tools
  • bunx tsc --noEmit -p apps/hub
  • bunx tsc --noEmit -p packages/seeding
  • bun test packages/access-tools (91 pass, incl. new
    routes.test.ts DB-gated ceiling suite: 401/403/403/201/403/404)
  • bun test packages/seeding
  • bun run check:structural report-error tool-package-pins licenses
  • CI

Resolves CL-7467

Covers the tool bundle's client (auth headers, response parsing, 403/404
mapped to typed errors) and the tool handlers themselves (declarations,
approval gating, invalid input short-circuiting fetch, a forbidden grant
surfacing as a clear tool error, and a successful grant round-tripping
through list_grants).
Lets an agent see who exists in its tenant and, once a human approves,
create or revoke scoped grants for the specialist agents it stands up.
list_principals and list_grants are plain reads; grant_access and
revoke_access declare approval: "ask" so a human signs off on each
grant before it takes effect.

The package's own routes.ts adds a workflow-run-authenticated mirror of
the tenant's principal/grant surface at /api/workflow-access, gated by
Interchange's real grant store and condition registry -- the same
authorization every session-authenticated route runs, not a bundle-local
approximation. Mounted in apps/hub alongside the other workflow-run
surfaces, threaded a hub URL by the sidecar's step-env builder, and
published into the corbits-tools registry.

Extends SEED_GRANTS with the principal/grant reads and grant
create/manage a seeded principal needs to actually use this surface.
One paragraph per tool, plus a pointer to the workflow-run-authenticated
routes they call.
@linear-code

linear-code Bot commented Sep 6, 2026

Copy link
Copy Markdown

CL-7467

Run scripts/generate-tsconfig-references.ts so the composite build graph
picks up the new package's dependencies -- required for tsc --build and
the tsconfig-references structural check.
Covers the caller-authenticated /principals and /grants routes: 401
without a recognized sidecar bearer token, 403 when the caller lacks
grant:*/create, 403 when a requested (resource, action) pair exceeds
the caller's own authority, 201 within the ceiling, revoke 403 outside
the ceiling, and 404 for an unknown grant.
A caller holding grant:*/create could previously mint any (resource,
action) pair, including ones its own principal was never granted --
self-escalation. POST /grants and DELETE /grants/:grantId now check
each requested pair against the caller's own authority through the
same @intx/authz authorize() call createRequireGrant uses, and refuse
with a 403 naming the first disallowed pair.
The four access-tools grants (principal:*/read, grant:*/read+create+
manage) were landing in SEED_GRANTS, which reconcileSeedGrants plants
on every tenant's shared principal -- letting that principal read and
mint grants for every principal in the tenant, not just the
specialist agents Myra stands up herself.

Plant them instead on the assistant workflow's own run principal,
derived the same deterministic way @intx/hub-api's workflow-run-
trigger mints one on a deployment's first materialized trigger
(deriveRunPrincipalId(tenantId, deploymentId)). That id is stable
across a relaunch because it's keyed to the deployment's own
top-level run id, not to a fresh per-invocation run. Only planted
once confirmDeploymentAnswers has actually triggered the assistant
deployment, since that trigger is what commits the run-principal row
these grants reference.
The merge from main picked up a v2-format lockfile from a newer local
bun than the .bun-version this repo pins (1.3.14) can read, so CI's
frozen-lockfile install failed before any check ran. Regenerating with
1.3.14 restores the v1 format CI expects.
check:structural's db-gate check flagged the hand-rolled describeIfDb;
switched to the shared dbGate helper. That import reaches
scripts/e2e/db-gate.ts, which the tsconfig-references generator treats
as disqualifying for a package's composite src/ project when the
import comes from src/ itself -- moving the DB-gated composition
suite to test/ (where multi-module suites already live per this
repo's convention) keeps access-tools in the composite graph.
@TheGreatAxios
TheGreatAxios merged commit 5452c42 into main Sep 6, 2026
7 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-7467-access-tools branch September 6, 2026 16:01
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.

1 participant