Skip to content

feat(web): the Plans page, inverted — a transparency artifact with nothing to click (#706) - #740

Merged
eaitbrahim merged 2 commits into
mainfrom
feat-706-plans
Sep 6, 2026
Merged

feat(web): the Plans page, inverted — a transparency artifact with nothing to click (#706)#740
eaitbrahim merged 2 commits into
mainfrom
feat-706-plans

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

Closes #706.

A retail broker's Plans & Features page is a tier matrix with "Current plan" and "Upgrade" badges. It is radically clear about pricing and honest as marketing — every row exists to move a reader one row down. Jesse paywalls even paper trading.

keel's constitution runs the other way: two of its eight numbered lines are "Paper is free and unlimited, forever" and "The free engine is never a demo." So this page takes the clarity and refuses the gating.

Traceability is the acceptance criterion, so it is mechanical

"Every claim on the page traces to the evolution plan or the constitution — no aspirational copy." A page of marketing sentences that merely sounded like the project's documents would pass a human reviewer and rot within a month.

So every claim carries the repository-relative path it was quoted from, and the tests open that file and look for the sentence. Verbatim, never paraphrased — a paraphrase is a new claim wearing the authority of an old one.

The check runs in both directions, and the second is why it earns its place. A claim nobody can trace fails the build; so does a claim the project has since edited out of its own documents — a promise page still making a promise nobody kept. Demonstrated by mutation: adding a ninth line to §4 of the evolution plan fails the build until the page carries it, because the count comes out of the document rather than a literal.

The whole constitution, not a selection. A page that quoted seven of eight would be choosing which of the project's own commitments a reader sees, on the page whose entire subject is what the project promises.

Nothing is for sale, and the page says so from the table

ADR 0004's answer is "not now" and none of its four triggers has fired, so no tier is shipped. That's a field, not a sentence, because "this does not exist yet" is the most important thing the table says and a sentence can be edited past without anything failing. The headline is derived from the field, so the two can't disagree — and the direction they'd disagree in is a shipped product under a headline saying there's nothing to buy.

available_now is separate and true of exactly one row: the free tier is what the reader is already running. Four rows presented as four choices would be a paywall matrix with the prices greyed out.

The refusals are structural, not editorial

  • No cta, url, action or contact key on the payload, and nothing beneath it to build one from — asserted over the serialised JSON, so a key added anywhere inside is caught. render.js cannot make a button out of a page that never sends it a destination.
  • The view builds no anchor, no button, no form, and binds no handler — pinned against the parsed function bodies with string literals kept (the recurring lesson in this series: a scan that strips them cannot see el("a")).
  • Citations are <code>, not links. keel serve is a loopback SQLite reader with no document server behind it, so a link would 404; a path a reader opens in their own checkout is the honest form, and it keeps the page free of anchors entirely.
  • No sort control, on the route or in the view. A tier table ordered by price is a shopping comparison.

No database

The one route whose subject is the project rather than the deployment. It reads no repository, no config and no network, so it answers on a machine with nothing set up — a page explaining that keel is free and runs entirely on the operator's own hardware would be a strange one to gate behind having already installed it.

Gates

  • 6,174 passed / 3 skipped; mypy clean on 443 files; ruff clean (the one E501 is pre-existing in docs/experiments/)
  • 12 mutants killed: a constitution line softened into marketing · the constitution quoted incompletely · a price drifting from the published one · a tier marked shipped ahead of the PR that ships it · the page growing an upgrade link · the payload carrying a checkout destination · the refusal list dropped from the page · the table becoming sortable by price · a citation becoming a link · the free tier ceasing to be the one that exists · for_sale hard-coded rather than read from the table · the route starting to require a database

🤖 Generated with Claude Code

https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6

eaitbrahim and others added 2 commits September 6, 2026 16:24
…thing to click (#706)

A retail broker's Plans & Features page is a tier matrix with "Current plan" and
"Upgrade" badges. It is radically clear about pricing and honest AS MARKETING,
because every row exists to move a reader one row down. Jesse paywalls even paper
trading.

keel's constitution runs the other way. Two of its eight numbered lines are
"Paper is free and unlimited, forever" and "The free engine is never a demo". So
this page takes the clarity and refuses the gating: what runs on this device and
why it costs nothing, what a tier would change if one existed, and the named
trigger that must fire before any of it does.

TRACEABILITY IS THE ACCEPTANCE CRITERION, SO IT IS MECHANICAL

"Every claim on the page traces to the evolution plan or the constitution -- no
aspirational copy." A page of marketing sentences that merely SOUNDED like the
project's documents would pass a human reviewer and rot within a month.

So every claim carries the repository-relative path it was quoted from, and the
tests open that file and look for the sentence. Verbatim, never paraphrased: a
paraphrase is a new claim wearing the authority of an old one.

The check runs in BOTH directions, and the second is why it earns its place. A
claim nobody can trace fails the build -- and so does a claim the project has
since edited out of its own documents, which is the failure that would otherwise
go unnoticed: a promise page still making a promise nobody kept. Demonstrated:
adding a ninth line to §4 of the evolution plan fails this until the page carries
it, because the count comes out of the document rather than a literal.

THE WHOLE CONSTITUTION, NOT A SELECTION

Eight lines. A page that quoted seven would be choosing which of the project's
own commitments to show a reader, on the page whose entire subject is what the
project promises.

NOTHING IS FOR SALE, AND THE PAGE SAYS SO FROM THE TABLE

ADR 0004's answer is "not now" and none of its four triggers has fired, so no
tier is `shipped`. That is a FIELD rather than a sentence, because "this does not
exist yet" is the most important thing the table says and a sentence can be
edited past without anything failing -- and the headline is derived from the
field, so the two cannot come to disagree. The direction they would disagree in
is a shipped product under a headline saying there is nothing to buy.

`available_now` is separate and true of exactly one row: the free tier is what
the reader is already running. Four rows presented as four CHOICES would be a
paywall matrix with the prices greyed out.

THE REFUSALS ARE STRUCTURAL, NOT EDITORIAL

There is no `cta`, `url`, `action` or `contact` key on this payload, and nothing
beneath it a client could build one from -- asserted over the serialised JSON, so
a key added anywhere inside is caught. `render.js` cannot make a button out of a
page that never sends it a destination.

The view builds no anchor, no button, no form and binds no handler, pinned
against the parsed function bodies with string literals KEPT (the earlier lesson
in this series: a scan that strips them cannot see `el("a")`). Citations are
`<code>` and not links -- `keel serve` is a loopback SQLite reader with no
document server behind it, so a link would 404, and a path a reader opens in
their own checkout is the honest form.

And the tier table has no sort control, on the route or in the view. A tier table
ordered by price is a shopping comparison.

NO DATABASE

The one route here whose subject is the PROJECT rather than the deployment. It
reads no repository, no config and no network, so it answers on a machine with
nothing set up -- a page explaining that keel is free and runs entirely on the
operator's own hardware would be a strange one to gate behind having already
installed it.

Twelve mutants killed: a constitution line softened into marketing, the
constitution quoted incompletely, a price drifting from the published one, a tier
marked shipped ahead of the PR that ships it, the page growing an upgrade link,
the payload carrying a checkout destination, the refusal list dropped from the
page, the table becoming sortable by price, a citation becoming a link, the free
tier ceasing to be the one that exists, `for_sale` hard-coded rather than read
from the table, and the route starting to require a database.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6
…resence, not faithfulness

An independent review found thirteen issues. The page's whole claim is that a
reader can check it, so the ones that mattered were all about the checking.

A SUBSTRING IS NOT A QUOTE

Every traceability assertion was `claim.text in document`. That verifies
PRESENCE, and presence is not faithfulness: "reopens affiliate links" is a
contiguous substring of ADR 0004, where the sentence reads "None of these four
reopens affiliate links (rule 2, above)". Quoted alone under the heading "What no
tier will ever gate" it would invert the record, carry a checkable citation, and
pass.

A quote must now begin where a statement begins -- a list item, a table cell, a
paragraph, a sentence within one -- with continuation lines joined first, because
these documents wrap at 100 columns and a quoted sentence should not have to
reproduce where the wrapping fell. That does not make a quote honest; nothing
can. It removes the trick of starting a sentence in the middle to drop the word
that reverses it.

AND ANCHORING CATCHES A QUOTE THAT BEGINS LATE, NEVER ONE THAT ENDS EARLY

Which is the dangerous half for a trigger. All three truncations in the first cut
were in ADR 0004's "What fires it" column, and each cut widened the condition:
trigger 3 lost "not a feature request, a request to pay for upkeep" -- the clause
separating it from an ordinary feature request; trigger 1 lost most of its
condition; trigger 4 lost "evidenced by external contributors, not by the
founder's schedule", which is what stops a tired maintainer being the evidence.

Each firing condition is now compared WHOLE against the record's own table, cell
for cell. The page was publishing an easier promise than the one that was made.

THE LONGEST STRING ON THE PAGE WAS TRACED BY NOTHING

`every_claim` yielded promises and not `Tier.buys` -- a whole table column -- and
the tier test checked name, price and promise only. A contributor could have
added "early access to new rule families" to a tier, rendered it with a citation
beside it, and passed every gate. All four cells are traced now, and a new test
compares what the payload SHOWS against what is traced, so the next omission
fails rather than waiting to be noticed.

THE PURCHASE BLOCKLISTS COULD NOT SEE THE RAIL THE PROJECT CHOSE

Two lists, neither a superset of the other, both built from generic e-commerce
words. Six real payment CTAs passed both -- among them `lightning:lnbc...` and
`bitcoin:bc1q...`, which ADR 0004 and Phase F both name as the intended rail.
One list now, shared, carrying those schemes.

The first belt is stronger and is worth naming: every cell of the tier table has
to appear verbatim in the evolution plan, so an injected call to action fails
traceability without anyone having had to think of its wording.

AND THE NEW "NOTHING CLICKABLE" TEST WAS WEAKER THAN ITS OWN SIBLING

#704's blocked `button` and `href` unquoted; #706's blocked `"button"` quoted and
dropped `href`. So `el('button', 'cta', 'Buy Pro')` in single quotes, and a
`setAttribute` of `href` on a span, passed the newer test while failing the older
one. One shared token list now, unquoted.

A SHIPPED TIER WOULD HAVE READ AS NOT A THING YET

`state` was `GOOD if available_now else UNKNOWN` and ignored `shipped` -- so the
day a Pro tier shipped, the one row that had become real would have worn the same
badge as the three that had not, under a docstring asserting every other row is
unknown because it is "simply not a thing yet". And that row's display collapsed
to the word itself: "shipped / shipped", on the only status a future PR will
actually set.

THE CONSTITUTION COUNTER FAILED ON THREE INNOCENT REFORMATS

"First character is a digit" counted a numbered sub-point and a prose line
beginning with a date; a renumbered heading raised a bare IndexError instead of
saying what was wrong. Anchored regex and a reported failure now, with the two
miscounted shapes as a fixture.

Also: the page sent `as_of`/`generated_at` and rendered neither -- `gates_payload`
next door describes capability rather than deployment and sends no timestamp at
all, and nothing on this page was OBSERVED at a moment, so both are gone.
`plansView` was inserted between `gatesView`'s jsdoc and `gatesView`, orphaning
it -- moved back. Three of six "never paywalled" entries were constitution lines
repeated from the section above, two of which are not things a tier could gate;
the list is six claims that are actually about gating, in prose rather than with
`**` markers rendering as literal asterisks through `plain()`. The section check
is derived from the payload's keys rather than a list of literals. And two
comments naming nav counts that were wrong by six ("the eighth nav entry", "seven
entries today") say what they mean instead of counting.

Eleven mutants killed, including: a promise nobody made added to a tier's `buys`,
a lightning invoice and a bitcoin URI in a price, a single-quoted Buy button, an
href set on a span, a trigger truncated back to a wider condition, a quote
starting mid-sentence to invert the record, a shipped tier reading as unknown,
the counter reverting to the loose predicate, and a payload section that stops
being rendered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KZZxmspQXe5qJ9FAsG13s6
@eaitbrahim
eaitbrahim merged commit af75938 into main Sep 6, 2026
4 checks passed
@eaitbrahim
eaitbrahim deleted the feat-706-plans branch September 6, 2026 21:13
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.

[web] Plans page, inverted: a manifesto of zero-cost permanence instead of a paywall matrix

1 participant