symfonic.services.cost.ports¶
ports ¶
Port-registry row 5, finally expressed as a protocol.
BudgetCheck is the narrow admission read; BudgetStore (persistence)
stays exactly where it is, in symfonic.core.observability. BUD-5 is the
dividing line: limits, ledgers, upserts and degraded-mode reads are the runtime
service's, and the platform owns only the admission decision and the read
models.
Both protocols take a :class:SubjectScope rather than a tenant string. The
platform used to hand the budget gate a raw X-Tenant-ID header value so the
check could run before scope construction; BUD-3 retires that, and a port whose
signature cannot express "an unauthenticated string" is how the retirement
stays retired.
BudgetCheck ¶
Bases: Protocol
The admission read. One method, because it answers one question.
check
async
¶
May this scope incur another billable call?
is_admin is a policy input, not a caller-side branch (BUD-4): the
port decides whether an administrator bypasses, and the transport never
skips the call.
Source code in src/symfonic/services/cost/ports.py
CostReadModel ¶
Bases: Protocol
The projection /metrics/* renders (BUD-6).
summarize
async
¶
Aggregated spend for a scope and window. Tenant-scoped, always.
SEC-TEN-2 makes isolation unconditional — never gated on a collector being enabled — so an implementation that cannot scope a summary must refuse it rather than widen it.