Skip to content

docs(release): record the changes since rc.5 and correct the incident guidance - #883

Merged
remyluslosius merged 2 commits into
mainfrom
docs/v0-8-0-changelog-and-incident-guidance
Sep 27, 2026
Merged

remyluslosius merged 2 commits into
mainfrom
docs/v0-8-0-changelog-and-incident-guidance

Conversation

@remyluslosius

Copy link
Copy Markdown
Contributor

Documentation for everything merged after v0.8.0-rc.5, plus one pre-existing runbook defect in a separate commit.

Commit 1: release notes and incident guidance for #870 to #879

  • CHANGELOG [Unreleased] covers the ten PRs since rc.5 (ci(go): run internal/server in its own test invocation, alone #874 is CI-only and not listed). It has upgrade notes, Security, Changed, Fixed and Known limitations sections. Each entry was checked against merged code: the 0065 migration body, the binder sid check and EvaluateBearerBinding, the logout CSRF branch, the LockWaitBound / OperationDeadline / RollbackCleanupLimit constants, and the auth.login.failure and admin.user.enabled declarations in audit/events.yaml.
  • QUICKSTART incident step said sessions end "via logout". Logout ends one login, and a self-service password change signs out nothing else (CP bugs/OW-072). It now names disable and the administrator reset.
  • SECURITY_INCIDENT said only a signing-key rotation ends an access token. Since fix(auth): bind access tokens to the session that issued them #876 a token names its session and is refused once that session is revoked. Key rotation stays, scoped to a key that may itself be exposed.

Commit 2: CP bugs/OW-082 (droppable)

All three defects are present in rc.5. This is proposed under the interruption rule's operator-text clause and can be dropped without affecting commit 1.

Merge-order note

#880, #881 and #882 each add an entry to CHANGELOG.md [Unreleased], and #881 edits the API-token row of the same SECURITY_INCIDENT table. Whichever merges second rebases. The conflicts are textual; no entry contradicts another.

Verification

  • check-doc-style.py clean on all three files; pre-commit clean (the .secrets.baseline change is the hook's line-number refresh).
  • No code changes.

@github-actions github-actions Bot added documentation Improvements or additions to documentation size/L labels Sep 26, 2026
… guidance

CHANGELOG [Unreleased] gains the ten PRs merged after v0.8.0-rc.5 (#870
to #879; #874 is CI-only and is not listed). Upgrade notes lead: the
0065 migration signs everyone out, cookie logout requires the CSRF
token, and the audit export refuses an unknown parameter. Each entry was
checked against the merged code: the 0065 migration body, the binder's
sid check and EvaluateBearerBinding, the logout CSRF branch, the
LockWaitBound, OperationDeadline and RollbackCleanupLimit constants, and
the auth.login.failure and admin.user.enabled declarations in
audit/events.yaml. Known limitations name CP bugs/OW-072 and OW-062.

QUICKSTART's incident step said active sessions end "via logout" and
told operators to rotate passwords. Logout ends one login, and a user's
own password change signs out nothing else (OW-072). It now names
disable and the administrator reset, which end every interactive
credential since #875 and #876.

SECURITY_INCIDENT said an access token is ended only by rotating the
signing key. Since #876 it names its session and is refused once that
session is revoked, so revoking the rows ends it with no restart. Key
rotation is kept, scoped to a key that may itself be exposed.
Three defects in SECURITY_INCIDENT, all present in v0.8.0-rc.5 (CP
bugs/OW-082), kept in their own commit so they can be dropped
independently.

- "There is no is_active flag; disabling an account means
  soft-deleting it." POST /api/v1/users/{id}:disable has existed since
  #601 and, since #875, ends every interactive credential. The section
  now leads with disable, which :enable reverses, and keeps delete and
  the SQL fallback with what each does and does not do.
- The delete was said to be audited as account.user.deleted, which is
  the host-side /etc/passwd event. DeleteUserByID emits
  admin.user.deleted.
- Recovery verification step 3, headed "No live sessions for disabled
  accounts", checked only deleted_at. It now checks disabled_at too.
@remyluslosius
remyluslosius force-pushed the docs/v0-8-0-changelog-and-incident-guidance branch from bc34d1a to 13ccc3b Compare September 27, 2026 01:23
@remyluslosius
remyluslosius merged commit 25b1794 into main Sep 27, 2026
14 checks passed
@remyluslosius
remyluslosius deleted the docs/v0-8-0-changelog-and-incident-guidance branch September 27, 2026 01:35
remyluslosius added a commit that referenced this pull request Sep 28, 2026
release-changelog C-02 and AC-02 allow only Added, Changed, Deprecated,
Removed, Fixed and Security as category headings. #883 added "Upgrade notes"
and "Known limitations" headings to [Unreleased], and main fails
TestChangelog_UnreleasedCategoriesAreStandard. Go CI did not catch it:
#883 changed only documentation, so the quality gate skipped every Go test.

Both blocks move, unchanged in content, into the section's lead-in as
labeled paragraphs, the form the candidate sections already use for text
before their first category. No entry is dropped.
remyluslosius added a commit that referenced this pull request Sep 28, 2026
* feat(kensa): integrate Kensa v0.10.0

Pins github.com/Hanalyx/kensa v0.10.0 (only kensa moved in go.sum). The
engine constant, system-kensa-executor 2.10.0 (context, C-13) and the
third-party notices follow.

Corpus: 769 to 779 rules, two new framework keys, nist_800_171 and
cmmc_l2, 324 rules each, measured through pkg/kensa.RuleFrameworkRefs.
The backend family labels and the host-detail lens chips name them
"NIST 800-171" and "CMMC Level 2"; the chip transform would have
rendered "CMMC L 2" (frontend-host-compliance-tab 1.7.0, AC-12). The
README, the scanning guide framework table and the distribution matrix
are re-derived from v0.10.0.

Variables: v0.10.0 ships eight corpus-used variables with an empty
default. The catalog test bounded the list at a constant 29; it now
derives the bound from BuiltInVars and checks membership.
api-system-scan-config 1.5.0 adds AC-11: a list override reaches the
rule's set_compare parameters verbatim on reload, and removing it
restores the default. configure_me stays limited to the three
placeholders (C-07); extending it is a decision, not part of this change.
The scanning guide gains a Scan variables section naming the eight, the
list syntax and the fact that values are not type checked.

Mutation checks, each red then restored by inverse edit with the hash
verified: reload without overrides (AC-11), a wrong CMMC backend label
(system-compliance-lens AC-02), a missing CMMC chip label (AC-12).

Findings filed, not fixed here: CP bugs/OW-080 (override values are
stored and applied without a type check; the Kensa checker is internal,
features/KN-OW-022) and bugs/OW-081 (the rc.5 engine cannot load the
0.10.0 corpus and the packages do not forbid that pairing; a decision is
needed before this merges).

* fix(packaging): refuse a Kensa corpus beside an older engine

CP bugs/OW-081. Kensa v0.9.0 cannot load the v0.10.0 corpus: LoadRules
fails on the first variable it does not know, serve starts anyway, and
every scan fails. kensa-rules takes its version from go.mod and openwatch
required it unversioned, so a rules-only upgrade onto rc.5 or earlier
produced exactly that pairing.

The openwatch RPM and DEB now provide openwatch-kensa-engine at the Kensa
version they link, and kensa-rules requires openwatch-kensa-engine at
least its own version. Both come from one helper,
packaging/common/kensa-version.sh, which the corpus stager also uses, so
the two values cannot drift. No floor is written by hand: a fixed
"openwatch >= rc.6" would have made the pair built on main uninstallable
together until the version bump. An older corpus under a newer engine
loads, so an openwatch-only upgrade stays allowed. The spec default
0.0.0 satisfies no corpus, so a build that forgets the define fails at
install rather than shipping an unguarded pair.

Contract: release-upgrade 1.1.0, C-06, AC-08 (built artifacts carry the
provide and the requirement at the go.mod version, read from go.mod
independently of the helper) and AC-09 (container behavior on RPM and
DEB). package-smoke gains a kensa-rules-compat job that runs AC-09
against v0.7.1, the previous GA.

Measured locally against the published v0.8.0-rc.5 packages and this
tree built as rc.6, Rocky 9 and Ubuntu 24.04:
- dnf, rpm -U and apt refuse kensa-rules 0.10.0 alone; the installed
  corpus stays 0.9.0;
- openwatch-only upgrade, coordinated upgrade and fresh install succeed;
- the new corpus refuses a downgrade of openwatch to rc.5;
- 1:0.8.0~rc.5 < ~rc.6 < ~rc.10 < 1:0.8.0 and 0.9.0 < 0.10.0 in both
  formats.
Mutations, each red then restored with the hash checked: kensa-rules
without the floor (AC-09, both formats), openwatch without the provide
(AC-09, both formats), and the DEB provide left at 0.0.0 (AC-08).

Residual, measured and documented, not covered: a bare dpkg -i of the
rules package unpacks it and leaves it unconfigured (the files are
replaced), and rpm --nodeps bypasses the check. The documented paths use
dnf and apt. The upgrade runbook and the CHANGELOG say so.

* fix(kensa): flag variables the operator must decide and keep NIST lift to 800-53

configure_me (api-system-scan-config C-07, amended in 1.5.0, which is
unpublished). It marked the three placeholder defaults only. It now marks
every variable whose built-in value cannot be right for a real site: the
three placeholders and every corpus-used variable Kensa ships with an
empty default. For v0.10.0 that is eleven, named in C-07 and pinned in
the test: the three plus authorized_listening_ports,
authorized_local_accounts, authorized_network_protocols,
authorized_privileged_users, authorized_service_accounts,
authorized_services, flaw_remediation_max_days and suid_sgid_baseline.
The other 32 carry a benchmark value or a generic list valid as shipped.
C-07 says configure_me marks a decision to make and does not classify a
scan result, which needs KN-OW-021. The card's count no longer calls
every flagged value a placeholder; the OpenAPI description follows and
the generated code is refreshed.

The catalog test (AC-08) now checks contents: the entry set, each
default and each rule-id list against values built in the test from the
two library tables, two fixed points, and the ConfigureMe set against
the named inventory. Mutations, each red then restored with the hash
checked: flag placeholders only; trim whitespace from defaults, which
changes only banner_text.

Remediation projected lift (api-remediation 1.8.0, C-07, AC-18). The
nist bucket matched every key starting "nist", so v0.10.0's nist_800_171
joined it: 19 rules carry 800-171 and not 800-53, so they would be
quoted a NIST gain, and the denominator would count both frameworks.
The bucket is now the 800-53 family, in both the class match and the
SQL denominator. AC-18 discriminates each half, and a mutation of each
half turns it red (25 instead of 33.33; a lift quoted for an 800-171-only
rule).

* test(packaging): verify the engine pairing against the resolvers in Go CI

Go CI failed its specter sync gate on 3a243ae: release-upgrade AC-09
skipped there, because its only test drove containers, which only
package-smoke provides. A skipped criterion is uncovered.

AC-09 now asks the resolvers directly, with no root, container or
network: rpm --test against a scratch rpmdb, and apt-get -s against a
fixture dpkg status, using the real packages the tree builds. The older
openwatch is a payload-free fixture at 1:0.7.1 that provides no engine.
It must sort below the tree's own build (packaging/version.env's rc),
which the first draft of this test got wrong: with the fixture at rc.5
the resolvers saw the same version already installed and tested nothing.
A base fixture supplies every other requirement of the packages under
test, derived from their own Requires and Depends.

Scenarios in both formats: rules-only upgrade refused naming
openwatch-kensa-engine; openwatch-only, coordinated and fresh install
accepted; downgrade to the fixture refused; version ordering. Mutations,
each red then restored with the hash checked: kensa-rules without the
floor (rules-only and downgrade accepted, both formats) and openwatch
without the provide (coordinated and fresh refused, both formats).

The container test stays, without a criterion of its own, and
package-smoke's kensa-rules-compat job runs it by its new name. AC-09's
text now says how it is verified.

* feat(frameworks): name every framework with Kensa's label (D-2 S-7)

Founder decision 2026-09-26: S-7 is in v0.8. OpenWatch held three label
sources that disagreed (internal/framework's map, the Compliance tab's
id transform, and the report's first-token collapse), and the rule
library and scan detail grouped every nist* key as one "NIST" tone. So
an 800-53 report and an 800-171 report carried the same "NIST" on their
cover, OSCAL title and file name.

One source now: internal/kensa.FrameworkLabel wraps
pkg/kensa.FrameworkFromID. An id Kensa does not know is returned as it
is; OpenWatch derives no label.
- framework.Label (family labels) and the report scope label use it.
- The API carries it: label on GET /hosts/{id}/compliance/frameworks
  and GET /reports/frameworks, framework_labels on GET /rules and GET
  /scans/{id}. Additive fields; generated code refreshed.
- The UI renders them: lens chips and panels, the report picker, and
  the rule library and scan detail tags (title and accessible name).
  The rule library filter offers one option per framework, by label.

Signed artifacts: the attestation content still carries the exact key.
The scope label is computed at generation and stored, and every face
reads the stored value, so a report generated before this change keeps
its label, its JSON face still hashes to content_sha256 and its
signature verifies (api-reports AC-27).

Contracts: system-compliance-lens 1.7.0 C-08, AC-02 amended, AC-14;
api-reports 1.19.0 AC-07 and AC-09 amended, AC-27;
frontend-host-compliance-tab 1.7.0 AC-12 rewritten (unpublished, so in
place); frontend-reports 1.14.0 AC-09; frontend-rules-library 1.1.0
AC-05; frontend-scan-detail 1.2.0 AC-09.

Mutations, each red then restored with the hash checked. The first
backend round failed to build, because removing the only kensa call left
an unused import, so it proved nothing; each was re-run in a form that
compiles:
- Label upper-casing the id (AC-02);
- the scope label's first-token collapse (AC-07, AC-27);
- the host label set to the raw id (AC-14);
- scan detail labels emptied (AC-14);
- the chip rendering framework_id (AC-02);
- the rule filter ignoring labels (AC-05);
- tags ignoring labels (AC-05).

Visible changes: "CIS RHEL 9" reads "CIS (RHEL 9)", "PCI DSS 4" reads
"PCI DSS 4.0". Kensa formats only RHEL versions, so Ubuntu benchmarks
read "CIS (ubuntu22)"; requested from Kensa as CP features/KN-OW-023
rather than patched locally.

* fix(packaging): never unpack a Kensa corpus beside an older engine

CP bugs/OW-081, design completed on founder authorization 2026-09-26.
The earlier change guaranteed only that the resolver refuses. dpkg
unpacks a package with an unmet Depends before failing, so a bare
`dpkg -i` of kensa-rules 0.10.0 beside rc.5 replaced the corpus and left
the package unconfigured, with every scan failing. That is an ordinary
install path, not a bypass.

Four DEB mechanisms were measured against the same scenarios before one
was chosen:
- Depends only: rules-only dpkg -i replaced the corpus.
- Breaks on openwatch older than rc.6: refused dpkg -i, but made the
  tree's own pair uninstallable, because main builds as the published
  rc.5 version.
- Pre-Depends: deadlocked with openwatch's own Depends, so even
  coordinated and fresh installs failed.
- A preinst guard (chosen): refused rules-only dpkg -i and apt with every
  corpus file byte-identical, and accepted the apt coordinated upgrade,
  dpkg -i with openwatch listed first, fresh installs and main's own
  pair. Its one cost is dpkg -i with the rules listed first: refused,
  leaving openwatch upgraded beside the old corpus, which is a working
  pair, with a message naming the fix.

packaging/kensa-rules/preinst.in reads openwatch's Status-Status and
Provides through dpkg-query, so a held or half-installed openwatch is
still checked, and compares with dpkg --compare-versions. RPM needed
nothing more: dnf and rpm -U refuse before touching files.

Measured, both formats: rolling openwatch back alone is refused;
rolling both packages back in one command succeeds. The upgrade runbook
now says so, with the recovery for a corpus forced on with --nodeps or
--force-depends (plain apt refuses from that broken state; dpkg -i then
dpkg --configure -a works).

Contract: release-upgrade C-06 reworded as the boundary tested today,
not a promise about future engines, and AC-10 added. AC-10 runs the
preinst from the built package against a stub dpkg-query in Go CI. The
container test gains the dpkg -i, rollback and file-digest checks, and
passes on Ubuntu 24.04 and Rocky 9. Mutations, each red then restored
with the hash checked: no preinst in the package; the guard checking
only "installed"; ge weakened to gt.

* chore(secrets): refresh baseline line numbers after rebasing onto main

detect-secrets rescan after the rebase moved flagged lines. No finding was added or removed; only line numbers changed.

* test(packaging): run the pairing scenarios with scriptlets and check state and corpus separately

The container test ran RPM transactions with tsflags=noscripts and checked
only the recorded kensa-rules version plus a corpus digest. It did not cover
a joint rpm -Uvh, dnf upgrade, or single-package upgrades in both orders.

- RPM transactions now run their scriptlets. The openwatch upgrade scriptlet
  skips its migration when no database answers, so a container is enough.
- New scenarios: dnf upgrade of both packages, a joint rpm -Uvh, and
  single-package upgrades in both orders through the package manager (and
  rpm -U on RPM).
- After every step, package-manager state and corpus contents are checked as
  separate facts: each package's recorded version and a clean dpkg --audit or
  rpm -V, and a digest of /usr/share/kensa/rules compared with the payload of
  the kensa-rules package that should be installed.

Removing the DEB preinst makes the refused dpkg -i step fail all three checks:
version, dpkg --audit and corpus digest.

* docs(packaging): scope the corpus check to OpenWatch-built packages and disclose the Kensa package

The CHANGELOG, the upgrade runbook and release-upgrade C-06 said a newer
corpus is refused beside an older openwatch on every ordinary install path.
That holds for the kensa-rules package OpenWatch builds. Kensa publishes a
package with the same name and install path that declares no engine
requirement, and openwatch's dependency on the name accepts it. The text
described a protection that does not cover that package.

- release-upgrade C-06 (1.1.0, not yet published) now scopes the MUST to the
  OpenWatch-built package and states the Kensa package as disclosed, not
  prevented (CP bugs/OW-081, criterion 3b).
- The upgrade runbook gains "What this check does not cover": the Kensa
  package, hosts on rc.5 or earlier, how to tell the two packages apart, and
  how to restore OpenWatch's. Updating the rules now means both packages from
  one OpenWatch release.
- The CHANGELOG qualifies the refusal and adds a known limitation.
- The installation guide no longer says the rules update without an
  OpenWatch release, which the engine requirement made untrue.

The secrets baseline moves only line numbers; no finding was added or
removed.

* docs(changelog): keep [Unreleased] to the standard categories

release-changelog C-02 and AC-02 allow only Added, Changed, Deprecated,
Removed, Fixed and Security as category headings. #883 added "Upgrade notes"
and "Known limitations" headings to [Unreleased], and main fails
TestChangelog_UnreleasedCategoriesAreStandard. Go CI did not catch it:
#883 changed only documentation, so the quality gate skipped every Go test.

Both blocks move, unchanged in content, into the section's lead-in as
labeled paragraphs, the form the candidate sections already use for text
before their first category. No entry is dropped.
remyluslosius added a commit that referenced this pull request Sep 28, 2026
Stage 1 for the sixth 0.8.0 candidate, from main fc1117d. VERSION is
0.8.0-rc.6 in packaging/version.env, the README phrase and the newest
CHANGELOG heading; CODENAME stays Eyrie. The hygiene test binds the three.

The changelog section records rc.5 on facts: built, every machine gate
passed, assets published as a pre-release, no human verdict recorded for
its fleet checks, documentation review or release-captain signature, tag
and assets preserved. Since rc.5: #870 to #881, #883 and #882. The
[Unreleased] notes move into the rc.6 section unchanged.

Known limitations gain the two the readiness record lists as shipping with
v0.8: drift does not distinguish a corpus change from a host change (D-2
S-3, accepted 2026-09-26), and scan variable values are not type checked
(OW-080).

Next unused candidate number verified: no v0.8.0-rc.6 tag on the remote or
locally, and no release or draft of that name.

No tag, publication or attestation.
remyluslosius added a commit that referenced this pull request Sep 28, 2026
Stage 1 for the sixth 0.8.0 candidate, from main fc1117d. VERSION is
0.8.0-rc.6 in packaging/version.env, the README phrase and the newest
CHANGELOG heading; CODENAME stays Eyrie. The hygiene test binds the three.

The changelog section records rc.5 on facts: built, every machine gate
passed, assets published as a pre-release, no human verdict recorded for
its fleet checks, documentation review or release-captain signature, tag
and assets preserved. Since rc.5: #870 to #881, #883 and #882. The
[Unreleased] notes move into the rc.6 section unchanged.

Known limitations gain the two the readiness record lists as shipping with
v0.8: drift does not distinguish a corpus change from a host change (D-2
S-3, accepted 2026-09-26), and scan variable values are not type checked
(OW-080).

Next unused candidate number verified: no v0.8.0-rc.6 tag on the remote or
locally, and no release or draft of that name.

No tag, publication or attestation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant