Bridge project scope and Work Center feeds¶
The Bridge is a local execution boundary. Its reviewed bridge:transport
credential authenticates bridge_identity_id, not a selected project. A node
may work for more than one
HeyAira project only when each project has an explicit active binding.
This contract keeps the account, project and node boundaries separate:
project_idis never selected from a Bridge read request to grant access;- the Bridge access token identifies the node;
- a project filter is accepted only after the server verifies an active node-to-project binding;
- binding requires a project owner/admin with
bridge:bindings:write, and the same authenticated subject must also be an owner/admin of the Bridge identity's enrollment project; - unbinding requires
bridge:bindings:writeand an owner/admin role only in the authenticated target project, so its data owner can always revoke the delegation; - an unbound or revoked project fails closed with
project_not_boundor a not-found response, and no task, memory, receipt or source data is returned.
Persistence and migration¶
Migration 017_bridge_node_project_bindings.sql adds the explicit binding
relation:
(bridge_identity_id, project_id) -> created_at, created_by_subject, source, revoked_at
Existing reviewed bridge_identities rows are backfilled as
bridge-enrollment-backfill. New enrollment proofs create the initial binding
in the same transaction that activates the identity and issues its token. The
older /pair credential remains valid for its original single project through
a read-only compatibility fallback; it cannot silently acquire more projects.
The migration is additive and does not delete or rewrite project data.
Binding management¶
The authenticated project is derived from the project token. The request body does not contain a target project, so a caller cannot switch tenants by changing JSON. Creating or restoring a binding is a durable delegation and therefore requires all three checks:
- the credential carries
bridge:bindings:write; - its subject is an owner or admin of the authenticated target project;
- that same subject is an owner or admin of the Bridge identity's original enrollment project.
The third check proves that the caller controls the Bridge identity rather
than merely knowing a node UUID. A foreign identity is returned as not found,
and a rejected request does not create or restore a binding. Limited OAuth
grants such as memory:read cannot manage bindings even when their subject has
an administrative project role.
POST /bridge/v1/nodes/{node_id}/projects
Authorization: Bearer <project-owner-or-admin-token-with-bridge:bindings:write>
The response contains the node ID and the bound project metadata. To revoke only that project's access:
DELETE /bridge/v1/nodes/{node_id}/projects
Authorization: Bearer <project-owner-or-admin-token-with-bridge:bindings:write>
Unbinding is intentionally asymmetric: the authenticated subject must administer the target project but does not need to administer the node's enrollment project. Revocation only removes the target project's delegation; requiring node control would let a removed administrator keep a binding that the target project's remaining owners could not revoke.
Here node_id is the database UUID of the reviewed enrollment identity
(bridge_identities.id). It is not the legacy /pair node UUID and not the
client-chosen textual bridge_id.
The existing POST /bridge/v1/nodes/{node_id}/revoke remains the legacy
global node-revocation route for /pair credentials. Modern transport-token
revocation continues through POST /bridge/v1/tokens/revoke; project
unbinding is independent and revokes only one binding.
Read feeds¶
Bridge access tokens can read the following bounded, no-store feeds:
| Endpoint | Purpose |
|---|---|
GET /bridge/v1/projects |
Active project catalog for this node |
GET /bridge/v1/tasks |
Task index with optional project_id, status, limit, and offset |
GET /bridge/v1/tasks/{task_id} |
Full task plus work sessions, only within an active binding |
GET /bridge/v1/memory |
Memory index with optional project, kind and text filters |
GET /bridge/v1/memory/{memory_id} |
Full memory entry, only within an active binding |
GET /bridge/v1/receipts |
Bounded work-session receipt index |
GET /bridge/v1/sources |
Bounded Git document-reference index |
Index responses do not include memory bodies. Detail endpoints perform the same binding check as index endpoints. Limits are clamped to 100 entries per request. Feed responses contain references and durable metadata; repository files remain in Git and are read through the existing pinned document access contract.
Client integration boundary¶
The Bridge client should call the project catalog after authentication, show
the user only projects returned by that catalog, and send an explicit project
filter for project-scoped Work Center views. A client must treat a 403
project_not_bound or a 404 detail response as an authorization result, not
as permission to retry against another project or infer a project from a task.
Session assignment, leases, and server-issued task plans are intentionally not part of this slice. They require a separate session registry and assignment contract. Until then, the client must keep the project ID in its local session state and record it in the normal task/work-receipt flow.