fix(db,agent,ui): a Redis ACL user was dropped, a right answer was scored wrong, and five more - #502
Open
cevheri wants to merge 1 commit into
Open
fix(db,agent,ui): a Redis ACL user was dropped, a right answer was scored wrong, and five more#502cevheri wants to merge 1 commit into
cevheri wants to merge 1 commit into
Conversation
…ored wrong, and five more Round 13 of the BACKLOG sweep. Seven entries closed, each measured on both arms with a control, and five new findings recorded with their measurements. D29 - Redis authenticated as a principal the user did not choose. `RedisProvider.connect()` built its ioredis options without `username`, so every Redis 6 ACL user was silently replaced by `default`. The form collects the value and the connection-string parser fills it from `redis://user:pw@host`; only the one place that matters ignored it. Measured against `redis:latest` with `default` left `nopass` and `probe` defined `on >pw ~* +@ALL -info`: before, `ACL WHOAMI` answered `default` and `INFO` succeeded, so a restricted principal reported full health; after, WHOAMI answers `probe` and the refused `INFO` reaches the connection test as a degraded (amber) result rather than a green tick. Two controls make it non-vacuous: `probe` with a wrong password now fails, and no-username with a wrong password still succeeds because `default` is `nopass`. No new machinery was needed for the degraded half - the amber path already existed and now gets fed the truth. B45 - an optimization run that produced the right index was told it had not answered. The verifier composed on the investigation baseline, whose emptiness census reads `rowCount`, and a plan artifact legitimately carries `rowCount: 0` because a plan is a description arriving in one column. So the product contradicted its own correct recommendation in its own voice. The census now takes a caller-supplied exemption set and only the optimization rule passes one - `agent-investigation.1` and the two verifiers composing on it are untouched. Because the rule changed its mind about released runs, the id moves `agent-query-optimization.2` -> `.3`. An empty bounded read cited by an optimization report still ends the run `empty-evidence`. B54 - a refused grounding capture left the ledger silent, in exactly the case an operator has to diagnose. Both refusal sites now record `context-unavailable` carrying the reason code, the capture's own sentence, and - where the row budget refused the read - rows projected against rows allowed. Its own event kind rather than a `context-captured` carrying an absence: a refused capture has no honest fingerprint or table count, and three readers take `context-captured` as proof an inventory exists. The two numbers still travel as prose inside the provider's refusal message; that is pinned by a test driving a real over-budget `queryReadOnly` through `bun:sqlite` and asserting the parse against the error the provider itself threw, so a reword goes red instead of going quiet (residual recorded as B73). B37 - a seed config the server could not read disabled the agent everywhere and blamed the connection, saying "its settings live in this browser" of connections this application seeds itself. The fault was a joint: `servedSeeds` was an array, and an array cannot say "I do not have this", so a failed load and an empty list were the same value. The endpoint now attributes its own failure (`reason: "seed-config-unreadable"`), the browser holds a load state, and the rail says which absence it is. Verified in Chrome against a real malformed seed file: the new copy renders with `data-tone="warning"` and no browser-settings claim, and is absent on a healthy config. B25 - SQLite stores no DDL for the index it builds to enforce a `UNIQUE` constraint, so the composed index read could not see it and a covered foreign key was reported as unindexed. The capture now reads those out of the table's own `CREATE TABLE` and lists them as `(unique constraint)` - plain words, because a named `CONSTRAINT` does not lend the index its name (measured) and inventing one would put an identifier in the inventory that does not exist. Coverage stays a prefix test: `UNIQUE (note, parent_id)` does not cover `parent_id`. `PRIMARY KEY` is not synthesized - an `INTEGER PRIMARY KEY` is the rowid with no index behind it. Note this changes the inventory fingerprint for any SQLite database with a UNIQUE constraint, and raises the per-table index count for those tables. H10 - the route-auth allowlist asserted that its entries reach no provider and never checked it, so an allowlisted route that later grew a provider call would escape the session sweep forever. It now reads each allowlisted route's source and matches the provider entry points by module-specifier prefix, including the indirect `@/lib/api/schema-route` helper that two routes reach a provider through without naming `@/lib/db` at all. Zero real violations today. D28 - closed by writing the decision down where it is made: the scheme-versus-parameter TLS asymmetry is now a documented decision on `withSSLMode` rather than an artefact. Also: `buildConnectionPayload`'s managed arm - a credential boundary - had no direct test and was covered only incidentally through hooks; it is now pinned with the assertion that no credential appears in a managed request body. New entries, each with its measurement: B72 (three verifiers still judge a plan-only report by the emptiness census), B73 (the row-budget pair travels as prose), B74 (a `COLLATE` unique constraint is reported as covering a key it cannot serve), T4 (eight backlog ids cited in `src/` name entries that no longer exist - pre-existing on main, and the drift guard covers only `docs/AGENT.md`), T5 (the allowlist check reads one level deep). D14's "Done when" was corrected: dropping `bufferPoolUsage` is no longer an option, because four providers fill it. Gates, all run locally: format, lint (0 errors), typecheck, knip, 351/351 core files, 33/33 component groups, 197 evals, build, build:lib, attw, coverage 43452/43452 lines (100.00%).
|
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.



