Repository navigation
feat(plans): the assurance dimension, declared and enforced - #79
Merged
Merged
Conversation
The register and the framework mappings are what an assurance buyer buys, so which frameworks a plan includes, how long evidence is kept, and whether delivery runs on a schedule are plan attributes now — and, more to the point, they are **enforced**. A plan card promising framework coverage the product gives everyone is a promise that means nothing, and the two can only be kept in step by one table feeding both. `haldir_tiers.assurance(tier)` returns the entitlements through the rename alias; `feature_lines()` renders them, so the card is generated from the same block the compliance routes filter by: free SOC 2 · 30-day retention usage + EU AI Act and ISO/IEC 42001 · 90-day retention · scheduled delivery enterprise + custom retention The evidence pack, the manifest, the score and the HTML admin view all pass the tenant's entitlement, resolved from the subscriptions table — the only thing that raises a tenant's tier, since `POST /v1/keys` deliberately always mints a free key. When a plan excludes a framework the pack says so: `frameworks_excluded` names it, and both rendered forms print "Not included in this plan". An auditor must not read an omission as "this evidence does not exist". The round-trip of the reassurance: the manifest and the full pack filter identically, so their digests still agree — a manifest that filtered differently would make the verification instructions wrong. Display names for the cards stay local to `haldir_tiers` with a comment explaining why (that module may import only `typing`, which is what lets it be the single definition), and `tests/test_assurance_plans.py` keeps them in step with `haldir_frameworks` in both directions — a framework with no card name would render its raw id on a pricing page. Verified: 1126 tests, flake8 clean, mypy clean over 31 files. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #78 (which is stacked on #77). Merge order: #77 → #78 → this, then retarget to
main.Why this exists
The register and the framework mappings are what an assurance buyer buys — and until now the plan table said nothing about them. Worse, mapping content existed but no plan claimed it, so there was nothing to sell and nothing to withhold.
This adds the dimension and enforces it. A plan card promising framework coverage the product gives everyone is a promise that means nothing; the two stay in step because one table feeds both.
Enforcement, not advertising
haldir_tiers.assurance(tier)→ entitlements, through thepro → usagealiasfeature_lines()renders the card from the same block the routes filter by — so "mapped to the EU AI Act" on the pricing page is exactly the set the pack will containPOST /v1/keysdeliberately always mints a free key)frameworks_excluded, and both rendered forms print "Not included in this plan" naming them. An auditor must not read "no AI Act mapping" when it means "not in this plan".The constraint that shaped it
haldir_tiersmay import onlytyping(a test enforces it; that's what lets it be the single definition without pulling Flask into pure code). So the card's display names are local, with a comment saying why, andtests/test_assurance_plans.pykeeps them in step withhaldir_frameworksin both directions — a framework with no card name would render its raw id (iso_42001) on a pricing page.Other tests: entitlements grow monotonically with the plan (a cheaper tier holding something the pricier one lacks reads as a downgrade), retention is a window or
None— never0, which would read as "keeps nothing", the retired name carries identical entitlements, and every entitled framework exists.Verification
1126 tests, flake8 clean, mypy clean. Free-tenant and subscribed-tenant behaviour checked over HTTP against a real subscription row, with cleanup so the session-scoped DB is not left with a subscribed tenant.
What this does not do
It does not set prices. The tiers keep their current pricing (free / metered / contact-sales) — the entitlement structure is what a price attaches to, and the numbers are a decision to make deliberately.
🤖 Generated with Claude Code