Summary
There is currently no way to authorize an automation lane to perform adapter.send to specific reply surfaces without granting far broader authority. Every available mechanism in v0.3.1 (unchanged through v0.3.3) is either UID-wide or service-wide, so a least-privilege outbound "reply relay" cannot be provisioned.
Feature request: token-bound adapter.send grants (exact adapter/account/surface targets carried on the token and checked per request).
Current authorization wall (v0.3.1 sources)
handleAdapterSend rejects before reaching the adapter worker when canSendToAdapterSurface fails (adapter-handlers.ts#L177-L205). The wall (adapter-handlers.ts#L217-L267):
service identity: allowed unconditionally, for every adapter/account/surface;
- non-user identity: rejected;
- uid 0 user: allowed unconditionally;
- any other user needs at least one identity link for the exact adapter/account;
- that link must pin the exact
surfaceKind+surfaceId, or the caller UID must own the exact surface route; a legacy/unpinned link only permits a DM when link.actorId === surface.id.
Why each existing mechanism can't scope this
- Another
kind=user token for the same UID changes no authorization decision — identity links and surface routes are keyed to the UID, not the token/session (identity-links.ts#L1-L47, surface-routes.ts#L17-L50). Adding a pin or route for the automation lane broadens every session of that UID.
- A
service token (root-only to create: sys/token.ts#L12-L19) bypasses the wall for everything — the opposite of scoping.
- Manual
sys.link is root-only and stores no surface metadata, so it only enables the legacy actor-id-equals-surface DM case (sys/link.ts#L51-L82).
- Token records carry no adapter/account/surface scope — just UID, kind/role, optional device binding, label, expiry (auth-store.ts#L34-L71).
Proposed contract
- Extend token issue/storage/protocol/CLI with repeatable exact grants, minimally
adapterSendTargets: [{ adapter, accountId, surfaceKind, surfaceId }] (no wildcards in v1). Persist with the token; return from authenticateToken as authenticated credential context (token id + grants), not as UID metadata.
- Attach that credential context to
ConnectionIdentity/KernelContext; password-authenticated sessions carry no token grants.
- In
canSendToAdapterSurface, allow a non-root user when the existing UID link/route rule succeeds or the current token has an exact normalized adapter/account/kind/id grant. Check the grant on every adapter.send; never copy it into the UID-wide link/route stores.
- CLI:
gsv auth token create --kind user --allow-adapter-send adapter:account:kind:surface (repeatable); list output shows grants, never secrets. A user may mint a scoped child token only for targets already allowed to that user (or require root issuance); reject grant escalation.
- Tests: exact target succeeds; different surface/account/adapter fails; another token for the same UID does not inherit the grant; revoked/expired token fails; service behavior unchanged and explicitly covered. Bonus: validate
AdapterSurface.kind as required at the syscall boundary (an omitted kind can currently never satisfy pinned metadata or the DM fallback, which makes failures confusing).
With this, one dedicated user token can be issued for exactly an automation lane's reply targets while the UID's other sessions and concierge-style agents keep their current authority.
Happy to help test a branch — we have a live multi-host deployment with a reply-relay lane that currently dead-letters on this wall.
Summary
There is currently no way to authorize an automation lane to perform
adapter.sendto specific reply surfaces without granting far broader authority. Every available mechanism in v0.3.1 (unchanged through v0.3.3) is either UID-wide or service-wide, so a least-privilege outbound "reply relay" cannot be provisioned.Feature request: token-bound
adapter.sendgrants (exact adapter/account/surface targets carried on the token and checked per request).Current authorization wall (v0.3.1 sources)
handleAdapterSendrejects before reaching the adapter worker whencanSendToAdapterSurfacefails (adapter-handlers.ts#L177-L205). The wall (adapter-handlers.ts#L217-L267):serviceidentity: allowed unconditionally, for every adapter/account/surface;surfaceKind+surfaceId, or the caller UID must own the exact surface route; a legacy/unpinned link only permits a DM whenlink.actorId === surface.id.Why each existing mechanism can't scope this
kind=usertoken for the same UID changes no authorization decision — identity links and surface routes are keyed to the UID, not the token/session (identity-links.ts#L1-L47, surface-routes.ts#L17-L50). Adding a pin or route for the automation lane broadens every session of that UID.servicetoken (root-only to create: sys/token.ts#L12-L19) bypasses the wall for everything — the opposite of scoping.sys.linkis root-only and stores no surface metadata, so it only enables the legacy actor-id-equals-surface DM case (sys/link.ts#L51-L82).Proposed contract
adapterSendTargets: [{ adapter, accountId, surfaceKind, surfaceId }](no wildcards in v1). Persist with the token; return fromauthenticateTokenas authenticated credential context (token id + grants), not as UID metadata.ConnectionIdentity/KernelContext; password-authenticated sessions carry no token grants.canSendToAdapterSurface, allow a non-root user when the existing UID link/route rule succeeds or the current token has an exact normalized adapter/account/kind/id grant. Check the grant on everyadapter.send; never copy it into the UID-wide link/route stores.gsv auth token create --kind user --allow-adapter-send adapter:account:kind:surface(repeatable); list output shows grants, never secrets. A user may mint a scoped child token only for targets already allowed to that user (or require root issuance); reject grant escalation.AdapterSurface.kindas required at the syscall boundary (an omittedkindcan currently never satisfy pinned metadata or the DM fallback, which makes failures confusing).With this, one dedicated user token can be issued for exactly an automation lane's reply targets while the UID's other sessions and concierge-style agents keep their current authority.
Happy to help test a branch — we have a live multi-host deployment with a reply-relay lane that currently dead-letters on this wall.