Round 13 of the
docs/BACKLOG.mdsweep. Seven entries closed, five new findings recorded.Every fix was measured on both arms with a control arm, so no assertion here is vacuous.
Ordered by cost/value, cheapest-and-highest first.
What is fixed
D29 - Redis authenticated as a principal the user did not choose (security)
RedisProvider.connect()built its ioredis options withhost,port,password,dbandno
username, so every Redis 6 ACL user was silently replaced bydefault. The connectionform collects the value and
connection-string-parser.tsfills it fromredis://user:pw@host-only the one place that matters ignored it. Every managed Redis (Redis Cloud, ElastiCache RBAC,
Azure Cache) issues least-privilege credentials as ACL users, so this is not exotic.
Measured against
redis:latestwithdefaultlefton nopass ~* &* +@allandprobedefinedon >probepw ~* +@all -info, four arms through the running product:ACL WHOAMI=default,INFOsucceeds, greenuser: probe(after)probe,INFOrefusedNOPERM, degraded (amber)probe+ wrong passworddefaultisnopass, any password worksThe second half of the entry's Done-when needed no new machinery:
getHealth()already throws,POST /api/db/test-connectionalready separates connect from health, and the amberdata-tonebanner already existed. It was being fed a lie; now it is fed the truth.
Provider tri-sync honoured: code,
docs/providers/redis.md(new ACL section with themeasurement and the INFO-derived surfaces a restricted user loses), and the integration tests
move together.
B45 - a run that produced the right index was told it had not answered
The optimization verifier composed on the investigation baseline, whose emptiness census reads
rowCount. A plan artifact legitimately carriesrowCount: 0- a plan is a description of astatement, arriving in one column - so runs that compared real costs and recommended the correct
index were scored
unanswered / empty-evidence. The product contradicted its own good answer,in its own voice, at the end of the run.
The census now takes a caller-supplied exemption set, and only the optimization rule passes
one.
agent-investigation.1and the two verifiers composing on it are deliberately untouchedand pinned by a test, so the boundary is stated rather than implicit. Because the rule changed
its mind about runs already scored on
main, the id movesagent-query-optimization.2->.3.The exemption is one artifact kind and nothing more: an empty bounded read cited by an
optimization report still ends the run
empty-evidence, because zero rows from a read is theanswer that read gave.
B54 - a refused grounding capture left the ledger silent
The repo's own rule is that the ledger is the authority on what a run did. That held only for
captures that succeeded: the refusal branch pushed a sentence into the model's prompt and
returned, so an ungrounded run's ledger had nothing between the drive starting and the run
ending - in exactly the case an operator has to diagnose.
Both refusal sites (found by grepping the mechanism, not the entry) now record
context-unavailablecarrying the reason code, the capture's own sentence, and - where the rowbudget refused the read - rows projected against rows allowed.
Its own event kind rather than a
context-capturedcarrying an absence, for two reasons thatpoint the same way: a refused capture has no honest
fingerprintortableCount(andtableCount: 0would assert the database has no tables), and three readers treatcontext-capturedas proof an inventory exists.The two numbers still travel as prose inside the provider's refusal message. That is pinned by a
test that drives a real over-budget
queryReadOnlythroughbun:sqliteand asserts the parseagainst the error the provider itself threw - so a reword goes red in a test instead of going
quiet in production. Residual recorded as B73 with its blast radius measured.
B37 - a server config fault disabled the agent and blamed the connection
A
seed-connections.yamlthe server could not read madeGET /api/connections/managedthrow, sothe browser held an empty
servedSeedsand the rail said "Sample (Employees) cannot be rebuilton the server: its settings live in this browser" - of a connection this application seeds
itself. False twice over, and it pointed the operator at the wrong file.
The fault was a joint, not a component:
servedSeedswas an array, and an array cannot say "I donot have this", so a failed load and a legitimately empty list were the same value. Fixed at the
three places that lose the distinction - the endpoint attributes its own failure
(
reason: "seed-config-unreadable"), the browser holds a load state, and the rail reads thereason rather than guessing it.
Verified in Chrome against a real malformed seed file:
data-tone="warning", no browser-settings claim, Start disabled. Control: the element is absenton a healthy config, so the new copy appears only when the server actually failed.
B25 - a covered foreign key was reported as unindexed
SQLite stores no DDL for the index it builds to enforce a
UNIQUEconstraint, so the composedindex read (
sql IS NOT NULL) could not see it andfk_unindexedfired on a covered key - afalse finding presented to the user as a fact.
The capture now reads those out of the table's own
CREATE TABLEand lists them as(unique constraint). Plain words rather than the engine'ssqlite_autoindex_<table>_<n>,because that name is not derivable: a named
CONSTRAINT uq_x UNIQUE (...)does not lend theindex its name (measured), and inventing one would put an identifier in the inventory that may
not exist in the database.
Coverage stays a prefix test -
UNIQUE (note, parent_id)does not coverparent_id, and thecontrol fixture asserts the finding still fires there.
PRIMARY KEYis not synthesized: anINTEGER PRIMARY KEYis the rowid, with no index behind it at all.PRAGMA index_listdoes list auto-indexes, and was measured to confirm it - but the M1 statementguard refuses any word beginning
PRAGMA_, so it is unreachable on the agent path. The DDL isthe only source.
Two side effects worth a release note: this changes the inventory fingerprint for any SQLite
database with a
UNIQUEconstraint (a run resumed across this deploy sees a moved descriptor),and raises the per-table index count for those tables.
H10 - a security gate asserted something it never checked
ROUTES_WITHOUT_A_PROVIDERclaimed its entries reach no database or LLM provider, and the testonly verified that each key named a real route. An allowlisted route that later grew a provider
call would have escaped the session sweep forever, silently.
It now reads each allowlisted route's own source and matches the provider entry points by
module-specifier prefix (so a new factory export is covered the day it lands), including the
indirect
@/lib/api/schema-routehelper thatdb/schema/listanddb/schema/relationsreach aprovider through without naming
@/lib/dbat all. Proven non-vacuous by temporarilyallowlisting
db/queryand watching it fail with the expected message.Zero real violations on the allowlist today. The one entry that does reach a provider
(
agent/drive, the durable transport's callback) is documented as such, and a second assertionkeeps that exemption from quietly becoming a new unchecked allowlist.
D28 - closed by writing the decision down
The scheme-versus-parameter TLS asymmetry now reads as a decision on
withSSLModerather than anartefact: a secure scheme is the only spelling a self-hosted deployment has, so a verifying
default there would refuse the ordinary
--tls-portinstall.Also
buildConnectionPayload's managed arm is a credential boundary - a seed travels as a bareseed:<id>reference so nothing about how to authenticate leaves the server's config - and ithad no direct test, its coverage arriving incidentally through other hooks. Now pinned, including
the assertion that no credential appears in a managed request body.
New backlog entries (with measurements)
plan-only report by the emptiness census. Reachable (
inspect_planis offered to all three);closing it changes what three released verifier ids mean.
consumers. Doing it properly touches the error type every provider throws (~40 call sites).
UNIQUE (a COLLATE NOCASE)is reported as covering a key it cannot serve for abinary lookup. A false negative in the direction opposite the one B25 fixed.
src/name entries that no longer exist (B7, B8, B17,B18, B24, B27, B43 in 8 files, B47 in 3). Pre-existing on
main; the drift guard enforces thetwo-way invariant only for
docs/AGENT.md, so source citations rot unnoticed. Not mass-fixedhere on purpose: each needs its claim re-verified, and the two possibilities are opposite
(deleted because fixed, or deleted by mistake).
new indirect helper would still pass.
D14's Done-when was corrected: droppingbufferPoolUsageis no longer an option, becauseMySQL, MongoDB, Couchbase and ClickHouse all fill it with a real figure.
Verification
All gates run locally, one command each:
formatlinttypecheckknipbuildbuild:lib+attwcoverage:checkWhat was NOT verified
Stated plainly rather than implied:
both arms, and B54's timeline copy ("Schema not captured") has unit coverage, but neither was
seen rendered from a real run.
returns
degraded: truewith the server's own NOPERM sentence, and the hook-to-banner chain iscovered by pre-existing tests; the rendered banner itself was not re-driven in the browser.
docs/providers/redis.mdstill carries staleredis.ts:<line>anchors, now ~17 lines furtherout of date below
connect(). The class is already tracked as DOC1; not partially correctedhere, because a half-fixed anchor looks authoritative while still being wrong.
Not merged - for review.