Skip to content

fix(core): a corpus file that becomes no rules now reaches the report - #635

Merged
beyondnetPeru merged 2 commits into
developfrom
fix/gt-575-corpus-load-accounting
Aug 21, 2026
Merged

fix(core): a corpus file that becomes no rules now reaches the report#635
beyondnetPeru merged 2 commits into
developfrom
fix/gt-575-corpus-load-accounting

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Closes #575.

The defect, and what turned out not to be one

The loader globs *.rules.json and four shipped files are not rule sets. They were read, produced nothing, and the only trace was a log line — which does not survive --format json, never reaches an exit code, and left the documents out of every denominator the report published. That is the silent drop this project exists to stop, happening inside its own loader.

#575 asked for the accounting first and the files second, and it was right — the files turned out not to need changing:

  • GT-649 had already classified three of them by declared $schema.
  • The two INFRA rules are genuinely enforced elsewhere: src/rulesets/opa/infrastructure/helm-enforcement.rego, opa-sidecar-bundle.rego, plus the dedicated 29-validate-opa-sidecar-bundles.mjs guard. Wrapping them into the corpus, as the issue suggested, would have double-counted them.

The issue counted three rejected files; there are four. sdlc/phase-gates.rules.json was recognised by filenamefilePath.endsWith("phase-gates.rules.json") skipped schema validation and then normalised to zero rules, so it was neither validated nor reported, and no warning ever named it.

The fix

  • IRulesetRepository gains CorpusDocumentOutcome and an optional describeLastLoad(). Optional so an implementation that cannot know stays valid; an implementation that drops documents and stays quiet reintroduces this bug.
  • The validator emits two rows, deliberately weighted differently:
    • GOV-CORPUS-NOT-A-RULESETCOULD, non-blocking. Names each document that declares a known non-ruleset schema and satisfies it. A fact about the corpus, not a violation.
    • GOV-CORPUS-REJECTEDMUST, blocking. Names each document that claims to be a ruleset and is not, because such a file is indistinguishable downstream from one that was never there.
  • phase-gates now dispatches on its declared ruleset-sdlc.schema.json like its neighbours. The path literal is gone and a rename can no longer defeat it. PhaseGateValidatorService reads the file at its own path and is unaffected.

Verified against the built CLI, not asserted

The issue's own proof commands, run on a fresh satellite:

  • the row appears in the report and in --format json, naming all four files with the reason for each
  • the blocking branch is falsifiable: dropping one deliberately broken *.rules.json into the corpus produced GOV-CORPUS-REJECTED … | YES and exit 2; removing it returned the run to the previous state
  • no rules were lost by the phase-gates change: evolith rulesets reports 178 packs / 413 rules before and after

Tests: infra-providers 181, core-domain 1997 (5 new in corpus-load-accounting.spec.ts), CLI 133, core-api 163 — all passing.

Second commit

The front page said the tree "carries 182 ruleset files" and stopped, because a tree-side rule count could not be measured — the published CLI evaluates its bundled corpus regardless of coreRef.path. Building the CLI from this tree answers it (178 packs, 413 rules), and this change explains the 182→178 gap without hand-waving. Both READMEs updated together.

🤖 Generated with Claude Code

beyondnetPeru and others added 2 commits August 21, 2026 14:24
Closes #575.

The loader globs `*.rules.json` and four shipped files are not rule SETS. They
were read, produced nothing, and the only trace was a log line. A log line does
not survive `--format json`, never reaches an exit code, and left the documents
out of every denominator the report published -- the silent drop this project
exists to stop, inside its own loader.

#575 asked for the accounting FIRST and the files second, and it was right: the
files turned out not to need changing. GT-649 had already classified three of
them by declared `$schema`, and the two INFRA rules are genuinely enforced
elsewhere -- `src/rulesets/opa/infrastructure/*.rego` plus the dedicated
`29-validate-opa-sidecar-bundles.mjs` guard. Wrapping them into the corpus would
have double-counted them. What was missing was the accounting, and only that.

- `IRulesetRepository` gains `CorpusDocumentOutcome` and an optional
  `describeLastLoad()`. Optional so an implementation that cannot know stays
  valid; an implementation that drops documents and stays quiet reintroduces this
  bug.
- The validator emits two rows, deliberately weighted differently.
  `GOV-CORPUS-NOT-A-RULESET` (COULD, non-blocking) names each document that
  declares a known non-ruleset schema and satisfies it -- a fact about the
  corpus, not a violation. `GOV-CORPUS-REJECTED` (MUST, blocking) names each
  document that claims to be a ruleset and is not, because such a file is
  indistinguishable downstream from one that was never there.
- The fourth file, `sdlc/phase-gates.rules.json`, was recognised by FILENAME --
  `filePath.endsWith("phase-gates.rules.json")` skipped schema validation and
  then normalised to zero rules, so it was neither validated nor reported. It
  declares `ruleset-sdlc.schema.json` like its neighbours declare theirs, so it
  now dispatches on that. The path literal is gone, and a rename can no longer
  defeat it. `PhaseGateValidatorService` reads the file at its own path and is
  unaffected.

Verified against the built CLI on a fresh satellite, not asserted:

- the row appears in the report AND in `--format json`, naming all four files
  with the reason for each
- the blocking branch is falsifiable: dropping one deliberately broken
  `*.rules.json` into the corpus produced `GOV-CORPUS-REJECTED ... | YES` and
  exit 2; removing it returned the run to the previous state
- no rules were lost by the phase-gates change: `evolith rulesets` reports
  178 packs / 413 rules before and after

Tests: infra-providers 181, core-domain 1997 (5 new in
`corpus-load-accounting.spec.ts`), CLI 133, core-api 163 -- all passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: aarroyo <beyondnet.peru@gmail.com>
The front page said the tree "carries 182 ruleset files" and stopped there,
because a tree-side RULE count could not be obtained: the published CLI evaluates
its bundled corpus regardless of `coreRef.path`, so pointing it at this tree
returns the tarball's numbers, not the tree's.

Building the CLI from this tree answers it — 178 packs, 413 rules — and #575's
accounting explains the gap between 182 files and 178 packs without hand-waving:
four files declare a non-ruleset schema and contribute no rules by design, and
they are now named in every report rather than dropped.

Both halves say the same three numbers, and the previous sentence blaming "the
loader rejection above" goes with them — nothing is being rejected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: aarroyo <beyondnet.peru@gmail.com>
@beyondnetPeru
beyondnetPeru requested a review from a team as a code owner August 21, 2026 19:25
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

Copy link
Copy Markdown

📊 Bilingual Coverage Impact

PR Changes

  • Paired EN/ES files modified: 12
  • New EN files needing ES translation: 0

Repository Coverage

Metric Value
Total EN files 525
Total ES files 495
Paired files 0
Coverage 0%

Good: All EN changes have ES counterparts.


Generated by GitHub Actions

@beyondnetPeru
beyondnetPeru merged commit a20bb90 into develop Aug 21, 2026
49 checks passed
@beyondnetPeru
beyondnetPeru deleted the fix/gt-575-corpus-load-accounting branch August 21, 2026 19:37
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