Skip to content

chore(deps): bump the minor-and-patch group across 1 directory with 3 updates - #332

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/go_modules/minor-and-patch-51698a0d21
Open

chore(deps): bump the minor-and-patch group across 1 directory with 3 updates#332
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/go_modules/minor-and-patch-51698a0d21

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 20, 2026

Copy link
Copy Markdown
Contributor

Bumps the minor-and-patch group with 3 updates in the / directory: golang.org/x/sys, golang.org/x/term and modernc.org/sqlite.

Updates golang.org/x/sys from 0.46.0 to 0.47.0

Commits
  • 9e7e939 cpu: handle vendor suffixes in parseRelease
  • f6fb8a1 unix: use epoll_pwait rather than epoll_wait
  • f3eeabf windows: avoid length overflow in NewNTString
  • 3cb6647 unix: update glibc to 2.43
  • c507910 windows: document safe usage of TrusteeValue
  • See full diff in compare view

Updates golang.org/x/term from 0.44.0 to 0.45.0

Commits

Updates modernc.org/sqlite from 1.53.0 to 1.54.0

Changelog

Sourced from modernc.org/sqlite's changelog.

Changelog

  • 2026-07-20 v1.55.0:

    • Add github.com/mattn/go-sqlite3-compatible shorthand DSN query parameters to ease migration from that driver: _busy_timeout/_timeout, _foreign_keys/_fk, _journal_mode/_journal, _synchronous/_sync, _auto_vacuum/_vacuum, and _query_only, each setting the correspondingly named PRAGMA. Values are validated against the same set mattn/go-sqlite3 accepts (case-insensitive) and an unrecognized value fails the connection with an error, so a typo such as _synchronous=fu1l or _foreign_keys=yes_please is reported rather than silently downgrading durability or dropping foreign-key enforcement. The keys are applied in a fixed order independent of their order in the DSN — _busy_timeout and _auto_vacuum before any _pragma values (auto_vacuum must be set before the database is first written), the rest after, and _query_only last — and where a key and its alias are both supplied the alias wins, matching mattn/go-sqlite3; selection is by presence rather than by value, so supplying the alias empty (_foreign_keys=on&_fk=) suppresses the PRAGMA rather than deferring to the primary key, again matching that driver. Behavior change to note: prior releases ignored these keys entirely, so a DSN carried over from a mattn/go-sqlite3 setup changes in two ways. A recognized key that previously did nothing now takes effect — _foreign_keys=on begins enforcing constraints against data that may already violate them, _journal_mode=wal persistently converts the database file, and _query_only=1 makes the connection read-only. And a value outside the accepted set now fails the connection with an error where the same DSN previously opened successfully — for example a duration-style _busy_timeout=5s or _timeout=5000ms, neither of which is the integer that key requires. Review such DSNs before upgrading. _pragma is unchanged and no pre-existing parameter changes meaning, though see the following entry for a change in when all of them are validated.
    • See [GitLab merge request #134](https://gitlab.com/cznic/sqlite/-/merge_requests/134), thanks Toni Spets (@​beeper-hifi) and Ian Chechin!
    • Validate every DSN query parameter before applying any of them. Parameters were previously checked as each was reached, so a DSN whose later parameter was rejected had already executed the PRAGMAs ahead of it. Because PRAGMA journal_mode and PRAGMA auto_vacuum are persistent changes to the database file, a DSN such as file:x.db?_journal_mode=wal&_synchronous=bogus failed the connection and yet left x.db converted to WAL. A failed Open now leaves the database as it found it. This covers the pre-existing _txlock, _timezone, _time_format, _time_integer_format, _inttotime and _texttotime parameters as well as the shorthand keys above: all of them were validated only after the _pragma list had already run, so the same DSN shape — a valid _pragma=journal_mode=wal alongside a misspelled _txlock — converted the file before reporting the error. Only the values accepted for each parameter are unchanged; a DSN that opened successfully before still opens, and one that failed still fails with the same error. _pragma remains the sole exception, since its values are executed verbatim and cannot be checked in advance: a malformed _pragma is still rejected by SQLite as it runs, after any earlier _pragma in the list has taken effect.
  • 2026-07-15 v1.54.0:

    • Upgrade to SQLite 3.53.3. This also bumps the pinned modernc.org/libc to v1.74.1; as always, downstream modules must pin the exact same modernc.org/libc version this module's go.mod pins (see [GitLab issue #177](https://gitlab.com/cznic/sqlite/-/issues/177)).
    • Under the opt-in _texttotime DSN parameter, best-effort parse date-shaped TEXT values from columns SQLite reports with an empty declared type — aggregates and expressions over a date column (MAX(d), COALESCE(d, ...), upper(d), d || ''), subqueries, and typeless real columns (CREATE TABLE t(x)) — into time.Time, instead of delivering them as a raw string that Scan cannot store into a *time.Time. The existing declared DATE/DATETIME/TIME/TIMESTAMP path is unchanged; this only adds the empty-decltype case. The conversion is strictly best-effort: a value that does not parse as a time falls through to the original string, so no Scan that worked before can newly fail. ColumnTypeScanType continues to report string for empty-decltype columns, since the declared type cannot prove the column is temporal. Without _texttotime the behavior is byte-for-byte unchanged. Resolves [GitLab issue #248](https://gitlab.com/cznic/sqlite/-/issues/248).
    • See [GitLab merge request #133](https://gitlab.com/cznic/sqlite/-/merge_requests/133), thanks Ian Chechin!
  • 2026-06-21 v1.53.0:

    • Add experimental netbsd/amd64 support, resolving the long-standing build break in [GitLab issue #246](https://gitlab.com/cznic/sqlite/-/issues/246). This target is intentionally not yet listed among the supported platforms in the package documentation: the port had been broken for years and is only now revived, and there is as yet no real-world experience running it under production workloads. Green CI is not the same as battle-tested — so while the full test suite (including the pcache and vec packages and the -race concurrency test) passes on NetBSD 10.1 / Go 1.26.3, and the entire upstream toolchain (libc, cc, ccgo, libz, libtcl8.6, libsqlite3, libsqlite_vec) is green on the NetBSD CI builder, the target is offered for evaluation only. If you run NetBSD, please exercise it with your own workloads and report back via #246; the intent is to promote it to a fully supported platform after a period of broader real-world testing (on the order of a month) elapses without surprises.
    • Implementation notes: the previously shipped lib/sqlite_netbsd_amd64.go was a stale old-generator transpile that no longer compiled (the mu.enter/mu.leave break in #246); it is replaced by a fresh new-generator transpile consistent with every other platform, and modernc.org/sqlite/vec (sqlite-vec) is vendored and auto-registers on netbsd. Correct operation requires the matching pinned modernc.org/libc, which carries two NetBSD-specific fixes found during this work: the mmap(2) PAD-argument ABI (without it, concurrent WAL access faults with SIGBUS in the WAL-index shared memory) and a working abort(3) (the prior stub left SQLite's crash-recovery writecrash test unable to terminate by signal). As usual, downstream modules must pin the exact modernc.org/libc version this module's go.mod pins.
    • See [GitLab merge request #82](https://gitlab.com/cznic/sqlite/-/merge_requests/82), thanks Leonardo Taccari (@​iamleot) and Thomas Klausner (@wiz)!
    • Add experimental freebsd/386 and freebsd/arm support. As with the netbsd/amd64 target above, these two 32-bit FreeBSD ports are intentionally not yet listed among the supported platforms in the package documentation: freebsd/386 previously shipped a stale, effectively untested SQLite 3.41 transpile, and freebsd/arm is entirely new, so neither has real-world production mileage yet. Both are now freshly transpiled at SQLite 3.53.2 consistent with every other platform, build cleanly, and pass the full test suite (core, WAL/concurrency, and the vec package) on the FreeBSD CI builders; they are offered for evaluation only. If you run 32-bit FreeBSD, please exercise these targets with your own workloads and report back — the intent is to promote freebsd/386, freebsd/arm, and netbsd/amd64 to fully supported platforms in a future release cycle, once a period of broader real-world testing elapses without surprises.
    • Implementation notes: correct operation on freebsd/arm requires the matching pinned modernc.org/libc (v1.73.4), which fixes the per-arch mmap(2) off_t encoding for 32-bit FreeBSD; without it the WAL shared-memory mapping faults with SIGBUS under concurrent access, the same class of bug found on the netbsd port. As usual, downstream modules must pin the exact modernc.org/libc version this module's go.mod pins.
    • See [GitLab merge request #119](https://gitlab.com/cznic/sqlite/-/merge_requests/119), thanks Olivier Cochard-Labbé (@​ocochard)!
    • Add a Go-facing wrapper for SQLITE_CONFIG_PCACHE2. PageCache is the factory and Cache the per-database instance, both idiomatic Go interfaces; Page exposes the raw Buf and Extra pointers that SQLite reads through the C pcache contract. RegisterPageCache and MustRegisterPageCache install the module process-globally before the first sql.Open; subsequent Open calls are gated through a one-shot Xsqlite3_config(SQLITE_CONFIG_PCACHE2) so a too-late Register returns ErrPageCacheTooLate rather than silently falling through to the built-in pcache1. The binding owns the sqlite3_pcache_page stub and re-consults the implementation on every Fetch, reusing the stub only when the returned Page value is unchanged, which keeps a bounded/evicting purgeable cache safe by construction.
    • See [GitLab merge request #126](https://gitlab.com/cznic/sqlite/-/merge_requests/126), thanks Ian Chechin!
    • Add modernc.org/sqlite/pcache, the reference page-cache implementation that accompanies the #126 SQLITE_CONFIG_PCACHE2 wrapper. pcache.New returns a *Pool satisfying the PageCache interface; register it once with sqlite.MustRegisterPageCache(pcache.New()) and every connection opened afterwards draws its pages from it. Each Pool.Create mints a fresh per-database Cache: a bounded, LRU-evicting page store that honours the PRAGMA cache_size soft cap and releases the least-recently-unpinned page when it must make room. Page memory — the Buf and Extra buffers SQLite reads through — is allocated with libc.Xmalloc/libc.Xcalloc and therefore lives off the Go heap, which keeps SQLite's interior pointer arithmetic on the page extras from tripping the race detector's checkptr enforcement. Pool.Stats reports aggregate lifetime counters (hits, misses, allocs, evictions, rekeys, truncates, caches) across every cache a Pool has created, so hit/miss/eviction behaviour is observable without instrumenting individual caches. Cross-connection page sharing is out of scope for now; each Create returns an independent per-database cache.
    • Validated end-to-end against the #126 stress workload (cache_size=16, 4000 BLOB rows with DELETE and incremental_vacuum, integrity_check clean under -race) and benchmarked for the memory-utilization goal tracked in [GitLab issue #204](https://gitlab.com/cznic/sqlite/-/issues/204).
    • See [GitLab merge request #127](https://gitlab.com/cznic/sqlite/-/merge_requests/127), thanks Ian Chechin!
    • Tighten the modernc.org/sqlite/pcache reference implementation per cznic's !127 review follow-ups. Adds Stats.EasyRefusals, a per-Pool counter for the cases where FetchCreateEasy returns nil at cap; SQLite reacts to a refusal by spilling dirty pages and retrying with FetchCreateForce, so the new field is a direct proxy for the I/O pressure the strict Easy contract imposes vs pcache1's recycle-without-spill behavior. BenchmarkPoolEvictionChurn was reworked to drive a rotating-residue DELETE (k % 3 = i % 3) and re-insert a matching batch each cycle so the spill pressure recurs and easy-refusals/op scales with b.N instead of capping at the seed's one-time first-cycle cost; both existing benchmarks now report easy-refusals/op alongside the page-allocs/evictions metrics. Stats.Evictions documentation was tightened to match the actual behavior (counts LRU eviction, Unpin(discard=true), Shrink releases, and Unpin(discard=false) trimming back to target after a FetchCreateForce overcommit; bulk frees from Truncate, Rekey collisions, and Destroy are not counted). The TestPoolRoundTripIntegrity comment claiming the workload exercises xRekey ~15 times has been corrected; the SQL surface does not reliably emit xRekey here, and that codepath is covered by the unit tests instead.
    • See [GitLab merge request #130](https://gitlab.com/cznic/sqlite/-/merge_requests/130), thanks Ian Chechin!
    • Make modernc.org/sqlite/pcache -race-clean under SQLite's cache=shared mode. The pool already runs correctly under shared-cache because every callback into a given Cache is serialised internally by SQLite's sqlite3BtreeEnter on the BtShared mutex; verified empirically with a lock-free in-flight probe (max-in-flight = 1 on the canonical two-connection workload, 4 on a positive control with goroutines hitting the cache directly). However the Go race detector does not recognise SQLite's libc mutex as a happens-before edge and reports false-positive races on Fetch vs Unpin reads/writes of the per-cache state, which surfaces as DATA RACE failures for any user who registers the pool and runs their suite under -race. A sync.Mutex on the cache type is now taken on every public method (SetSize, PageCount, Fetch, Unpin, Rekey, Truncate, Destroy, Shrink), always. On the common non-shared-cache path the lock is uncontended (one atomic CAS per Lock/Unlock pair, negligible next to the SQLite work it bookends); on the shared-cache path it just rubber-stamps the order SQLite's BtShared mutex already established. A new e2e_test.go TestSharedCacheTwoConns_Integrity drives two sql.Conn against the same cache=shared URI with concurrent writers and asserts PRAGMA integrity_check = ok under -race; passes cleanly with the lock, would surface the false-positive without it. Design notes live in pcache/sharing.go.
    • See [GitLab merge request #131](https://gitlab.com/cznic/sqlite/-/merge_requests/131), thanks Ian Chechin!
    • Add a Go wrapper for sqlite3_db_status, the per-connection runtime counters (cache hit/miss/write/spill rates, schema and prepared-statement memory, lookaside usage, deferred foreign keys). DBStatus is an interface implemented by the driver connection and reached through the database/sql escape hatch (*sql.Conn).Raw(), mirroring the existing FileControl surface; DBStatusOp is a distinct typed enum of the SQLITE_DBSTATUS_* verbs so a counter from a different op family will not compile in its place. Status(op, reset) returns the (current, high) pair and optionally resets the counter. This also lets modernc.org/sqlite/pcache measure real I/O instead of the EasyRefusals proxy: the new BenchmarkPoolSpillIO reads the pager-level SQLITE_DBSTATUS_CACHE_SPILL/_CACHE_WRITE counters, which the pager maintains identically for pcache1 and the pool, making the pcache1-vs-pool comparison cznic raised on the !127 review a genuine apples-to-apples measurement. On the rotating-residue eviction-churn workload at cache_size=16 the pool spills ~3.5x more than pcache1 (cache-spill/op 31.96 vs 8.96) for ~3% more page writes (cache-write/op 450 vs 436) at identical hit/miss, quantifying the I/O cost of the strict Easy contract that EasyRefusals only proxied.
    • See [GitLab merge request #132](https://gitlab.com/cznic/sqlite/-/merge_requests/132), thanks Ian Chechin!
    • Add an opt-in _dqs DSN query parameter that disables SQLite's double-quoted string literal compatibility quirk on a per-connection basis. When _dqs=0 (or any strconv.ParseBool false value) is supplied, the driver calls sqlite3_db_config with SQLITE_DBCONFIG_DQS_DDL and SQLITE_DBCONFIG_DQS_DML set to off before any statement is prepared, so a double-quoted identifier that fails to resolve raises a parse error instead of silently falling back to a string literal. Absence of the parameter, or _dqs=1, leaves SQLite's default behavior unchanged; existing DSNs continue to work byte-for-byte. Resolves [GitLab issue #61](https://gitlab.com/cznic/sqlite/-/issues/61).
    • See [GitLab merge request #128](https://gitlab.com/cznic/sqlite/-/merge_requests/128), thanks Ian Chechin!
    • Add an opt-in _error_rc DSN query parameter for clearer error reporting on open-time failures. When _error_rc=1 (or any strconv.ParseBool true value) is supplied, error strings synthesised from a (rc, db) pair only append sqlite3_errmsg(db) when sqlite3_extended_errcode(db) is consistent with the operation rc (full match first, primary code &0xff as fallback). On mismatch the canonical sqlite3_errstr(rc) is used alone, so an open-time SQLITE_CANTOPEN no longer carries the temporary handle's stale "out of memory" errmsg. Absence of the parameter, or _error_rc=0, preserves the legacy "errstr: errmsg" form byte-for-byte; existing callers that parse error strings are unaffected. The driver's *Error.Code() returns the same SQLite result code in both modes. Parsed before sqlite3_open_v2 so open-time errors are covered. Resolves [GitLab issue #230](https://gitlab.com/cznic/sqlite/-/issues/230).
    • See [GitLab merge request #129](https://gitlab.com/cznic/sqlite/-/merge_requests/129), thanks Ian Chechin!
  • 2026-06-06 v1.52.0:

    • Upgrade to SQLite 3.53.2.
    • Add Backup.Remaining and Backup.PageCount, thin wrappers around the existing sqlite3_backup_remaining and sqlite3_backup_pagecount C symbols. Together they expose the per-Step progress counters that the underlying backup object already maintains, enabling progress reporting during online backups without dropping to modernc.org/sqlite/lib directly.
    • See [GitLab merge request #122](https://gitlab.com/cznic/sqlite/-/merge_requests/122), thanks Ian Chechin!
    • Drop the redundant second copy in (*conn).columnText, the path that backs every Rows.Scan into a Go string for a TEXT column. The value's bytes are still copied once out of SQLite-owned memory into a fresh Go buffer; that buffer is then reinterpreted as the result string with unsafe.String rather than copied a second time by the implicit string([]byte) conversion. This removes one allocation per TEXT value per row and roughly halves the bytes allocated on that path; on the new BenchmarkColumnTextScan cases it is ~13–20% faster for payloads of 256 B and larger, with no measurable change for very short strings. Purely internal: no API or behavioral change, and the returned string never aliases SQLite's buffer.
    • See [GitLab merge request #123](https://gitlab.com/cznic/sqlite/-/merge_requests/123), thanks Ian Chechin!
    • Cache each result column's declared type once per result set in newRows instead of recomputing it on every row. The TEXT branch of Rows.Next calls ColumnTypeDatabaseTypeName for every TEXT column on every row (independent of any DSN flag), which previously did a libc.GoString + strings.ToUpper each time; that lookup is now a single index into a cached, pre-uppercased []string, and ColumnTypeScanType reads the same cache and drops its per-call strings.ToLower. The declared type is fixed for the lifetime of a prepared statement, so the C round-trip is paid once per column rather than once per column per row, removing exactly 1 alloc + 8 B per TEXT column per row from the Next hot path. The new BenchmarkTextToTimeScan cases show ~7% faster on a 1000-row DATETIME SELECT under _texttotime=1. Purely internal: ColumnTypeDatabaseTypeName and ColumnTypeScanType return identical values, no API or behavioral change.
    • See [GitLab merge request #124](https://gitlab.com/cznic/sqlite/-/merge_requests/124), thanks Ian Chechin!
    • Cache, per result column, the parseTimeFormats index that first parsed a TEXT-stored DATE/DATETIME/TIMESTAMP value, and try that format first on later rows instead of re-walking the list from the top. (*conn).parseTime previously ran time.Parse down the format list on every such row; for the canonical SQLite TEXT datetime format every row paid two failed time.Parse attempts — each allocating a *time.ParseError — before the match. On a 1000-row DATETIME TEXT SELECT this cuts ~50% of allocs/op and ~57% of B/op and is ~37% faster. The fall-through chain is preserved exactly: the seven formats are mutually exclusive, so the cached hint can never select a different match than the in-order scan, and the parsed driver.Value is identical to before. Purely internal: no API or behavioral change.
    • See [GitLab merge request #125](https://gitlab.com/cznic/sqlite/-/merge_requests/125), thanks Ian Chechin!
  • 2026-05-28 v1.51.0:

    • Pool the []driver.Value slice passed to scalar/aggregate UDF callbacks and to vtab Filter/Insert/Update callbacks, eliminating the dominant per-row allocation on UDF-heavy queries. Benchmarks on a 1000-row, 3-arg noop scalar UDF show ~40% fewer bytes/op and ~15% fewer allocs/op.
    • Document the matching "arguments are not valid past return" contract on vtab.Cursor.Filter and vtab.Updater.Insert/Update, consistent with the existing rule for FunctionImpl.Scalar / AggregateFunction.Step / WindowInverse.
    • Resolves [GitLab issue #226](https://gitlab.com/cznic/sqlite/-/issues/226). See [GitLab merge request #114](https://gitlab.com/cznic/sqlite/-/merge_requests/114), thanks Ian Chechin!

... (truncated)

Commits
  • 693ff38 upgrade to SQLite 3.53.3
  • 5d24346 Merge branch 'texttotime-aggregates' into 'master'
  • 892d847 sqlite: document _texttotime empty-decltype upgrade, widen #248 comment, add ...
  • f2c8758 sqlite: _texttotime best-effort parse for empty-decltype TEXT columns (#248)
  • See full diff in compare view

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file go Pull requests that update go code labels Jul 20, 2026
remyluslosius added a commit that referenced this pull request Jul 23, 2026
… bump, sync main

Release prep for the staged-remediation branch (one-click merge once the RED
gates clear):
- VERSION 0.7.6 → 0.8.0 (frozen-api/ addition + behavior change + substantial
  capture/rollback work = the v0.8 major-position line per CLAUDE.md; founder to
  confirm the release-line number).
- CHANGELOG Unreleased: completed with the cross-file conflict-guard fix and the
  exact-audit-matching (substring false-match) fix; noted the x/text bump.
- Bumped golang.org/x/text v0.38.0 → v0.39.0 (GO-2026-5970, newly published;
  required to pass the govulncheck gate — not covered by dependabot #332).
- Merged origin/main (13 commits) to bring the branch up to date (clean).

go test ./... green, golangci-lint 0, specter 143/143, govulncheck clean.
Still founder-gated: FMA ratification + two-human rollback review + OpenWatch
`staged` mapping before its go.mod bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
remyluslosius added a commit that referenced this pull request Jul 23, 2026
Newly-published vuln in x/text v0.38.0; required to pass the govulncheck gate.
Not covered by dependabot #332 (its group still pins v0.38.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
remyluslosius added a commit that referenced this pull request Jul 23, 2026
* feat(cli): shell completion for all commands (bash, zsh, fish)

Adds `kensa completion <bash|zsh|fish>`, emitting a static completion script
that completes every subcommand and each subcommand's long flags, plus the
global flags. The CLI is pflag-based (no cobra), so this is a hand-rolled
generator driven by a single command/flag table (completionSpecs).

- completion.go: the table + bash/zsh/fish generators + the `completion`
  subcommand (wired into the runCLI dispatch + top-level --help listing).
- completion_test.go: a DRIFT-GUARD — for every command it runs `<cmd> --help`,
  extracts the advertised flags, and asserts they exactly match the table
  (fails the build if a flag is added to a command without updating the table,
  so completion can never silently omit a real flag). Also asserts every
  dispatch command is covered and each shell script names all commands+flags.

Covers all 16 commands: detect, check, remediate, rollback, recover, history,
plan, mechanisms, coverage, list, info, diff, agent, verify, migrate, version
(+ completion itself). Bash completion functionally verified (che→check,
`check --ho`→--host, `rollback --`→flags); zsh/fish scripts structurally
validated (markers + all commands/flags present).

Install: `kensa completion --help` prints per-shell source/install instructions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(deps): bump golang.org/x/text to v0.39.0 (GO-2026-5970)

Newly-published vuln in x/text v0.38.0; required to pass the govulncheck gate.
Not covered by dependabot #332 (its group still pins v0.38.0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dependabot dependabot Bot changed the title build(deps): bump the minor-and-patch group across 1 directory with 3 updates chore(deps): Bump the minor-and-patch group across 1 directory with 3 updates Jul 23, 2026
@dependabot
dependabot Bot force-pushed the dependabot/go_modules/minor-and-patch-51698a0d21 branch from 326c076 to 2c9c8a4 Compare July 23, 2026 11:01
remyluslosius added a commit that referenced this pull request Jul 23, 2026
… StatusStaged) [RED — founder-gated] (#325)

* feat(audit): stage audit_rule_set on immutable hosts instead of failing (StatusStaged)

audit_rule_set could never complete on a STIG-hardened (immutable, enabled 2)
RHEL host: the runtime load EPERMs, the engine's post-apply re-check fails, and
the transaction rolls back to nothing. Since immutable audit is itself a STIG
requirement, this blocked all 104 audit_rule_set rules on exactly the hosts
under assessment. This adds a reboot-deferred "staged" outcome.

- api: new TransactionStatus "staged" + StepResult.Staged + Staged event/StagedData
- auditnl: AuditClient.GetStatus() (adapter over libaudit) to positively detect
  enabled 2 before attempting a load; fake models Enabled/AddErr
- handler: on immutable, stage the persist layer (merge drop-in) and return
  Staged:true without loading (netlink GetStatus + shell auditctl -s)
- engine: an apply step reporting Staged terminates StatusStaged WITHOUT the
  runtime re-check and WITHOUT rollback; signed+persisted like committed;
  HostUnchanged=false; neither CommittedAt nor RolledBackAt set
- output: text + pdf remediation summaries count "staged" distinctly (not skipped)
- specs: auditnl-rule-set C-06/AC-07; engine-transaction C-14/AC-25

Rollback is unchanged and byte-perfect (persist file fully captured; immutability
guarantees runtime never diverged).

--- FAILURE-MODE ANALYSIS (AI-DRAFT, pending founder ratification per RED gate) ---
1. What could this change do wrong in production?
   - A false-positive immutable detection would stage a rule on a MUTABLE host,
     leaving it non-compliant when it could have loaded. Mitigated: positive
     GetStatus/auditctl-s confirmation of enabled==2; a status-read error falls
     through to the normal load path (which surfaces AddRule EPERM as a failed
     step), never to a false stage.
   - A staged change that the operator never reboots leaves the host
     runtime-non-compliant indefinitely. Mitigated by honest reporting: scan
     stays fail, remediation terminal status is "staged" (not committed),
     OpenWatch must render "reboot required" (boundary change tracked).
2. Is captured state sufficient to fully restore on rollback?
   - Yes. On immutable the persist file is the ONLY mutated layer and Capture
     records it byte-perfect (base64 whole-file). Runtime is frozen (enabled 2),
     so it cannot diverge between capture and rollback. /etc/audit/audit.rules is
     a derived artifact regenerated from rules.d; not independently captured.
3. What edge case is this NOT safe for, is it documented + gated?
   - Default persist file 99-kensa.rules sorts after 99-finalize.rules (-e 2) so
     a staged rule there would not load even at reboot; latent (all 104 corpus
     rules set persist_file explicitly). Tracked as standalone finding F-1.
   - Remediating -e 2 before its siblings flips immutable mid-run (F-2).
   - Live atomicity proof on a mutable-audit host still required (hosts 213/247).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): carry StepResult.Staged over the wire + make staged txns rollback-able

Two integration bugs caught by live agent-mode verification on the fleet
(offline in-process fakes could not surface either):

1. WIRE DROP: StepResult.Staged was not in the agent wire protocol
   (WireStepResult). In agent mode Apply runs on the host and its StepResult is
   serialized back to the client engine — so the agent staged the file but the
   client received Staged=false, ran the runtime re-check, and rolled back.
   Result on immutable 211: "rolled_back" instead of "staged". Fixed: add
   `bool staged = 7` to wire.proto, regenerate wire.pb.go (codegen-drift gate
   green), carry it in both APIStepResultToWire / WireStepResultToAPI, and lock
   it with a round-trip bridge test case.

2. ROLLBACK EXCLUSION: staged transactions were absent from RollbackableSessions
   (WHERE txn_committed > 0) and CommittedTxnIDs (status = 'committed'), so a
   staged change — a real on-disk file with captured pre-state — could not be
   reverted via `kensa rollback`. Fixed: both queries now admit 'staged'.

LIVE-VERIFIED (agent-mode remediate + rollback, byte-perfect sha):
- 211 (RHEL 9.6, enabled 2): stage → status=staged, file written, runtime NOT
  loaded → rollback → file ABSENT (byte-perfect).
- 213 (RHEL 9.8, enabled 1): remediate → committed, file + runtime watch loaded
  → rollback → ABSENT + unloaded (byte-perfect). Normal path unaffected.
- 203 (RHEL 8.10, enabled 2): stage → REBOOT → watch LOADED at boot before -e 2
  re-locked (convergence proven) → cleanup → clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): address adversarial-panel BLOCK — staged rollback, renderer, counters

The 5-seat adversarial panel returned BLOCK with a confirmed BLOCKER + 3
must-fixes. All addressed:

1. [BLOCKER] Staged rollback called DeleteRule for rules the staged Apply never
   loaded; an immutable kernel rejects that unload with EPERM (211 only passed
   because its kernel returns the tolerated ENOENT — a single-host false
   positive). Fix: Capture is now immutable-aware (symmetric with Apply) —
   records no kernel-unload set and marks the pre-state immutable_staged; both
   rollback paths short-circuit to a success (drop-in removed, nothing was
   loaded) instead of a false PartialRestore. Regression test uses a realistic
   fake (Enabled=2 + DeleteErr=EPERM) that fails pre-fix.

2. [must-fix] Default console renderer showed a successful staging as red FAIL
   ("0 passed, 1 failed") — the buffered text/pdf writers were updated but the
   canonical stream renderer (scan.go/stream_scan.go) was not. Fix: progress.Update
   gains Staged; the renderer shows a distinct amber STAGED row + "N staged
   (reboot required)" tally. Live-confirmed on 211.

3. [must-fix] 'staged' was classified as txn_failed at three SQL counter sites
   (sessions.go FinishSession, sqlite.go post-rollback refresh, migrate.go
   backfill) — a class-incomplete sweep of the earlier rollback-able fix. Fix:
   exclude 'staged' from the failed bucket at all three, so a staged session is
   never reported "failed: N". (Per-txn --info already shows the staged status.)

4. [minor] Frozen-api additions had no CHANGELOG entry. Added an Unreleased
   section flagging the api additions + the required OpenWatch staged mapping.

LIVE RE-VERIFIED on 211: remediate → "STAGED (reboot required)", rollback →
succeeded 1/1, file ABSENT (byte-perfect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): close panel re-run findings — ExecutePlan staged sweep + shell rollback consistency

Second adversarial-panel pass (no BLOCKER; prior BLOCKER resolved). Fixes:

1. [Sweep Auditor, CONFIRMED — "fix one path, ship the sibling"] ExecutePlan
   (internal/engine/plan.go) is the SECOND apply→terminal path (public
   api.Kensa.ExecutePlan, consumed by OpenWatch) and lacked the anyStaged
   intercept that Run gained — a staged step there failed the re-check and
   mis-reported rolled_back. Added the same intercept. Test TestExecutePlan_Staged.

2. [Refuter, PLAUSIBLE] rollbackShell used a bare `&& augenrules --load` while
   the staged apply path uses `|| true`; on a kernel where augenrules exits
   non-zero on immutable, the !res.OK() guard would fire first and report a
   byte-perfect restore as rollback_failed, making the immutableStaged success
   branch dead code. Rollback now tolerates augenrules failure for the staged
   case exactly as apply does (committed rollback keeps bare `&&` so a genuine
   reload failure still surfaces). Live re-checked on 211 (shell path): STAGED →
   rollback succeeded → ABSENT.

3. [MINOR] Documented the anyStaged whole-transaction short-circuit's
   single-mechanism assumption (no corpus rule mixes mechanisms; tighten to a
   per-step verdict before authoring a mixed rule).

Remaining is founder-gated: the frozen-api StatusStaged addition needs a minor
semver decision + coordinated OpenWatch staged mapping (flagged in CHANGELOG);
plus the FMA + two-human rollback review the Red gate requires.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): honest post-reboot staged-rollback verdict (panel pass-3 SHOULD-FIX)

Adversarial-panel pass-3 (verdict ESCALATE, no blockers) found — and live
testing on RHEL 8.10 CONFIRMED — a rollback-verdict honesty gap: rolling back a
staged audit txn AFTER a reboot reported clean success while the running kernel
still enforced the rolled-back rule.

Mechanism: rollback trusted the CAPTURE-time immutable_staged flag, which is
stale once the host reboots — auditd loads the persisted rule into the kernel
and re-locks immutable, so the rule IS live and cannot be unloaded, yet the
drop-in removal reported "no runtime rule was loaded".

Fix (verdict from LIVE state, not the stale flag):
- rollbackNetlink/rollbackShell: on a staged rollback, read the live ruleset
  (GetRules / auditctl -l) and report PartialRestore when the rule is still
  loaded (honest, mirrors the committed-immutable path); clean success only when
  genuinely absent. Never calls DeleteRule (immutable would EPERM). New helper
  auditRuleLoadedShell for the shell path.
- cmd/kensa rollback --start: counted only errors, so a PartialRestore printed
  "succeeded". Now counts+surfaces a distinct "partial: N (disk reverted;
  running state clears on reboot)" line + per-txn warning. (Pre-existing gap for
  all handlers, surfaced by the staged path; RollbackResult.PartialRestore is
  carried over the wire already.)

Regression test TestRollback_Netlink_StagedPostRebootReportsPartial (rule loaded
at rollback → PartialRestore).

LIVE-PROVEN on 203 (RHEL 8.10): stage → reboot (rule loads) → rollback now
reports "succeeded: 0, partial: 1" + warning; disk ABSENT (byte-perfect); final
reboot leaves the host clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): shell staged-rollback matcher handles syscall rules (panel pass-4)

Panel pass-4 SHOULD-FIX: auditRuleLoadedShell did a whitespace-normalised
full-line compare against `auditctl -l` with a comment claiming rules "print
verbatim" — true for WATCH rules but FALSE for SYSCALL rules, which auditctl -l
reorders/normalises (-k → -F key=, fields reordered). So a staged syscall rule
loaded post-reboot was missed → rollback over-reported a clean restore instead
of PartialRestore. The default agent/netlink path was already correct
(containsWire against GetRules); this was shell-transport-only.

Fix: export internal/check.AuditLineLoaded (the exact matcher the
audit_rule_exists check uses — handles watch verbatim, syscall field-matching,
-k/-F key= equivalence, syscall-set reordering, auid normalisation) and
delegate to it from auditRuleLoadedShell. Regression test
TestRollback_Shell_StagedSyscallPostRebootReportsPartial (canonical -k rule vs
normalised -F key= in auditctl -l).

LIVE-PROVEN on 203 (RHEL 8.10) with the SHIPPING binary (sha 616fc820): stage a
SYSCALL rule via the shell path → reboot (loads as `-F key=`) → rollback reports
"succeeded: 0, partial: 1" + warning; disk ABSENT byte-perfect; host left clean.

NOTE: the first syscall live attempt reported a false "succeeded" — it ran a
STALE ./bin/kensa (I did not `make build` after the fix). Rebuilding fixed it.
The repo's stale-binary-live-test hazard recurred; always rebuild before live.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(cli): surface staged count in rollback --list/--info (founder-caught)

`rollback --info` showed "committed: 0 / 1 total" for a staged-only session with
no staged line, so it read as "nothing to roll back" — even though rollback
--start DOES process staged transactions (CommittedTxnIDs matches status IN
('committed','staged')). The query was staged-aware; the display was not.

- rollback --list: new `staged` column
- rollback --info: new `staged: N (reboot-pending; rollback-able)` line
- JSON: listSessionRow gains txn_staged
staged is derived (total - committed - rolled - failed; the only status excluded
from all counters). Live-verified on 203: staged session shows "committed 0,
staged 1"; rollback --start reports "attempted: 1".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): detect cross-file rule conflicts before writing (don't break the audit load)

Root-caused on a live host: audit_rule_set's mergeRuleLines dedups only WITHIN
the target drop-in, so remediating a rule whose audit action is already audited
elsewhere (e.g. the audit-file-shadow `-k identity` watch on a host that already
watches /etc/shadow under `-k audit_rules_usergroup_modification`, or the
perm_mod syscall rule under the same key in a second file) silently wrote a
second drop-in. The kernel dedups on the ACTION (watch path+perms; syscall
signature), NOT the key, so both load → "Rule exists" → auditctl aborts the
ruleset load → `-e 2` (last) never applies → the host boots MUTABLE. This
actually dropped host 192.168.1.211 out of immutable state (confirmed + fixed).

Fix — detect the conflict and gate, don't silently apply (the operator owns the
resolution, per the ownership boundary):
- check.AuditActionLoaded: key-AGNOSTIC matcher (watch path+perms; syscall
  fields) — "is this action already loaded under any key?" — distinct from the
  key-strict AuditLineLoaded used by the check itself.
- audit_rule_set.guardConflict: before writing, if a rule line NEW to this
  drop-in has its action already in the live ruleset (`auditctl -l`), refuse
  with Success=false and a detail naming the existing rule; do NOT write the
  duplicate. Idempotent re-apply of the rule's own drop-in is not a conflict;
  an unreadable live ruleset never blocks a legitimate apply (best-effort).
- scan.go: surface a handler's refusal detail in the remediate row (was bare
  "rolled_back").
- spec auditnl-rule-set C-07/AC-08; matcher + handler + absent-conflict tests.

NOTE: this is NOT the corpus-level `--allow-conflicts` (which gates declared
conflicts_with between rules at resolution time) — it is a new runtime
state-conflict guard in the handler.

LIVE-PROVEN on 211 (RHEL 9.6, immutable, /etc/shadow already watched): remediate
audit-file-shadow now prints "conflict — action already audited as ...; would
duplicate it and abort the audit load at reboot. Not applied", writes NO
50-identity.rules, and the host stays enabled 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): conflict guard scans rules.d files, not just the live ruleset (panel pass-5)

Panel pass-5 SHOULD-FIX (same-session gap): guardConflict read only `auditctl -l`
(the LIVE ruleset). On an immutable host, two staged rules writing the same audit
action to different drop-ins in ONE remediate run both pass the guard (neither is
loaded yet) and both load at the next reboot — augenrules "Rule exists" → the very
immutability drop the guard exists to prevent.

Fix: scan the on-disk rules.d FILES (what augenrules actually loads at reboot),
not the live ruleset. This closes the same-session gap AND is strictly more
robust — it also catches a pre-existing duplicate that is not currently loaded
(e.g. on a host whose load already aborted). grep exit 1 (no rules) is not an
error; a non-root/failed read still degrades to no-scan (best-effort, never
blocks a legitimate apply).

LIVE-PROVEN on 211 (RHEL 9.6, immutable): remediate audit-file-shadow still
refuses with the naming conflict detail (now matched from
audit_rules_usergroup_modification.rules on disk), writes no 50-identity.rules,
host stays enabled 2.

Panel note on the class-wide behavior change (audit_rule_set now refuses a rule
whose action is already audited under a different key): the kernel-dedups-on-
action-not-key premise is confirmed by the live 211 boot log ("Rule exists" for
two different-key /etc/shadow watches). Refuse-over-brick is the intended, safer
behavior; ratifying it (and confirming no in-corpus fleet host is newly reported
unremediable) is a founder call, tracked on the PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(check): exact audit field/key matching — stop path/auid/key prefix false-matches (cross-session panel BLOCKER)

A concurrent session's adversarial panel found (empirically reproduced) that the
audit matchers used strings.Contains (substring), not field equality. Effect,
confirmed by test AND live on 211:

- AuditLineLoaded (the COMPLIANCE CHECK): "-F path=/usr/bin/su" substring-matched
  a loaded "-F path=/usr/bin/sudo" (su ⊂ sudo) → the su control FALSE-PASSES on a
  host that only audits sudo. A false-COMPLIANT in the check itself — worse than
  the note framed, and the cardinal sin for a compliance tool.
- AuditActionLoaded (the new conflict guard): same substring → falsely refuses a
  legitimate distinct rule (su) as "already audited" because sudo is loaded →
  su permanently unremediable. Generalizes to any path that is a prefix of
  another (also -k key prefixes and auid>=1000 ⊂ auid>=10000).

Fix: exact field-token matching. arch/path/perm/exit compared via auditFieldEqual
(whole "-F field=value" token, not substring); the -k key via extractAuditKey
(now also parses the auditctl -l "key=" form) compared for equality; auid tokens
via auditFieldPresent (whole whitespace-delimited field). This also corrects the
pre-existing check's false-PASS, not just the new guard.

Regression: TestAuditMatch_NoPrefixPathFalseMatch (su≠sudo for both matchers;
exact su still matches; auid>=1000 ≠ auid>=10000). LIVE-VERIFIED on 211: a check
for the non-audited /usr/bin/sud (prefix of audited /usr/bin/sudo) now correctly
FAILs "rule line not loaded" (pre-fix it false-PASSed). go test ./... green,
lint 0, specter 143.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): prep #325 — VERSION 0.8.0, CHANGELOG, x/text security bump, sync main

Release prep for the staged-remediation branch (one-click merge once the RED
gates clear):
- VERSION 0.7.6 → 0.8.0 (frozen-api/ addition + behavior change + substantial
  capture/rollback work = the v0.8 major-position line per CLAUDE.md; founder to
  confirm the release-line number).
- CHANGELOG Unreleased: completed with the cross-file conflict-guard fix and the
  exact-audit-matching (substring false-match) fix; noted the x/text bump.
- Bumped golang.org/x/text v0.38.0 → v0.39.0 (GO-2026-5970, newly published;
  required to pass the govulncheck gate — not covered by dependabot #332).
- Merged origin/main (13 commits) to bring the branch up to date (clean).

go test ./... green, golangci-lint 0, specter 143/143, govulncheck clean.
Still founder-gated: FMA ratification + two-human rollback review + OpenWatch
`staged` mapping before its go.mod bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… updates

Bumps the minor-and-patch group with 3 updates in the / directory: [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [modernc.org/sqlite](https://gitlab.com/cznic/sqlite).


Updates `golang.org/x/sys` from 0.46.0 to 0.47.0
- [Commits](golang/sys@v0.46.0...v0.47.0)

Updates `golang.org/x/term` from 0.44.0 to 0.45.0
- [Commits](golang/term@v0.44.0...v0.45.0)

Updates `modernc.org/sqlite` from 1.53.0 to 1.54.0
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.53.0...v1.54.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: golang.org/x/term
  dependency-version: 0.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: modernc.org/sqlite
  dependency-version: 1.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot changed the title chore(deps): Bump the minor-and-patch group across 1 directory with 3 updates chore(deps): bump the minor-and-patch group across 1 directory with 3 updates Jul 27, 2026
@dependabot
dependabot Bot force-pushed the dependabot/go_modules/minor-and-patch-51698a0d21 branch from 2c9c8a4 to 2a7f32c Compare July 27, 2026 09:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants