Skip to content

feat(plans): the assurance dimension, declared and enforced - #79

Merged
ExposureGuard merged 2 commits into
mainfrom
feat/assurance-plans
Oct 4, 2026
Merged

ExposureGuard merged 2 commits into
mainfrom
feat/assurance-plans

Conversation

@ExposureGuard

Copy link
Copy Markdown
Owner

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.

Plan Frameworks Retention Scheduled delivery
free SOC 2 30 days —
usage + EU AI Act, ISO/IEC 42001 90 days ✅
enterprise all three custom ✅

Enforcement, not advertising

  • haldir_tiers.assurance(tier) → entitlements, through the pro → usage alias
  • feature_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 contain
  • 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 — POST /v1/keys deliberately always mints a free key)
  • An omission is never silent: a filtered pack carries 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 manifest and the pack filter identically, so their digests still agree — a manifest that filtered differently would make its own verification instructions wrong. There's a test.

The constraint that shaped it

haldir_tiers may import only typing (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, 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 (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 — never 0, 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

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>
@ExposureGuard
ExposureGuard merged commit a8235e3 into main Oct 4, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant