Story 5.4: the engine gets a return type — and still no algebra - #52
Merged
Conversation
`identity/cascade.rs` gains five types and no behaviour: `Verdict` (D13's five,
spelled as architecture.md:964 spells them), `RuleVerdict { rule, verdict,
evidence }`, `RulesetVersion`, `Conclusion` and `Decision`. Nothing combines a
verdict set, no rule produces a `Verdict`, nothing produces a `Decision` — the
combining function is story 5.4b's, inserted at this story's contexting.
`Decision` is a struct and `Conclusion` an enum so that `ruleset_version` rests
on one declaration rather than three variants agreeing (D14: without it,
improving the engine is "a silent data migration — the worst kind"). Measured:
removing the field gives five E0560 plus one E0609.
267 → 271 tests. Four mutations run. M3 is the one worth naming: deleting a
`Verdict` variant gives `error[E0599]` at three sites and NEVER the `E0308` this
story's first draft predicted — the validation agent compiled the case and
refuted the prediction before dev, and the measurement agrees with the
correction. M4 turned up an unpredicted side effect, recorded rather than
smoothed: dropping `evidence` orphans the `ObsId` import, which the CI form of
clippy makes a hard error.
Two of the four reds are compiler-carried, so this does NOT close
deferred-work.md:1045-1053 — owner stays 5.5.
Six doc locations changed, five because this story falsified them, including
`trap.rs`'s `RuleId` doc: it predicted Epic 5 would close the enum, and the
corpus writes seven rule names of which five are `l2-*`, Epic 6's.
Nothing under fixtures/ moved.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three parallel layers (Blind Hunter, Edge Case Hunter, Acceptance Auditor), none failed. The SCOPE held and no behavioural defect was found in the five types: no decide(), no table, no rule, no producer; VerdictVectorEntry still uninhabited, trap.rs:38 byte-unchanged, fixtures/ xtask/ opencmdb-bin/ and Cargo.lock untouched. All four mutation records re-derive against the tree — no under-reporting of a red set, which is what the last three reviews caught. The residue was in the prose the story wrote, and twice it is a lesson the story quotes about itself: - HIGH. "TWO of the four reds are COMPILER-carried ... M1 and M2 are not" is false. M1 (delete ruleset_version) produced SIX compile errors and ZERO executed assertions — five E0560 at construction sites, the exact class the same sentence assigns to M4 — and it contradicted the M2 bullet six lines above, which already called M2 the only one that reds an assertion. THREE of four are compiler-carried; only M2 reds an assertion. Two layers measured it independently. The weaker true sentence shipped. - HIGH. Every line citation into deferred-work.md written by this branch used PRE-commit numbering while the commit's own insertions shifted the file by +21. Three layers converged. That is inherited lesson #1 — "a check that its own commit falsifies is worse than no check" — committed by the commit that quotes it. Fixed by citing entries BY TITLE, which does not rot; post-review positions recorded once in the Completion Notes. - MEDIUM. The register preamble said "fifteen items: thirteen name a story". Counted bullet by bullet: twelve named a story, one named Epic 6, two named a condition — an epic called a story in the sentence forbidding exactly that. - MEDIUM. The #[non_exhaustive] rationale claimed the attribute would destroy the E0004 protecting story 5.4b's table. It would not: #[non_exhaustive] is inert within the defining crate and 5.4b's table lives in this one. The real loss is downstream exhaustiveness in opencmdb-bin. AC1 carries the same error, so the code faithfully copied a false spec. Also: "folded into the existing entry" was never folded (now performed); the test-placement entry Task 6 declared closed was byte-untouched (now closed, naming the convention and both cases it decides); AC5's four-name table lacked the owner column it binds and its heading was untrue of two of its four rows; the Verdict <-> TrapVerdict cross-reference was one-way; "five tests" where four shipped; "evidence in four places" where the citations give five. One patch is more than a doc edit: the vector test's message claimed "the three lists are distinct" while asserting nothing of the kind. The fix ADDS the pairwise-distinctness assertion rather than deleting the clause, because distinctness is what stops that test passing on three identical evidence lists — the vacuous shape Task 6 banned when it refused Uuid::nil() three times. Measured, not predicted: replacing RuleVerdict's derived PartialEq with a total eq() leaves the crate green, so the_verdict_vector_carries_the_whole_triple_in_ order pins a SHAPE and no behaviour. Its doc now says so. Two findings were handed to story 5.4b's contexting rather than answered here (Guy's call), and epics.md was NOT edited — writing 5.4b's criteria from inside 5.4 would be the act 5.4 refused when it declined to write decide(): - this commit introduces the workspace's FIRST f32/f64 token (1 on this branch, 0 on master) — a QUOTATION of D13's own refusal at cascade.rs:42, sitting in exactly the subtree 5.4b's line-grep gate is specified to scan; - three documents name 5.4b owner of the conclusion<->verdict_vector invariant while its six criteria cover neither it nor what decide() returns. Three findings were REFUTED by measurement and dropped: "five sha256-locked trap files" (measured: exactly five of ten name an l2-* rule), the missing #[must_use] (the workspace carries one in total), and M1's "elided" sites (Task 7 asked for the first error and the count, and got them). Register: 15 -> 18 items in the story-5.4 section, fifteen naming a story, one naming an epic, two naming a condition — counted mechanically after the last edit. One item deferred under a new code-review section (the six Decision literals with an empty verdict_vector that 5.4b will have to rewrite). Gate re-run WHOLE after the last edit: fmt clean; clippy --all-targets clean; clippy WITHOUT --all-targets clean (the CI form); 135 + 94 + 42 = 271 tests, zero failures; cargo xtask ci five gates green with views-hash STALE exit 0, NOT regenerated (issue #50); git status fixtures/ empty; cargo doc the same three pre-existing warnings, so no new intra-doc link is broken. The review's own patches falsified a count the story had measured, and it was re-measured rather than carried: cascade.rs is 690 lines, first #[cfg(test)] at :457 -> 456 CODE lines (was 660 / :445 / 444), ceiling 2000. Story stays at `review`. `done` is the MERGE's business in this project — the code-review workflow's default of setting it here is wrong and was not applied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
guycorbaz
added a commit
that referenced
this pull request
Jul 29, 2026
…idated (#53) Bookkeeping only — **no code changes**. Two acts, because the second depends on the first. ## Story 5.4 → `done` PR #52 squash-merged as `da87b62`, CI green on both commits (the story and its 22 code-review patches). `done` is the **merge's** business in this project, so the `code-review` workflow's default of setting it at the end of the review was again not applied. Master re-verified after the merge: **135 + 94 + 42 = 271**, zero failures. ## Story 5.4b → `ready-for-dev` `decide(Vec<RuleVerdict>, RulesetVersion) -> Decision` as a **total** pure function over D13's six rows plus the one input class the table leaves uncovered, and the `cargo xtask ci` gate that refuses a float under `identity/`. **The design decision**, which answers a gap 5.4's review handed forward: `decide` returns a `Decision`, **not** a bare `Conclusion`. The returned `verdict_vector` *is* the input, so *"a `Match` with an empty vector"* and *"a conclusion naming a rule absent from its own vector"* become **unrepresentable through the function** rather than merely unenforced — which is what makes the invariant testable at all. ## What validation actually caught Two fresh-context agents (fact-check + gap-hunt): **9 HIGH + 12 MEDIUM/LOW applied**. Four changed the *design*, and every one was **measured, not argued**: - **M2 would have been a silent no-op.** The gap-hunt agent *compiled* all three arm shapes the story permitted and found that with an `if`-chain, deleting the arbitration arm *"compiles and changes 0 of 16 input classes"*. The arms are now bound to a `match` on the presence tuple — the only shape that turns the deletion into `error[E0004]`. - **The permutation test was vacuous.** Three mixed verdicts conclude `Abstained`, which names no rule, so no tiebreak can change it — and the 32-subset walk gives that AC zero coverage, since one `RuleVerdict` per verdict makes `min()` a singleton. - **A wrong implementation passed every specified test.** `min()` over the whole vector is deterministic and order-independent, yet returns `NoMatch { rule }` naming the rule that argued *for* the merge. - **The float gate was built and run.** The specified word-boundary match misses `0.85f64` and `let confidence = 0.85;` — the likeliest shapes a weight takes — and reds on a block comment, a **false positive**, the one direction the draft said could not happen. Bookkeeping corrected before dev rather than after: the register set is **eight** entries across **four** sections, not six in one; **eleven** doc blocks are falsified, not five; and the story cited `cascade.rs:42` for the `f64` token when it is `:52` — inherited from the register without re-measuring, which is this project's own lesson #1 committed by the story that quotes it. ## One deviation, recorded rather than smoothed 5.4b is the **first** Epic 5 story contexted while its predecessor's PR was still open; the four before it were each merged first. Every `cascade.rs` line number in it was measured on the post-review tree, and the deviation is stated at the top of the story. PR #52 has since merged, so the prerequisite it states is now satisfied. Docs current before push: `sprint-status.yaml`, `docs/project-context.md` and `CLAUDE.md` all name 5.4 `done` and 5.4b `ready-for-dev`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What lands
crates/opencmdb-core/src/identity/cascade.rsgains five types and no behaviour:Verdictarchitecture.md:964spells themRuleVerdict { rule, verdict, evidence: Vec<ObsId> }(rule, verdict, evidence)triple that is the explanationRulesetVersion(u32)Conclusion { Match | NoMatch | Abstained }Decision { conclusion, verdict_vector, ruleset_version }Plus
Verdict::all()andDecision::rule(). Nothing combines a verdict set, no rule produces aVerdict, nothing produces aDecision— the combining function is story 5.4b's, inserted intoepics.mdat this story's contexting.Decisionis a struct andConclusionan enum soruleset_versionrests on one declaration rather than three variants agreeing. Measured: removing the field gives fiveE0560plus oneE0609.Measurements
--all-targets.cargo doc→ the same three pre-existing warnings; none of the new full-path intra-doc links is broken.cargo xtask ci: five gates green,ℹ views-hash STALEexit 0 (expected — issue architecture-views.md is STALE: regenerate at the next milestone (drift measured: 2 commits, 59 insertions) #50, not regenerated).fixtures/moved;MANIFEST.tomluntouched.The four mutations
ruleset_version→ 6 errors (5×E0560, 1×E0609at the assertion that reads it).rule()returnsNoneforNoMatch→ 2 assertion panics, both new. The only one of the four that reds an assertion rather than a name.Verdict::Disqualifying→error[E0599]at three sites and NEVERE0308— this story's first draft predictedE0308; the validation agent compiled the case and refuted it before dev, and the measurement agrees with the correction.evidence→ 4 errors, plus an unpredicted side effect recorded rather than smoothed: it orphans theObsIdimport, which the CI form of clippy makes a hard error.deferred-work.md:1045-1053— owner stays 5.5.the_verdict_vector_carries_the_whole_triple_in_orderhas no possible behavioural mutation: apubfield has no code to break.Docs and register
Six doc locations changed, five because this story falsified them — including
trap.rs'sRuleIddoc, which predicted Epic 5 would close the enum: the corpus writes seven rule names and five arel2-*, Epic 6's.deferred-work.md:1073-1076is closed (this story was its named owner — its AC8 ends at the PR, and the merge is a separate act). Three entries annotated↺, none struck. New## Deferred from: story-5.4section: fifteen items — thirteen name a story, two name the condition that would produce an owner, and the preamble says so.Scope held
No
decide(), no table, noxtaskgate (5.4b) · no rule, no join, no producer (5.5) · noFrom<Decision> for Outcome,VerdictVectorEntrystill uninhabited (5.7) · no serde (5.9) · noDisplay, nocause()(5.14) ·RuleIdnot closed into an enum (Epic 6).🤖 Generated with Claude Code