A started trial never showed as entitled. Two causes, both fixed here.
1. The live path never fetched entitlements at all
loadTenantForShell resolves fixture tenants first and, for a real tenant, returned a shim with entitled: [] and products: []hard-coded. So entitled.includes(p.id) was false for everything, regardless of what the registry said. It now fetches /v1/catalog and /v1/entitlements (both best-effort — a failure degrades to an empty grid rather than 404-ing the page) and filters to enabled + not expired.
2. The ids could not match even in principle
The registry catalog offers certifai and compliance. The portal hard-coded compliance-scanner — a different product entirely, which lives in ~/workspace/compliance-scanner and is not in this catalog. The findings filter hard-coded the same wrong id.
The fix removes the class of bug, not one constant
Products are now derived from the registry catalog, and entitled is built from the same registry keys — so the two sides cannot drift apart again. The findings filter likewise derives from the tenants own products.
tsc --noEmit 0 · next build 0. Fixture tenants unaffected — they still carry their own products and the derived filter uses those.
Note: I started a real trial entitlement on the dev acme tenant to verify this end to end. It expires 2026-09-16 and can be revoked if unwanted.
**A started trial never showed as entitled.** Two causes, both fixed here.
### 1. The live path never fetched entitlements at all
`loadTenantForShell` resolves fixture tenants first and, for a **real** tenant, returned a shim with `entitled: []` and `products: []` **hard-coded**. So `entitled.includes(p.id)` was false for everything, regardless of what the registry said. It now fetches `/v1/catalog` and `/v1/entitlements` (both best-effort — a failure degrades to an empty grid rather than 404-ing the page) and filters to enabled + not expired.
### 2. The ids could not match even in principle
The registry catalog offers `certifai` and `compliance`. The portal hard-coded `compliance-scanner` — **a different product entirely**, which lives in `~/workspace/compliance-scanner` and is not in this catalog. The findings filter hard-coded the same wrong id.
## The fix removes the class of bug, not one constant
Products are now derived from the registry catalog, and `entitled` is built from the **same** registry keys — so the two sides cannot drift apart again. The findings filter likewise derives from the tenants own products.
## Verified against the live dev registry
```
/v1/catalog -> [certifai, compliance]
start trial (acme, compliance) -> 201, expires 2026-09-16
/v1/entitlements -> compliance (enabled)
entitled = [compliance]
entitled.includes("compliance") -> true
entitled.includes("certifai") -> false
```
`tsc --noEmit` 0 · `next build` 0. Fixture tenants unaffected — they still carry their own products and the derived filter uses those.
Note: I started a real trial entitlement on the dev `acme` tenant to verify this end to end. It expires 2026-09-16 and can be revoked if unwanted.
A started trial never showed as entitled. Two causes, both fixed here.
1. The live path never fetched entitlements at all. loadTenantForShell resolves
fixture tenants first and, for a REAL tenant, returned a shim with
`entitled: []` and `products: []` hard-coded — so `entitled.includes(p.id)`
was false for everything no matter what the registry said. It now fetches
/v1/catalog and /v1/entitlements (both best-effort: a failure degrades to an
empty grid rather than 404-ing the whole page) and filters entitlements to
enabled + not expired.
2. The ids could not match even in principle. The registry catalog offers
`certifai` and `compliance`; the portal's hard-coded list used
`compliance-scanner` — a different product entirely (it lives in
~/workspace/compliance-scanner and is not in this catalog). The findings
filter hard-coded the same wrong id.
The fix removes the class of bug rather than renaming one constant: products are
now derived from the registry catalog, and `entitled` is built from the SAME
registry keys, so the two sides cannot drift apart. The findings filter is
likewise derived from the tenant's own products.
Verified against the live dev registry: catalog returns
[certifai, compliance]; after starting a trial for acme, entitlements returns
`compliance` (enabled, expires 2026-09-16), and the mapping yields
entitled=[compliance] with entitled.includes("compliance")=true and
certifai=false. tsc --noEmit 0, next build 0.
Fixture tenants are unaffected — they still carry their own products, and the
derived filter uses those.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CNdLL9BdsWm7MCyui5ffPD
sharang
merged commit faa6e83513 into main2026-09-02 09:47:53 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
A started trial never showed as entitled. Two causes, both fixed here.
1. The live path never fetched entitlements at all
loadTenantForShellresolves fixture tenants first and, for a real tenant, returned a shim withentitled: []andproducts: []hard-coded. Soentitled.includes(p.id)was false for everything, regardless of what the registry said. It now fetches/v1/catalogand/v1/entitlements(both best-effort — a failure degrades to an empty grid rather than 404-ing the page) and filters to enabled + not expired.2. The ids could not match even in principle
The registry catalog offers
certifaiandcompliance. The portal hard-codedcompliance-scanner— a different product entirely, which lives in~/workspace/compliance-scannerand is not in this catalog. The findings filter hard-coded the same wrong id.The fix removes the class of bug, not one constant
Products are now derived from the registry catalog, and
entitledis built from the same registry keys — so the two sides cannot drift apart again. The findings filter likewise derives from the tenants own products.Verified against the live dev registry
tsc --noEmit0 ·next build0. Fixture tenants unaffected — they still carry their own products and the derived filter uses those.Note: I started a real trial entitlement on the dev
acmetenant to verify this end to end. It expires 2026-09-16 and can be revoked if unwanted.A started trial never showed as entitled. Two causes, both fixed here. 1. The live path never fetched entitlements at all. loadTenantForShell resolves fixture tenants first and, for a REAL tenant, returned a shim with `entitled: []` and `products: []` hard-coded — so `entitled.includes(p.id)` was false for everything no matter what the registry said. It now fetches /v1/catalog and /v1/entitlements (both best-effort: a failure degrades to an empty grid rather than 404-ing the whole page) and filters entitlements to enabled + not expired. 2. The ids could not match even in principle. The registry catalog offers `certifai` and `compliance`; the portal's hard-coded list used `compliance-scanner` — a different product entirely (it lives in ~/workspace/compliance-scanner and is not in this catalog). The findings filter hard-coded the same wrong id. The fix removes the class of bug rather than renaming one constant: products are now derived from the registry catalog, and `entitled` is built from the SAME registry keys, so the two sides cannot drift apart. The findings filter is likewise derived from the tenant's own products. Verified against the live dev registry: catalog returns [certifai, compliance]; after starting a trial for acme, entitlements returns `compliance` (enabled, expires 2026-09-16), and the mapping yields entitled=[compliance] with entitled.includes("compliance")=true and certifai=false. tsc --noEmit 0, next build 0. Fixture tenants are unaffected — they still carry their own products, and the derived filter uses those. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CNdLL9BdsWm7MCyui5ffPD