Repository navigation
Harden account identity normalisation and enterprise dispatch - #62
Conversation
Follow-up to maiphucgiang#60, addressing review findings on the enterprise dispatch and the identity rules it relies on. Enterprise dispatch (finding A): a credential that is enterprise by type alone needs one tenant lookup. If that lookup cannot confirm the tenant, the sync now raises a readable error instead of passing an empty id, which fell through to the personal endpoint and reported an enterprise account as having no credits at all. Personal accounts never trigger the lookup, so their path is unchanged. Identity normalisation (finding B): a numeric uid is a real identity. It is now normalised through one shared helper instead of being treated as missing, so two different numeric uids can no longer collapse onto a single account key and a numeric uid hashes exactly like its string form. The email fallback moved to the same helper and is now reachable from import validation, which previously rejected uid-less credentials before the fallback could run - it was effectively dead code. Placeholders ("unknown", "手动添加", "未命名账号") and values that merely repeat the nickname or uid are still rejected so unrelated accounts never merge, and _sync_credits reads the identity from the credential itself rather than rebuilding it from request headers, which would have diverged for a uid-less account. Response codes (finding C): the enterprise allowance and the console-accounts helper now agree on accepting 0 and 200, matching the existing precedent in auth_oauth. Failure codes, HTTP failures, non-JSON bodies and transport errors all produce a readable enterprise error rather than leaking a raw network exception. account_key is unchanged for every existing credential: the 11-credential fixture and the persisted credit ledger reproduce their historical identities byte for byte, so no migration is required. 17 new tests in tests/test_identity_hardening.py reproduce each defect before the fix; the pre-existing suite shows no new failures and the GUI suite stays green at 79.
Reviewer's GuideThis PR hardens account identity handling with a shared canonicalization path, prevents unresolved enterprise credentials from being billed through the personal endpoint, and makes enterprise response/error handling consistent and user-readable, with regression tests covering identity collisions, fallback validation, dispatch isolation, and response failures. Sequence diagram for enterprise credit dispatch and failure isolationsequenceDiagram
participant Sync as CreditSync
participant Dispatch as _enterprise_dispatch_id
participant Resolver as resolve_enterprise_id
participant Credits as fetch_credits
participant Personal as RESOURCE_PATH
participant Enterprise as ENTERPRISE_USAGE_PATH
Sync->>Dispatch: _enterprise_dispatch_id(token, account, uid, domain)
alt Stored enterprise_id
Dispatch-->>Sync: enterprise_id
else Enterprise account without tenant id
Dispatch->>Resolver: resolve_enterprise_id(token, uid, domain)
alt Tenant resolved
Resolver-->>Dispatch: enterprise_id
Dispatch-->>Sync: enterprise_id
else Tenant unresolved
Resolver-->>Dispatch: None
Dispatch-->>Sync: RuntimeError
end
else Personal account
Dispatch-->>Sync: empty dispatch id
end
Sync->>Credits: fetch_credits(..., enterprise_id)
alt Enterprise dispatch
Credits->>Enterprise: POST enterprise usage request
else Personal dispatch
Credits->>Personal: POST personal usage request
end
Flow diagram for canonical identity derivationflowchart TD
A[Credential account data] --> B[normalize_uid]
B -->|valid uid| E[canonical_uid]
B -->|missing or invalid uid| C[identity_email]
C -->|valid email| E
C -->|placeholder or invalid email| D[Reject credential]
E --> F[account_key]
F --> G[summary uid and sync identity]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Follow-up to #60, addressing the review findings from Sourcery that held up. Relates to #59. Findings 1, 3, 4, 7, 8, 9 are fixed. Finding 2 (unlimited allowances dropped by the ledger) was already fixed upstream in 15bb2fd before the merge. Findings 5 and 10 were verified as false positives — the measurement evidence is in the PR description.
|
There was a problem hiding this comment.
Hey - I've reviewed your changes and they look great!
Sourcery assessment
Needs a human reviewer. A uid-less credential now uses its email as the account key, and enterprise credit lookups are routed by the resolved tenant ID. If identity normalization or dispatch is wrong, credit-sync records could be associated with the wrong account or show an incorrect balance; reverting restores the prior behavior, and those records can be reconciled.
Follow-up to #60 (issue #59). Addresses the Sourcery review findings that held up; two of its ten findings were false positives and are documented below.
A — Unresolvable tenant fell back to the personal endpoint
A credential that is enterprise by type alone (historical backups) needs one tenant lookup. When that lookup failed,
resolve_enterprise_id()returnedNoneandor ""passed an empty id, so the enterprise branch was skipped and the personal endpoint answered with an empty package list — exactly the "no credits available" misreport issue #59 was about._enterprise_dispatch_id()now raises a readable error when the tenant cannot be confirmed. Personal accounts never call the lookup, so their path is byte-for-byte unchanged (verified across all 10 personal credentials: zero lookup calls).B — Identity normalisation
isinstance(uid, str)treated a numeric uid as missing, so12345and67890both fell back to""and produced the sameaccount_key. They now normalise through one shared helper: numeric and string forms hash identically, different uids never collide.validate_cred_data()rejected uid-less credentials before_credential_identity()could reach it. Both now use the same canonical helper, so a uid-less credential with a real email imports and identifies correctly.unknown,手动添加,未命名账号, non-email values, and values that merely repeat the nickname or uid are refused, so unrelated accounts never merge. A credential with no determinable identity is rejected rather than silently merged._sync_credits()derived its key fromX-User-Idwhile the pool hashed the credential data; for a uid-less account those diverged and sync would be rejected forever. It now readssummary()["account_key"].summary()["uid"]is the canonical identity value, so duplicate detection and durability gates no longer skip uid-less accounts (reload()previously admitted them twice).C — Response codes
The enterprise allowance and the
console-accountshelper in the same module disagreed ((0, None)vs(0, 200, None)). Both now accept 0 and 200, matching the existing precedent inauth_oauth.py. Failure codes (400/401/403/500/10085), HTTP failures, non-JSON bodies and transport errors all produce a readable enterprise error — previously aConnectErrorescaped raw.Compatibility
account_keyis unchanged for every existing credential. Evidence:No migration required. Personal accounts keep using
RESOURCE_PATH; enterprise accounts keep usingENTERPRISE_USAGE_PATH.Verification
account_keyfor the 11-credential fixture1995.93(= 2000 − 4.07)Independent audit of identity collisions (13-case exhaustive matrix), historical ledger compatibility, and personal/enterprise endpoint isolation all pass.
Findings that did not hold up
HTTP 200 code=0both with and without the tenant headers. Not a defect.account_key; the persisted ledger reproduces every historical identity. The only theoretical divergence isenterprise_id-only credentials, of which the fixture has none.Both are called out so the review record is accurate rather than silently dropped.
Summary by Sourcery
Harden enterprise credit dispatch and account identity handling while preserving existing account keys and personal-account behavior.
Bug Fixes:
Enhancements:
Tests: