Skip to content

explain: gate the value census, don't just make it re-derivable (#260) - #267

Merged
mobileskyfi merged 3 commits into
mainfrom
agent/explain-value-census-drift-gate
Aug 11, 2026
Merged

explain: gate the value census, don't just make it re-derivable (#260)#267
mobileskyfi merged 3 commits into
mainfrom
agent/explain-value-census-drift-gate

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Closes #260.

The gap

The value-census figures live in three places: the census script, the
corpus block of test/fixtures/explain/values.json, and prose in
commands/explain/README.md. Committing the census script (#242) made them
re-derivable; it did not make them checked. #256 changed the value
emission, regenerated the fixture and the unit assertions, and shipped with
the README still quoting the pre-change numbers — three review comments found
that, not CI.

Two gates, split by what data they need

The issue's option (3) was "the one to do first" because it needs no corpus.
But ci.yaml:164 already reserved the other half — "Asserting them against
the committed fixture is #260's job, and it needs the reachability this job
provides"
— so both legs ship here.

gate asserts needs corpus runs in
bun run explain:value-census:check fresh census == fixture corpus block yes ci.yaml corpus job
bun run explain:value-census:readme:check README block == fixture corpus block no lint:ci + bun test

The fixture is the hinge: it is the one copy a human writes, and both gates
point at it rather than at each other. The README paragraph is now a
generated projection (bun run explain:value-census:readme) between
<!-- BEGIN/END GENERATED value-census --> markers.

The corpus gate runs after the summary step, so a drifted run still
publishes its figures — those are what say whether the drift is an intended
emission change or a regression.

The generated prose states regressions

Every sentence stays true at any value. The invariants are reported as
counters, not asserted as held:

The three structural counters — spans addressing bytes outside their own
source, members naming a container that does not exist, members escaping the
container they name — read 0, 0 and 0, and each must stay 0.

So regenerating after a real regression states it rather than publishing a
reassuring lie. test/unit/explain-value-census.test.ts proves that by
rendering a synthetic regressed census and asserting the numbers surface.

Verification

  • explain:value-census --json against the real 948-script corpus still
    reproduces the committed fixture byte for byte — no figure moved.
  • Both gates were confirmed to fail on an injected one-figure drift
    (948 -> 999 in the README; keyedElements/shapeCounts.array in the
    fixture) before being wired in, and to pass again on restore.
  • bun run lint:ci && bun run test && bun run build green (2595 pass, 0 fail).

Not done here

explain:corpus-census still only reports — it has no committed fixture to be
asserted against, so there is nothing to gate yet.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Updated corpus validation guidance to distinguish gating checks from informational checks.
    • Expanded the value-census documentation with regeneration commands and structural integrity counters.
    • Added changelog details for census drift validation and generated README content.
  • Quality Improvements

    • Added automated checks to detect differences between census results, committed fixtures, and README content.
    • Improved reporting for census mismatches and nonzero validation metrics.
    • Added regression coverage for generated formatting and census comparisons.

The census figures live in three places — the census itself, the `corpus`
block of `test/fixtures/explain/values.json`, and prose in
`commands/explain/README.md`. Committing the census script (#242) made them
re-derivable; nothing checked that they still matched. #256 changed the value
emission, regenerated the fixture and the unit assertions, and shipped with
the README prose still quoting the pre-change numbers — three review comments
found that, not CI.

Each link in corpus -> fixture -> README now has its own gate, separated by
what data it needs:

- `explain:value-census:check` re-runs the census and asserts it figure by
  figure against the committed fixture. It needs `corpus.sqlite`, so it runs
  in ci.yaml's `corpus` job, which #186 gave `corpus:fetch` reachability for
  precisely so this could exist. It runs after the summary step, so a drifted
  run still publishes its figures — those are what say whether the drift is an
  intended emission change or a regression.
- `explain:value-census:readme:check` asserts the README paragraph against
  that same fixture block. The paragraph is now a generated projection of it
  (`explain:value-census:readme`), so the drift that actually happened is
  caught with no corpus at all: in `lint:ci`, and in `bun test`.

The fixture is the hinge — the one copy a human writes — and both gates point
at it rather than at each other.

The generated prose reports the invariants as counters rather than asserting
they hold ("read 0, 0 and 0, and each must stay 0"), so every sentence stays
true at any value and regenerating after a regression states it instead of
publishing a reassuring lie. A unit test proves that by rendering a synthetic
regressed census.

Verified end to end against the real corpus: `explain:value-census --json`
still reproduces the committed fixture byte for byte, and both gates were
confirmed to FAIL on an injected one-figure drift before being wired in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 11, 2026 04:55
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 713d6452-eb82-4507-bb05-f8928b8e893f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The value-census script now validates corpus output against a committed fixture and synchronizes generated README content. Package scripts, CI, documentation, and offline unit tests enforce both drift checks.

Changes

Value census drift gates

Layer / File(s) Summary
Census comparison and README generation
scripts/explain-value-census.ts
The script compares fresh census results with the committed fixture. It renders, rewrites, and checks the generated README block without requiring a corpus database.
CI commands and census documentation
package.json, .github/workflows/ci.yaml, commands/explain/README.md, CHANGELOG.md
Package scripts and CI run fixture and README checks. Documentation describes the validation commands, fixture source, and structural counters.
Offline drift tests
test/unit/explain-value-census.test.ts
Tests validate generated README content, census wording, and scalar and nested fixture differences.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Corpus as corpus.sqlite
  participant Census as explain-value-census.ts
  participant Fixture as values.json
  participant README as README.md
  participant CI as CI workflow
  Corpus->>Census: Generate fresh value census
  Census->>Fixture: Compare census values
  Census->>README: Render or check generated block
  CI->>Census: Run fixture validation
  CI->>Census: Run README validation
Loading

Possibly related PRs

  • tikoci/centrs#256: Introduced the value-census tooling that this change extends with fixture and README drift checks.
  • tikoci/centrs#265: Added the corpus CI and census workflow extended here.
  • tikoci/centrs#222: Modified related explain structural-integrity metrics in separate code areas.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding drift gates for the explain value census.
Description check ✅ Passed The description explains the problem, implemented gates, generated README flow, validation, and scope, although it omits the repository template sections.
Linked Issues check ✅ Passed The changes satisfy issue #260 by gating README figures against the fixture and adding the available corpus-to-fixture check.
Out of Scope Changes check ✅ Passed The workflow, scripts, README, changelog, package scripts, and tests support the linked issue objectives without unrelated changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/explain-value-census-drift-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/unit/explain-value-census.test.ts`:
- Around line 61-63: Update the outside-content extraction in the explain-value
census test to locate the generated block using its BEGIN and END marker
indices, then concatenate only the lines before BEGIN and after END. Replace the
content-based filter around README and block so duplicated lines matching
generated content cannot be incorrectly removed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b84bab06-39f9-4ec4-9cc2-6ce6a32391e7

📥 Commits

Reviewing files that changed from the base of the PR and between 96d9785 and 4a9c93f.

📒 Files selected for processing (6)
  • .github/workflows/ci.yaml
  • CHANGELOG.md
  • commands/explain/README.md
  • package.json
  • scripts/explain-value-census.ts
  • test/unit/explain-value-census.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (7)
  • GitHub Check: CHR smoke (stable)
  • GitHub Check: Corpus census
  • GitHub Check: Build
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Transport or RouterOS-touching code is not done until bun run test:integration passes.
Do not silently fall back to another protocol when the caller pinned --via.
Do not make generated output the hand-edited source of truth.
Do not disable validation to make a test pass; validation is part of the product.

Files:

  • test/unit/explain-value-census.test.ts
  • scripts/explain-value-census.ts
{docs/**,.github/instructions/**,.github/**/*.yml,.github/**/*.yaml,**/*.{md,txt,dict}}

📄 CodeRabbit inference engine (AGENTS.md)

Run bun run lint:ci when changing documentation, instructions, security configuration, spelling dictionaries, or workflow files.

Files:

  • CHANGELOG.md
  • .github/workflows/ci.yaml
  • commands/explain/README.md
commands/*/README.md

📄 CodeRabbit inference engine (commands/AGENTS.md)

commands/*/README.md: Each commands/<name>/README.md is the executable specification's designed tier: document intent, flags, and behavior; include a Designed, not implemented table for spec-only flags; do not duplicate implemented-flag tables generated in docs/CLI.md; link to docs/CONSTITUTION.md rather than restating constitution-wide rules; document only command-specific behavior; and keep the Status line consistent with docs/MATRIX.md.
Implemented flags must be generated from CliCommandMetadata into docs/CLI.md via bun run docs:cli; command READMEs must not maintain duplicate tables of implemented flags.

Files:

  • commands/explain/README.md
commands/*/{README.md,examples.md}

📄 CodeRabbit inference engine (AGENTS.md)

Read the target command's README.md and examples.md as the executable specification before writing code or tests.

Files:

  • commands/explain/README.md
🧠 Learnings (1)
📚 Learning: 2026-08-11T03:49:52.355Z
Learnt from: mobileskyfi
Repo: tikoci/centrs PR: 265
File: .github/workflows/ci.yaml:171-172
Timestamp: 2026-08-11T03:49:52.355Z
Learning: In this repository's GitHub Actions workflows, SHA-pin `oven-sh/setup-bun` and `actions/setup-node` references and include version comments. Other first-party `actions/*` references may continue using floating major-version tags according to the repository's current convention. Do not request SHA pinning for those first-party actions unless a repository-wide maintainer policy and corresponding Dependabot update policy have been established.

Applied to files:

  • .github/workflows/ci.yaml
🔇 Additional comments (3)
scripts/explain-value-census.ts (1)

407-451: 🎯 Functional Correctness

Provide the RouterOS integration result.

This TypeScript census reads RouterOS corpus data. Run bun run test:integration and attach a passing result before merge.

As per coding guidelines, “Transport or RouterOS-touching code is not done until bun run test:integration passes.”

Source: Coding guidelines

package.json (1)

67-67: LGTM!

Also applies to: 101-103

commands/explain/README.md (1)

782-801: 📐 Maintainability & Code Quality

Run documentation and workflow validation.

Run bun run lint:ci and attach the passing result before merge.

  • commands/explain/README.md#L782-L801: validate generated README content.
  • CHANGELOG.md#L30-L41: validate changelog documentation.
  • .github/workflows/ci.yaml#L161-L169: validate workflow changes.
  • .github/workflows/ci.yaml#L217-L221: validate workflow changes.

As per coding guidelines, run bun run lint:ci when changing documentation or workflow files.

Source: Coding guidelines

Comment thread test/unit/explain-value-census.test.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes #260 by turning the explain value-census numbers into drift-gated artifacts, using the committed test/fixtures/explain/values.json corpus block as the single source of truth and adding two checks: (1) fresh corpus census vs fixture (in CI’s corpus job) and (2) README generated block vs fixture (in lint:ci and unit tests).

Changes:

  • Add explain:value-census:check (fixture-vs-fresh-census) and explain:value-census:readme(:check) (README-vs-fixture) flows in scripts/explain-value-census.ts.
  • Add a unit test suite to validate README rendering invariants and the fixture-diff comparator behavior.
  • Wire the new gates into lint:ci and the corpus CI job; update commands/explain/README.md to use a generated value-census block.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/unit/explain-value-census.test.ts Adds unit tests to enforce README block/fixture consistency and validate drift detection.
scripts/explain-value-census.ts Adds README block render/rewrite/check functionality and a fixture drift comparator plus --check behavior.
package.json Wires README drift check into lint:ci and adds new scripts for value-census gates.
commands/explain/README.md Replaces hand-written census prose with a generated block bounded by markers.
CHANGELOG.md Documents the new drift-gating behavior and the two-gate model.
.github/workflows/ci.yaml Adds a corpus-job step to gate fresh value census output against the committed fixture.
Suppressed comments (2)

test/unit/explain-value-census.test.ts:63

  • The outside calculation filters out any line whose text appears anywhere inside the generated block (block.includes(line)), which can hide duplicated census figures if the same line appears both inside and outside the block. Compute “outside” by slicing the README around the marker indices instead.
		const block = readmeBlock().join("\n");
		const outside = README.split("\n")
			.filter((line) => !block.includes(line))
			.join("\n");

scripts/explain-value-census.ts:376

  • runReadme() splits the README with split("\n") and then looks for exact marker lines via indexOf. If the README has CRLF line endings, lines will include trailing "\r" and the markers won’t match. Splitting on /\r?\n/ makes this gate robust on Windows checkouts.
	const lines = readme.split("\n");

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/unit/explain-value-census.test.ts Outdated
Comment thread scripts/explain-value-census.ts Outdated
Comment thread scripts/explain-value-census.ts
…fixes

CodeRabbit's finding on the "no census figure outside the block" test was
right, and worse than reported. Filtering out README lines whose text appears
in the generated block used a SUBSTRING test, so it dropped every blank line
(the empty string is "in" any block) plus any short line duplicating generated
text: measured, 112 of 1343 lines vanished from the region the test claimed to
scan. Now sliced by marker index, like the gate itself does. The test still
passes over the full region, so it was not masking real drift.

Copilot's CRLF finding is real here, not hypothetical: the repo ships no
.gitattributes, so a Windows clone with the Git-for-Windows default
core.autocrlf=true gets CRLF, every line keeps a trailing \r, and neither
marker is found — the gate would fail for a line ending rather than for drift,
in the unit tier #142 wants running on Windows. Both the gate and the test now
split on /\r?\n/. The write path additionally rejoins with the ending the file
already used: splitLines discards the \r, so writing back a bare "\n" would
have silently converted a CRLF checkout to LF and put the whole README in the
diff. Verified by CRLF-ifying the real README — check passes, and a forced
rewrite preserves all 1342 CRLFs with zero bare LFs introduced.

The remaining two were accurate reads of imprecise prose, not bugs:

- wrap()'s "counts as one character" described the TOKENIZATION regex, where a
  backtick span is one atom in the alternation, but read as a claim about
  length accounting. Reworded to say which it is, and that a span longer than
  the wrap column overflows rather than splitting. No behavior change.
- diffAgainstFixture excluded `censusCommand` only in its second loop. It
  cannot fire today, since a fresh ValueCensus never carries that key, but the
  contract was true by accident rather than by rule. Now a PROVENANCE_KEYS set
  skipped in both directions.

Adds a CRLF regression test that asserts the fix is load-bearing (a bare "\n"
split finds neither marker).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#260)

Codex's exact-head review of 7612f1e found diffAgainstFixture treating JSON
object insertion order as census data. Reproduced: reversing only the KEYS of
`shapeCounts`, with every key/value pair preserved, reported drift.

It is a live hazard, not a theoretical one. `shapeCounts` is built by `bump()`
in first-seen order across the corpus walk, so a corpus whose scripts are
visited in a different order — or a hand-tidied fixture — reorders the keys
without moving a single count. And the failure mode was the worst kind: the
message dumped both tallies, so a reader would have compared two 200-character
blobs containing identical data and seen no difference.

Tallies are now compared entry by entry over the union of their keys, which
fixes the false positive and sharpens the diagnostic at the same time:

  before: shapeCounts: fixture {"time":128,"str":14845,…}, measured {…}
  after:  shapeCounts.array: fixture 813, measured 814

Sensitivity is unchanged in the direction that matters — added, removed and
changed entries all still report, each naming the shape that moved, and an
absent side prints "absent" rather than a misleading 0.

Regression test covers both halves as asked: reordered-equal maps produce no
drift, while add/remove/change still do. Verified end to end that the CI gate
still fails on a real injected `shapeCounts.array` change, now with the precise
message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mobileskyfi

Copy link
Copy Markdown
Contributor Author

Codex Rereview (original review in #260)

Yes—PR #267 is good to merge at exact head 1e70c7d.

No remaining findings from my rereview.

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.

explain: the value census is re-derivable but not drift-gated

2 participants