Skip to content

docs(formal): generate PROFILES.md from the replay descriptors and shrink BEHAVIOR.md - #233

Merged
lan17 merged 3 commits into
mainfrom
claude/formal-profiles-doc
Oct 15, 2026
Merged

lan17 merged 3 commits into
mainfrom
claude/formal-profiles-doc

Conversation

@lan17

@lan17 lan17 commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Phase 3 of #220, item A4. One commit, base main.

What

Six sections of BEHAVIOR.md (6,600 of its 9,600 words) restated the replay descriptors in formal/replay/features.mjs, effects.mjs, local-clock.mjs and profiles/*.mjs: each profile's fixture, setup, actions and the driver commands their choices decode into. Those facts are now generated into formal/generated/PROFILES.md by node formal/tools/generate-catalog-docs.mjs --write, and make audit fails when the view is stale (--check).

Per profile the view gives: model, version and input encoding, smoke trace, the ports claiming it, the generation bounds and counts from execution.json, the required witness count, the fixture (per init choice where it varies), the setup commands, every action with its choice range and the decoded driver commands, and the observation channels the coordinator compares. A decoder that reads the observation or the environment (the "release the latest effect" actions, the wall-clock-stamped seeds) is evaluated against probes and the page says which keys it reads. Only a counter walking up (consecutive decodings differing in one numeric field) is abbreviated; a payload class or an overlay per choice is shown in full. The fixture and the setup come from bindings.mjs, the same functions the coordinator uses, so the view cannot drift from what the drivers receive. 17 profiles, 169 actions, 773 lines.

BEHAVIOR.md keeps what tables cannot state, condensed into one "Generated profiles" section: the trace contract, the value codes (which effects.mjs cites), the meaning of each observation channel (io, d, events, markers, compression, policyErrors), the rules each profile holds its drivers to, and how to replay a generated trace. The composition-profile summary now follows it and links the view.

Before After
BEHAVIOR.md words 9,597 5,563
Hand-written per-profile tables 124 rows across 6 sections 0 (generated view)
Where "what does action X decode to" lives prose that drifted from the descriptors rendered from the descriptors, freshness-checked

Two decisions to flag

Verification

  • make audit green with the new "Check the generated read views" step.
  • vitest run test/formal-catalog-docs test/formal-validation test/formal-exploration test/formal-source-audit test/formal-go-parity: all pass (the Rust prerequisite probe test fails locally only because Docker is not running here; it does not touch this change).
  • tsc --noEmit clean; node scripts/check-docs.mjs green (30 pages, every link in the generated view resolves).
  • Ledger: BEHAVIOR.md, AUTHORING.md, guides/README.md and formal/README.md re-keyed with --refresh; parity inputs re-pinned.

🤖 Generated with Claude Code

@codecov-commenter

codecov-commenter commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.73%. Comparing base (0b386be) to head (5bcd355).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #233      +/-   ##
==========================================
- Coverage   93.73%   93.73%   -0.01%     
==========================================
  Files          78       78              
  Lines       11357    11357              
  Branches      656      656              
==========================================
- Hits        10646    10645       -1     
- Misses        641      642       +1     
  Partials       70       70              
Flag Coverage Δ
go 91.12% <ø> (-0.06%) ⬇️
python 92.08% <ø> (ø)
rust 94.29% <ø> (ø)
typescript 96.56% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

lan17 added a commit that referenced this pull request Oct 15, 2026
Phase 3 of #220, item B1. One commit, base `main`.
Independent of #233.

## What

`node formal/tools/check-style.mjs --report` prints the readability
measurements #220 tracks, computed from the sources so progress is
reproducible:

| Measure | Value today |
| --- | --- |
| Quint files under formal/models | 82 |
| Lines over 120 characters (models / kernel / fixtures) | 658 (171 /
180 / 307) |
| Module header comment lines, total / longest / over 25 | 805 / 32 / 2
|
| Quint comment sentences, count / over 40 / over 60 / over 100 words /
longest / average | 1793 / 210 / 88 / 32 / 277 / 24.6 |
| Guide sentences, count / over 40 / over 60 / over 100 words / longest
/ average | 2226 / 93 / 23 / 5 / 161 / 17.4 |
| Definitions, count / six or more parameters (models / kernel /
fixtures) | 854 / 45 (0 / 45 / 0) |
| Definitions taking two or more boolean parameters | 7
(cache-rules.qnt:sourceMayWarmLocal,
fixtures/kernel/markers.qnt:initial,
fixtures/kernel/markers.qnt:initialize,
kernel/remote-io.qnt:startLoader, kernel/serving.qnt:authority,
kernel/serving.qnt:judgeReleaseUnder, kernel/shadow.qnt:newJob) |
| Free-text exclusion reasons in execution.json | 531 |

Those are reported, not enforced; the editorial guidance for them stays
in AUTHORING "Writing conventions".

Without arguments the tool enforces the two rules guidance alone did not
hold (AUTHORING already asked for lines without horizontal scrolling,
and the Quint sources broke that 658 times): a Quint line is at most 120
characters and a module header is at most 25 comment lines. The header
is the `//` comment block beside the `module` line, before it or after
it, up to the first import or definition, so the kernel fixtures'
headers written above `module` count too. Enforcement covers every
`.qnt` under `formal/models` not named for that rule in
`formal/catalogs/style-allowlist.json`. The allowlist names the 60 files
that break the line cap today and the two fixtures (`held-remote.qnt`,
`payload-classes.qnt`) whose headers exceed 25 lines; it carries no
counts and may only shrink: an entry whose file has become clean fails
the check, so cleaning a file means deleting its line, and adding one is
a reviewed exception. The check runs in `make audit`.

## Why now

#231 brought every kernel header under the cap; without an enforced rule
that regresses the first time someone pastes a paragraph. The line cap
starts as a ratchet over 22 clean files and tightens as files are
wrapped (Quint 0.32.0 has no formatter, so wrapping stays manual and is
separate work). Review follow-up (second commit): the header is counted
on both sides of the `module` line, banner lines no longer inflate the
sentence metric, boolean parameters are counted anywhere in a signature
with actions included, and the report carries the over 60 and over 100
columns #220 tracked.

## Tests

`formal-style.test.ts`: on a fake tree, an unlisted long line fails
naming `path:line`, an unlisted tall header fails whether written before
or after `module`, a stale entry for either rule and a file outside the
scope fail, a file listed for one rule and offending the other reports
both, header counting stops at the first import or definition,
definitions are scanned to the matching parenthesis, and the report
renders every row; on the committed tree the check passes, exactly two
headers exceed the cap (both allowlisted), and the audit plan contains
the step.

## Verification

`make audit` green with the new step; `vitest run test/formal-style
test/formal-validation` pass; `tsc --noEmit` clean; `node
scripts/check-docs.mjs` green. AUTHORING re-keyed with `--refresh`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
lan17 added 2 commits October 14, 2026 18:59
…k BEHAVIOR.md

Six sections of BEHAVIOR.md (6,600 of its 9,600 words) restated the replay
descriptors in formal/replay/features.mjs, effects.mjs, local-clock.mjs and
profiles/*.mjs: each profile's fixture, setup, actions and the driver
commands their choices decode into. Those facts are now rendered from the
descriptors and the catalogs into formal/generated/PROFILES.md by
formal/tools/generate-catalog-docs.mjs. Per profile: model, version and
input encoding, smoke trace, the ports claiming it, the generation bounds
and counts from execution.json, the required witness count, the fixture
(per init choice where it varies), the setup commands, every action with
its choice range and the decoded driver commands (long ranges show the
first six and last two), and the observation channels the coordinator
compares. A decoder that reads the observation or the environment is
evaluated against probes and the page says which keys it reads.

The view lives under formal/generated/ rather than guides/ because
generated text is not reviewed by hand: the source audit scans every
guide, and a view that changes with its sources would need a ledger
re-key on every regeneration, which is the ripple #220 removes. `--check`
runs in the audit lane; `--write` is documented in AUTHORING.

BEHAVIOR.md keeps what the tables cannot state, condensed into one
"Generated profiles" section: the trace contract, the value codes
(effects.mjs cites them), the meaning of each observation channel, the
rules each profile holds its drivers to, and how to replay a generated
trace. It goes from 9,597 words to 4,920; the composition-profile summary
now follows it and links the view.

Tests: formal-catalog-docs.test.ts pins the committed view to the
renderer, determinism, coverage of every profile and action, the probe
decoding and the declared channels. Ledger: BEHAVIOR, AUTHORING, the two
indexes re-keyed; parity inputs re-pinned.
…condensation lost

Review follow-up on the generated profile view and the shrunk BEHAVIOR.md.

The view renders each profile's setup commands from the same function the
coordinator uses: bindings.mjs now exports fixtureFor and setupFor, so the
effects profile's init-dependent initial policy (empty, a 30 ms read budget
or a null reply) appears per init choice instead of nowhere. The effects
channel lists the fields the parser compares, writeTtls included. A decoder
that reads the observation or the environment is annotated per choice and
per probe kind (a count shown with one so far; the wall clock shown as 0)
instead of one note claiming every choice assumes one. Only a counter
walking up (consecutive decodings differing in one numeric field) is
abbreviated; a payload class or an overlay per choice is shown in full, so
the 26 policy overlays and the recovery-read frame seeds are readable
again. The scope profile's diagnostics say no age event is selected rather
than naming a `none` event. The observation probe has the shared record's
shape and rejects an unknown field; a decoder failure names its profile and
action; the view header names the Quint models among its inputs, since a
model's public runs are its replayed regressions.

BEHAVIOR.md restores every normative statement the condensation dropped:
the effects setup and bounds, the observer-fault isolation, the ms-to-s
conversion and the one-byte scalar, the policy overlay consequences
(choices 10 to 25), the scope trail rules and the AsyncLocalStorage
disclaimer, the nested-context ownership rule, the admission readmission,
600 ms bound, 32-effect bound and smoke description, the independent
bounds and settlement boundary, the layers two-slot rationale and
operation variants, the shadow C1 identity, reencode, advanceWall and
timer rules, the JSON warning obligation, the failing-replay diagnostics,
and what each profile leaves to others. It also corrects two channel
attributions: effects asserts s.o, s.io and s.events only, and
shadow-read-deadlines carries s.io.

Five model comments that cited the removed headings now point at the
view and the new section; the fixtures lock follows. AUTHORING and the
generated README name the models and both regeneration commands.
@lan17
lan17 force-pushed the claude/formal-profiles-doc branch from 807d90f to 5df88f1 Compare October 15, 2026 02:00
@lan17
lan17 merged commit e75a04b into main Oct 15, 2026
21 checks passed
@lan17
lan17 deleted the claude/formal-profiles-doc branch October 15, 2026 02:40
lan17 added a commit that referenced this pull request Oct 15, 2026
…ere.mjs (#235)

Phase 3 of #220, item D1, reshaped after review. One
commit, stacked on #233 until it lands, then retargeted to `main`.

## What changed since the first version

The first version mirrored the challenge and mutant catalogs into 8,190
lines of Markdown. That earned its lines badly: a 345 KB mirror is no
easier to read than the JSON it copies, it costs a regeneration on every
catalog edit, and it is worse for an agent, which parses JSON fine. What
a reader needs is a skimmable list and one entry at a time.

## What this does now

- **`formal/generated/CHALLENGES.md`** (91 lines) and **`MUTANTS.md`**
(76 lines): one row per challenge (contract, source, invariant, native
mutant, reproducer run, detected and excluded counts) and per mutant
(case, description, detections per port, challenges mapped to it).
Generated by the same `--write`, checked by the same `--check` in the
audit lane.
- **`where.mjs` reads an entry in full on demand:** `node
formal/tools/where.mjs recovery-inclusive-maximum` prints the edit, the
invariant, the mapping with its cross-contract note and evidence
checkpoint, the reproducer and every exclusion with its reason; `node
formal/tools/where.mjs M01` prints the rationale, the edits per port
including the Rust mirror, the required detections and the challenges
mapped to it.

Net: +167 generated lines instead of +8,190, no new reading surface, and
the lookup the mirror was really for.

## Tests

`formal-catalog-docs.test.ts` pins all three views to the renderers and
checks that every challenge and every mutant has one row and that the
indexes stay within a few lines of the entry count.
`formal-where.test.ts` covers the challenge and mutant lookups and their
error paths.

## Verification

`make audit` green; `vitest run test/formal-catalog-docs
test/formal-where` pass; `tsc --noEmit` clean; `node
scripts/check-docs.mjs` green (32 pages). Indexes and AUTHORING re-keyed
with `--refresh`; parity inputs re-pinned.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

2 participants