Skip to content

feat(pkg/kensa): public rule read model for catalog consumers#85

Merged
remyluslosius merged 1 commit into
mainfrom
feat/rule-read-model
Jun 15, 2026
Merged

feat(pkg/kensa): public rule read model for catalog consumers#85
remyluslosius merged 1 commit into
mainfrom
feat/rule-read-model

Conversation

@remyluslosius

Copy link
Copy Markdown
Contributor

What

Tranche 1 of the OpenWatch read-model request, scoped by the ownership test in docs/KENSA_OPENWATCH_BOUNDARY.md §3.3: publish the normalization Kensa already owns; carry facts, not policy.

A consumer rendering a rule catalog previously had only the raw api.Rule — the heterogeneous references map and an unsummarized Remediation — so each consumer re-implemented internal/mappings.RefsFromReferences (the path the scanner already uses for ScanResult.Outcomes) and re-derived the framework-id scheme. This publishes the read model on pkg/kensa:

Surface Purpose
RuleFrameworkRefs(*api.Rule) Normalized []api.FrameworkRef, delegating to internal/mappings — no re-implementation, no drift
Framework / FrameworkFromID / Frameworks Framework registry (cis_rhel9{Family:"cis", Version:"rhel9", Label:"CIS (RHEL 9)"}); unknown frameworks degrade gracefully
RuleSummary / RuleToSummary / LoadRuleSummaries Lightweight catalog projection over existing Rule fields, via the existing LoadRules path
RemediationSummary Derivable facts only: Available, Mechanisms, RestartsServices, RebootBehavior (boot-param/none)

Boundary compliance (the deliberate omissions)

Per §3.3, RemediationSummary does not carry:

  • RiskLevel — operator policy (environment-dependent); the consumer computes it from the facts.
  • A blanket RequiresReboot bool — mechanism dictates reboot only for the 8 grub boot-param rules; deriving it for the change-specific cases (auditd -e 2, SELinux enable) is a dangerous false-negative. RebootBehavior is the honest mechanism-level signal; a complete answer needs an authored requires_reboot: schema field (deferred).

Placement / semver

Types live on pkg/kensa (public-but-not-frozen) — the read model is a derivation that will grow. The frozen api/ is untouched → future v0.4.3 PATCH. (RuleFrameworkRefs is a function, not a method on api.Rule, because api/ is a leaf and can't import internal/mappings — a method there would force duplicating the normalization.)

Verified against the real corpus

539 summaries load; the 9 distinct frameworks normalize to clean labels (CIS (RHEL 8/9/10), STIG (RHEL 8/9/10), NIST 800-53, PCI DSS 4.0, SRG); a grub rule correctly reports RebootBehavior: boot-param.

Gate

go test ./... green · golangci-lint 0 · specter 119/119 (rule-read-model T2 7/7). No engine/capture/rollback touched.

🤖 Generated with Claude Code

Tranche 1 of the OpenWatch read-model ask, scoped by the ownership test
in docs/KENSA_OPENWATCH_BOUNDARY.md §3.3: publish the normalization Kensa
already owns; carry facts, not policy.

A consumer rendering a rule catalog previously had only the raw api.Rule
— the heterogeneous `references` map and an unsummarized Remediation —
so every consumer re-implemented internal/mappings.RefsFromReferences
(the path the scanner already uses for ScanResult.Outcomes) and
re-derived the framework-id scheme. This publishes the read model:

- RuleFrameworkRefs(*api.Rule) — normalized []api.FrameworkRef,
  delegating to internal/mappings (no re-implementation; api/ is a leaf
  and can't import mappings, so this is a pkg/kensa function, not a
  method on api.Rule).
- Framework / FrameworkFromID / Frameworks — a framework registry
  (cis_rhel9 -> "CIS (RHEL 9)") so consumers stop hardcoding prefixes;
  unknown frameworks degrade gracefully.
- RuleSummary / RuleToSummary / LoadRuleSummaries — a lightweight catalog
  projection over existing Rule fields, loaded via the existing LoadRules
  path (539-rule production corpus verified).
- RemediationSummary — derivable FACTS only: Available, Mechanisms,
  RestartsServices, RebootBehavior (boot-param/none). Per the boundary it
  omits RiskLevel (operator policy) and a blanket RequiresReboot
  (mechanism dictates reboot only for boot-param rules; deriving it
  elsewhere is a false-negative — needs an authored schema field,
  deferred).

Types live on pkg/kensa (public-but-not-frozen); the read model is a
derivation expected to grow. The frozen api/ surface is untouched. Spec
rule-read-model (Tier 2, 7 ACs). No engine/capture/rollback touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@remyluslosius remyluslosius merged commit 2c1f15d into main Jun 15, 2026
14 checks passed
@remyluslosius remyluslosius deleted the feat/rule-read-model branch June 15, 2026 01:28
remyluslosius added a commit that referenced this pull request Jun 15, 2026
Stamp v0.4.3. VERSION -> 0.4.3; CHANGELOG's Unreleased "public rule read
model on pkg/kensa" entry (PR #85) moves under the v0.4.3 heading and
Unreleased resets.

PATCH bump: the addition lives on pkg/kensa (RuleFrameworkRefs / Framework
registry / RuleSummary / RemediationSummary / LoadRuleSummaries); the
frozen api/ surface is untouched. Tranche 1 of the OpenWatch read-model
ask, scoped by the ownership test (boundary §3.3) — facts, not policy.

Verification: go test ./... green; goreleaser check + snapshot build;
Makefile build stamps `kensa 0.4.3`. The signed release pipeline
triggers on the v0.4.3 tag.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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