feat(pkg/kensa): public rule read model for catalog consumers#85
Merged
Conversation
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
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>
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
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 heterogeneousreferencesmap and an unsummarizedRemediation— so each consumer re-implementedinternal/mappings.RefsFromReferences(the path the scanner already uses forScanResult.Outcomes) and re-derived the framework-id scheme. This publishes the read model onpkg/kensa:RuleFrameworkRefs(*api.Rule)[]api.FrameworkRef, delegating tointernal/mappings— no re-implementation, no driftFramework/FrameworkFromID/Frameworkscis_rhel9→{Family:"cis", Version:"rhel9", Label:"CIS (RHEL 9)"}); unknown frameworks degrade gracefullyRuleSummary/RuleToSummary/LoadRuleSummariesRulefields, via the existingLoadRulespathRemediationSummaryAvailable,Mechanisms,RestartsServices,RebootBehavior(boot-param/none)Boundary compliance (the deliberate omissions)
Per §3.3,
RemediationSummarydoes not carry:RiskLevel— operator policy (environment-dependent); the consumer computes it from the facts.RequiresRebootbool — 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.RebootBehavioris the honest mechanism-level signal; a complete answer needs an authoredrequires_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 frozenapi/is untouched → future v0.4.3 PATCH. (RuleFrameworkRefsis a function, not a method onapi.Rule, becauseapi/is a leaf and can't importinternal/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 reportsRebootBehavior: boot-param.Gate
go test ./...green · golangci-lint 0 · specter 119/119 (rule-read-modelT2 7/7). No engine/capture/rollback touched.🤖 Generated with Claude Code