EffectiveOrg returned the home org for an ask outside the membership set and
called that "fail closed". It is not. This function selects the PAYER, so
falling back does not fail — it SUCCEEDS against a different economic principal.
The model runs, the tokens meter, the ledger writes, and the wrong account is
charged with no error anywhere. An obvious failure would have been safer.
Measured, which is how this surfaced: org `hanzo` holds 14,986,317 and user
`hanzo/z` holds 0. A request selecting `hanzo` that the subject could not bill
resolved silently to `hanzo/z` and answered "insufficient balance" — a true
statement about an account nobody meant to charge.
The contract is now (string, error):
requested == "" -> owner, nil
requested == owner -> owner, nil
requested in memberships -> requested, nil
otherwise -> "", ErrOrgForbidden
Unauthorized and nonexistent are the SAME refusal so the ask cannot be used to
enumerate orgs; callers surface both as 403.
An empty owner now refuses an explicit ask too. It previously took the first
branch and answered ("", nil) — success naming no payer, the same shape as the
fallback being removed, one level down.
Breaking on purpose, no EffectiveOrgStrict alongside it: leaving the permissive
signature reachable invites the next caller to pick it by accident, and this is
exactly the API where "accidentally permissive" spends someone else's money.
LedgerOrg is untouched. Its sudo->home rule is already correct: an admin acting
inside a customer's org must never spend the customer's balance.
21 table cases become refusals; restoring the fallback fails all 21. Six call
sites in the suite migrate to the two-value form.
Co-authored-by: Hanzo Dev <[email protected]>
account
The billing-account rule. One value, one decision, no dependencies.
account.Payer(account.Credential{Owner: org, Name: user, Account: claim}).Subject()
(Org, Subject) is the money's address — Org names the ledger that holds
it, Subject the key within that ledger. A deposit credits that address, the
spend-gate reads it, a usage debit spends it. One address, one answer, every
caller.
Payer is the whole point: it, and only it, decides whether a request pays from
an org pool or a person's own account. That decision shipped wrong twice
by being made in two places — a gate keyed on the pool while the debit spent a
person's wallet, so a funded pool green-lit a request that drained an empty
personal one, and an empty pool 402'd a funded person. Any new money path
resolves its address here rather than re-deriving it.
Why it is its own module
It has zero dependencies — go.mod is two lines and the code imports only
strings. That is what lets every layer import it: cloud's trust boundary
(clients/principal), its metering middleware, and commerce's billing resolver
all call the same rule without any of them depending on each other.
Folding it into the commerce service would invert that: identity code would have to import a ledger, a SQLite store and an HTTP surface to answer a pure question about who pays.
Layout
account.go—Account,Org/Person/Project,Parse,Payer,Credentialorg.go— org-level helpers
Licensed under MIT OR Apache-2.0, per HIP-0137.