Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project status

`opencmdb` is a self-hosted, single-binary **Rust** network reconciliation engine (IPAM + light app CMDB + topology) for home-lab/SMB. Core thesis: continuously compare the **observed** state (auto-discovered) against the **declared** state (documented); the gap is the product. **Planning is COMPLETE** (product brief, PRD, UX spec, architecture — all in `_bmad-output/planning-artifacts/`, decision register D1–D66). **As of 2026-07-22 the code RUNS**: `v0.1.1` is tagged and published to Docker Hub as `gcorbaz/opencmdb`, and it scans a CIDR and shows a real observed-vs-declared gap on one page. **Epics 1–4 of 23 are done** (the walking skeleton shipped as v0.1). **Epic 4 closed on 2026-07-25**: the fixture corpus, the metrics harness and the trap corpus — all written BEFORE the identity engine — are committed and locked (25 artefacts, 24 traps across nine families, plus the wire-format spec). Its story 4.19 was SPLIT at closure: 4.19a shipped, **4.19b (the mutation generator) moved to Epic 11** — recorded in `epic-4-correct-course-2026-07-25.md` and GitHub issue #34, not silently. **Epic 5 — the identity engine — is IN PROGRESS since 2026-07-27** (**16 stories**: 5.4b was INSERTED 2026-07-29 at story 5.4's contexting, splitting the verdict algebra — a function that must be TOTAL over a table D13 leaves one input class short — and its anti-float `xtask` gate out of 5.4). Its **three inherited debt stories come first and are all `done`** — 5.1, 5.2 and 5.2b, PRs #41, #44 and #46, all merged 2026-07-28. The engine proper starts at **5.3, `done`** — PR #48, merged 2026-07-29 — which ships the identity engine's own abstention vocabulary (`IdentityAbstentionCause`, in the new `crates/opencmdb-core/src/identity/`) and **no engine**: no cascade, no rule, no `Decision`, no join. **5.4 follows and is `done`** — PR #52, squash-merged 2026-07-29 as `da87b62`: it adds the engine's RETURN type in the same file — `Verdict` (D13's five verdicts), `RuleVerdict { rule, verdict, evidence }`, `RulesetVersion`, `Conclusion` and `Decision` — and still **no algebra**: nothing combines a verdict set, no rule produces a verdict, nothing produces a decision. **5.4b follows and is `done`** — PR #55 squash-merged 2026-07-30 as `4f4e774` after a green CI run, code-reviewed first (281 tests): it ships the ALGEBRA — `decide(Vec<RuleVerdict>, RulesetVersion) -> Decision`, a TOTAL pure function over D13's six rows plus the one input class the table leaves uncovered (`≥1 Opposes` alone → `Abstained { AbsenceOfProof }`, Guy's arbitration; the D13 correction itself is **GitHub issue #54**, a milestone act) — and a **sixth `cargo xtask ci` gate, `float-free`**, which reds on a float type or a float literal in code under `identity/`, comments stripped so the architecture may be quoted. The review applied 22 patches and registered 5 deferrals: `decide` now matches on `Option<RuleId>` rather than booleans, so the presence test and the rule selection are one act and no unreachable arm has to be justified; and the gate's matcher is a **numeric-literal tokeniser** rather than a digit-dot-digit search, because the search form was measured to pass `1e-3` and `1.` (both `f64`) while reddening `"192.168.0.1"`, `t.0.1` and any identifier containing `f64` — a shape story 5.5 would have tripped on its first MAC/IP rule — although in the event **5.5 wrote no IP literal at all**, and that mis-prediction was itself corrected as a finding. **5.5 follows and is `done`** — PR #57 squash-merged 2026-07-31 as `0ebd50f` after a green CI run, code-reviewed first (three layers): it ships `crates/opencmdb-core/src/identity/l1.rs` — the L1 join `join(&[Observation]) -> BTreeMap<(L2DomainId, MacAddr), BTreeSet<ObsId>>` (the value is a `BTreeSet` so order-independence holds by CONSTRUCTION, not by a `sort()` a refactor can drop), the two rules the committed corpus names (`l1-exact-mac -> Decisive` on **at least one shared key**, `l1-distinct-mac -> Disqualifying` when they share none, `Neutral` when either side carries no MAC), and `decide_pair` — **the first caller of `decide` outside its own tests** — with `CURRENT_RULESET_VERSION = RulesetVersion(1)` and still **no `Default`**. **281 → 309 tests.** Its AC3 was discharged as a NEGATIVE requirement: D13 calls the U/L bit and the IANA prefixes `Disqualifying` *"as GROUPING anchors"* and grouping is L2, so **no IANA predicate was added** and two tests assert `l1-exact-mac` still fires on a locally-administered MAC and on a VRRP MAC — implementing D13's label at L1 would have reddened two committed traps. Prove-to-red: six mutations, **every red assertion-carried, none compiler-carried**. **Still no blocker** (5.6), nothing feeds the corpus harness (5.7), and `Verdict::Supports`/`Opposes` still have **no producer** (Epic 6). **Next is 5.6, the blocker — whose `blocking_recall >= 0.999` is a bare float literal that reds the `float-free` gate, measured and flagged forward.** _(This sentence named only 5.1 until 2026-07-29 — stale for a day, found by story 5.3's code review, not by the two stories that caused the drift.)_ Live status is `_bmad-output/implementation-artifacts/sprint-status.yaml`; grounding is `docs/project-context.md`.
`opencmdb` is a self-hosted, single-binary **Rust** network reconciliation engine (IPAM + light app CMDB + topology) for home-lab/SMB. Core thesis: continuously compare the **observed** state (auto-discovered) against the **declared** state (documented); the gap is the product. **Planning is COMPLETE** (product brief, PRD, UX spec, architecture — all in `_bmad-output/planning-artifacts/`, decision register D1–D66). **As of 2026-07-22 the code RUNS**: `v0.1.1` is tagged and published to Docker Hub as `gcorbaz/opencmdb`, and it scans a CIDR and shows a real observed-vs-declared gap on one page. **Epics 1–4 of 23 are done** (the walking skeleton shipped as v0.1). **Epic 4 closed on 2026-07-25**: the fixture corpus, the metrics harness and the trap corpus — all written BEFORE the identity engine — are committed and locked (25 artefacts, 24 traps across nine families, plus the wire-format spec). Its story 4.19 was SPLIT at closure: 4.19a shipped, **4.19b (the mutation generator) moved to Epic 11** — recorded in `epic-4-correct-course-2026-07-25.md` and GitHub issue #34, not silently. **Epic 5 — the identity engine — is IN PROGRESS since 2026-07-27** (**16 stories**, 7 done and 5.6 in `review`: 5.4b was INSERTED 2026-07-29 at story 5.4's contexting, splitting the verdict algebra — a function that must be TOTAL over a table D13 leaves one input class short — and its anti-float `xtask` gate out of 5.4). Its **three inherited debt stories come first and are all `done`** — 5.1, 5.2 and 5.2b, PRs #41, #44 and #46, all merged 2026-07-28. The engine proper starts at **5.3, `done`** — PR #48, merged 2026-07-29 — which ships the identity engine's own abstention vocabulary (`IdentityAbstentionCause`, in the new `crates/opencmdb-core/src/identity/`) and **no engine**: no cascade, no rule, no `Decision`, no join. **5.4 follows and is `done`** — PR #52, squash-merged 2026-07-29 as `da87b62`: it adds the engine's RETURN type in the same file — `Verdict` (D13's five verdicts), `RuleVerdict { rule, verdict, evidence }`, `RulesetVersion`, `Conclusion` and `Decision` — and still **no algebra**: nothing combines a verdict set, no rule produces a verdict, nothing produces a decision. **5.4b follows and is `done`** — PR #55 squash-merged 2026-07-30 as `4f4e774` after a green CI run, code-reviewed first (281 tests): it ships the ALGEBRA — `decide(Vec<RuleVerdict>, RulesetVersion) -> Decision`, a TOTAL pure function over D13's six rows plus the one input class the table leaves uncovered (`≥1 Opposes` alone → `Abstained { AbsenceOfProof }`, Guy's arbitration; the D13 correction itself is **GitHub issue #54**, a milestone act) — and a **sixth `cargo xtask ci` gate, `float-free`**, which reds on a float type or a float literal in code under `identity/`, comments stripped so the architecture may be quoted. The review applied 22 patches and registered 5 deferrals: `decide` now matches on `Option<RuleId>` rather than booleans, so the presence test and the rule selection are one act and no unreachable arm has to be justified; and the gate's matcher is a **numeric-literal tokeniser** rather than a digit-dot-digit search, because the search form was measured to pass `1e-3` and `1.` (both `f64`) while reddening `"192.168.0.1"`, `t.0.1` and any identifier containing `f64` — a shape story 5.5 would have tripped on its first MAC/IP rule — although in the event **5.5 wrote no IP literal at all**, and that mis-prediction was itself corrected as a finding. **5.5 follows and is `done`** — PR #57 squash-merged 2026-07-31 as `0ebd50f` after a green CI run, code-reviewed first (three layers): it ships `crates/opencmdb-core/src/identity/l1.rs` — the L1 join `join(&[Observation]) -> BTreeMap<(L2DomainId, MacAddr), BTreeSet<ObsId>>` (the value is a `BTreeSet` so order-independence holds by CONSTRUCTION, not by a `sort()` a refactor can drop), the two rules the committed corpus names (`l1-exact-mac -> Decisive` on **at least one shared key**, `l1-distinct-mac -> Disqualifying` when they share none, `Neutral` when either side carries no MAC), and `decide_pair` — **the first caller of `decide` outside its own tests** — with `CURRENT_RULESET_VERSION = RulesetVersion(1)` and still **no `Default`**. **281 → 309 tests.** Its AC3 was discharged as a NEGATIVE requirement: D13 calls the U/L bit and the IANA prefixes `Disqualifying` *"as GROUPING anchors"* and grouping is L2, so **no IANA predicate was added** and two tests assert `l1-exact-mac` still fires on a locally-administered MAC and on a VRRP MAC — implementing D13's label at L1 would have reddened two committed traps. Prove-to-red: six mutations, **every red assertion-carried, none compiler-carried**. **5.6 follows and is in `review`** — the blocker, on branch `story-5.6-blocker-and-recall-assertion` off `master` at `440b30e`: `crates/opencmdb-core/src/identity/blocking.rs` ships `CandidatePair` (private fields ordered by its constructor, `new(a, a) -> None` — which closes the register's self-pair entry **in the type**), `candidates(&[Observation]) -> BTreeSet<CandidatePair>` — **TOTAL by decision**, every unordered pair of distinct `obs_id`s, calling neither `join` nor `decide_pair` because a blocker that consults a rule is that rule's echo — and the floor. **The float was resolved by TYPING it, not avoiding it**: D13's `blocking_recall >= 0.999` ships as `BLOCKING_RECALL_FLOOR_PER_MILLE: u32 = 999` on D13's own milli-units corollary, and the `float-free` gate was **not** weakened — it walks **4** files under `identity/` where it walked 3. The corpus assertion lives in `opencmdb-bin`'s `fixtures.rs` test module because D47 forbids the domain crate to read files: **24 traps, 23 pairs, 10 `must-merge`, recall 1000‰, all asserted rather than quoted**. `blocking_recall_above_999` computes only the recall and the per-trap containment is a **separate** test — measured to be load-bearing: in one test a missing pair panics before any recall exists. **309 → 332 tests**, and **333** after the code review. Six mutations: **M1 (blocking on an L1 key) scores 700‰ exactly**, and **M2 (blocking on `l2_domain`) leaves the entire corpus GREEN** — only a synthetic cross-domain test reds it, which is why that test was written first. Zero compiler-carried reds. Nothing feeds the corpus harness (5.7), the blocker has **no production caller**, and `Verdict::Supports`/`Opposes` still have **no producer** (Epic 6). **5.6 has now been code-reviewed** (three layers, 2026-08-01): 9 patches applied, 2 deferrals registered, 11 findings dismissed, **332 → 333 tests**, six gates green. What it caught was again mostly CLAIMS rather than behaviour — 6 of the 9 were sentences — and the HIGH one was a doc asserting that blocking on the MAC, the hostname *or* the `l2_domain` would each pass the whole corpus, **refuted by a measurement inside the same commit**: measured over the ten `must-merge` pairs, a MAC-blocked universe scores **700‰** (the story's own M1), a hostname-blocked one **400‰**, and only `l2_domain` reaches 1000‰. The one behavioural gap: no test fed `candidates` an observation with EMPTY `facts`, so a fact-presence narrowing was measured green across all 332 tests — the fifth consecutive story whose review found a claim outrunning its measurement. **Next is the merge.** _(This sentence named only 5.1 until 2026-07-29 — stale for a day, found by story 5.3's code review, not by the two stories that caused the drift.)_ Live status is `_bmad-output/implementation-artifacts/sprint-status.yaml`; grounding is `docs/project-context.md`.

### Build / lint / test commands (the stack is chosen and building)

Expand Down
Loading