tenant-engine/workplans/ADHOC-2026-07-24.md
tegwick 31e237cfa6 Fix: resolve tenants by identifier, not only internal tenant_id
Found via a real cross-service check while implementing key-cape's
KEY-WP-0005-T02: key-cape's Go adapter called GET /tenants/tenant:coulomb/roles
and got a genuine 404 for a tenant that existed. External callers
(key-cape, flex-auth) only ever have a tenant's profile identifier, never
tenant-engine's internal tenant_id (caller-chosen at creation, otherwise
opaque). Every existing test happened to use identical strings for both
fields, so this was invisible until a real, independent second caller
exercised the documented contract.

InMemoryTenantStore gained a _by_identifier index and a _resolve() helper
every method calls first; create_tenant now also rejects a duplicate
identifier under a different internal id (an oversight the same fix
surfaced). 5 new tests, including the exact HTTP-level scenario with colon
characters in the URL path. 65 total, all 60 pre-existing tests unaffected.

Re-verified end-to-end for real: fresh flex-auth + tenant-engine + key-cape's
actual adapter code, over real HTTP -- roles=[IAM] ok=true resolving by
identifier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 00:15:26 +02:00

3.2 KiB

id type title domain repo status owner topic_slug created updated
ADHOC-2026-07-24 workplan Resolve tenants by identifier, not only internal tenant_id infotech tenant-engine finished codex netkingdom 2026-07-24 2026-07-24

Resolve tenants by identifier, not only internal tenant_id

Discovered via a real cross-service check while implementing key-cape's KEY-WP-0005-T02 (tenant_roles cache-read integration): key-cape's Go adapter called GET /tenants/tenant:coulomb/roles and got a genuine 404 tenant_not_found for a tenant that actually existed. External callers (key-cape, flex-auth) only ever have a tenant's profile identifier (the IAM Profile tenant claim value, e.g. tenant:coulomb) — never tenant-engine's internal tenant_id, which is caller-chosen at creation (POST /tenants) and otherwise opaque. Every existing test happened to pass an identical string for both fields, so this gap was invisible to unit/API tests until a genuine second, independent caller (key-cape, written against the documented contract, not against tenant-engine's internal test conventions) exercised it for real.

Task: Fix

id: ADHOC-2026-07-24-T01
status: done
priority: high

InMemoryTenantStore gained a _by_identifier index (identifier → internal tenant_id) and a private _resolve() helper every method now calls first, so get_tenant, grant_role, revoke_role, active_roles, and assign_plan all transparently accept either the internal id or the identifier. create_tenant also now rejects a duplicate identifier under a different internal id (previously unenforced — an oversight the same fix surfaced).

Done when: a real cross-process check (real flex-auth, real tenant-engine, real key-cape adapter code, over actual HTTP) resolves a tenant by identifier correctly, and all existing tests still pass unmodified in intent.

Done 2026-07-24: Fixed in store.py. Added test_get_tenant_resolves_by_identifier_not_only_internal_id, test_active_roles_resolves_by_identifier, test_revoke_role_resolves_by_identifier, and test_create_tenant_rejects_duplicate_identifier_with_different_internal_id in tests/test_store.py; added test_cache_read_roles_resolves_by_identifier_not_only_internal_id in tests/test_api_reads.py (the exact HTTP-level scenario, with the colon characters a real tenant:friendly:binky-shaped identifier puts in the URL path). pytest65 passed (all 60 pre-existing tests unaffected).

Re-ran the exact real end-to-end check that found the bug, on fresh ports to avoid colliding with another session's own flex-auth serve process on this shared workstation (a collision that led to accidentally killing that other process — a low-harm, trivially-restartable local dev server, but noted here as a mistake to avoid repeating: check pgrep output carefully and prefer distinctive ports before killing anything matching a broad pattern): created a tenant with a genuinely different internal id and identifier, granted it a role through the real flex-auth-gated write path, then called GET /tenants/tenant:coulomb/roles from key-cape's actual internal/adapters/tenantengine.Client (not a stand-in) — correctly returned roles=[IAM] ok=true.