Skip to content

Budget cost-center derivation cannot support parent wallet ids containing ':' #64

Description

@c-1k

Raised by the Codex gate on #63 (review), dismissed for that PR after deliberation, filed here as the real follow-up.

The limitation

TrustTBClient.createUserWallet() accepts ordinary wallet ids containing a single colon (acct:123), but every budget entry point routes through costCenterUserId(), whose PARENT_USER_ID_PATTERN is /^[a-zA-Z0-9._@-]{1,128}$/ and excludes :. A tenant that keys wallets with colon-delimited ids therefore cannot call allocateBudget, reclaimBudget or getBudgetStatus against a wallet that is otherwise valid and funded.

Why it was not simply widened

The exclusion is load-bearing for the current encoding, not an oversight. The cost-center id is built by concatenation as parent + "::" + costCenter, and createUserWallet(id, { derived: true }) validates it by splitting on :: and rejecting any part that itself contains :. Widening PARENT_USER_ID_PATTERN alone would produce ids that the wallet validator rejects on the other side, and a parent ending in : would derive acct:::x, which split("::") reads as ["acct", ":x"] — exactly the ambiguity the reservation exists to prevent.

Codex agreed on deliberation: "simply widening PARENT_USER_ID_PATTERN would produce IDs that the wallet validator rejects... That is a product limitation, not a safe regex fix for this PR."

The actual fix

Derive the cost-center account id from a hash of the (parentUserId, costCenter) tuple rather than from a concatenated string — e.g. domain-separated and length-prefixed, so no parent/cost-center pair can produce another pair's id regardless of charset. That removes the need for a reserved separator entirely and lifts the : restriction as a side effect.

Worth doing sooner rather than later: the budget feature is new and unreleased, so no derived account ids exist in the wild yet. Once cost centers have been allocated against the string derivation, changing it becomes a migration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions