The shape
A table keyed by the tenant column — one row per caller — has no REST shape a resource can generate. From the studio-apps port (SQLB-PORT-FEEDBACK.md finding 35), billing's billing_subscriptions is keyed by org_id, which is also the scope column. The two ops on offer are both wrong:
OpList answers a one-element {items:[…]} envelope for a resource that is definitionally singular — every client unwraps items[0] forever;
OpRead at /billing-subscriptions/{org_id} asks the client to send its own tenant id, a value the server already holds and the BeforeQuery hook already enforces — so the path parameter is either redundant or a lie, and a mismatch is a 404 that means "you typed your own name wrong".
The port's conclusion was to expose nothing and keep the one handler hand-written — the right call today, and the reason this is worth a spelling: the module kept a handler only because the generated surface cannot say "the caller's row".
Why it recurs
The port names the class: "every payments, webhook or sync-target table in this platform has it" — any table whose writers are a third party (PSP webhooks, a reconcile worker) and whose one client-facing operation is "show me mine". Settings-per-tenant, profile-per-user, subscription-per-org are all this shape. Each one is currently a permanent hand-written handler beside an otherwise fully-declared module.
What a singleton op could be
OpSingleton (or rest.Singleton): GET <path> — no {id} segment — answering the caller's one row as a bare object, confined by the same BeforeQuery hook every other read runs, 404 when the hook's predicate matches nothing. The Scoped obligation applies unchanged; in fact the op only makes sense on a Scoped table, which is checkable at mount. Optionally OpUpdate on the same path for the settings-table variant where the client may write its own row.
This is the same family as #101 (read-only mount as a first-class shape): the mounts an adopting application actually reaches for, offered as declarations rather than left as the residue of a port.
The shape
A table keyed by the tenant column — one row per caller — has no REST shape a resource can generate. From the studio-apps port (SQLB-PORT-FEEDBACK.md finding 35), billing's
billing_subscriptionsis keyed byorg_id, which is also the scope column. The two ops on offer are both wrong:OpListanswers a one-element{items:[…]}envelope for a resource that is definitionally singular — every client unwrapsitems[0]forever;OpReadat/billing-subscriptions/{org_id}asks the client to send its own tenant id, a value the server already holds and theBeforeQueryhook already enforces — so the path parameter is either redundant or a lie, and a mismatch is a 404 that means "you typed your own name wrong".The port's conclusion was to expose nothing and keep the one handler hand-written — the right call today, and the reason this is worth a spelling: the module kept a handler only because the generated surface cannot say "the caller's row".
Why it recurs
The port names the class: "every payments, webhook or sync-target table in this platform has it" — any table whose writers are a third party (PSP webhooks, a reconcile worker) and whose one client-facing operation is "show me mine". Settings-per-tenant, profile-per-user, subscription-per-org are all this shape. Each one is currently a permanent hand-written handler beside an otherwise fully-declared module.
What a singleton op could be
OpSingleton(orrest.Singleton):GET <path>— no{id}segment — answering the caller's one row as a bare object, confined by the sameBeforeQueryhook every other read runs, 404 when the hook's predicate matches nothing. The Scoped obligation applies unchanged; in fact the op only makes sense on a Scoped table, which is checkable at mount. OptionallyOpUpdateon the same path for the settings-table variant where the client may write its own row.This is the same family as #101 (read-only mount as a first-class shape): the mounts an adopting application actually reaches for, offered as declarations rather than left as the residue of a port.