fix(agent): read the sqlite kind vocabulary from the provider, not source text - #986
Conversation
…urce text
The guard that catches a SQLite object kind declared but left unmapped read
`SQLITE_OBJECT_KINDS` out of `sqlite.ts` with a `{ id: "..."` scrape that matches
a single-line entry only. A fifth kind written across two lines was therefore
absent from the guard's population AND from `COMPOSED_KIND_WORDS`, both sides
shrank together, and the guard passed over a kind nothing maps.
Measured against the same declaration, using the same pattern:
today, unchanged scrape sees 4 -> guard passes
a FIFTH kind wrapped scrape sees 4 -> guard passes
the same FIFTH kind on one line scrape sees 5 -> guard fails on it
The guard now asks the constructed provider for `getCapabilities().objectKinds`,
the durable fix the array's own comment named. Capabilities are type-driven and
read off a provider that is never connected, so no socket is opened.
An entry that wraps can no longer hide a kind, which is what the new test pins,
alongside the unmapped-kind failure itself. The one-line-entries constraint and
its rationale leave the comment in `sqlite.ts`, since nothing reads that array as
text any more.
cevheri
left a comment
There was a problem hiding this comment.
Thanks for this, and for the measurement table in the description. The core fix is right, and I verified it rather than took it on trust: with a fifth kind wrapped across two lines in SQLITE_OBJECT_KINDS, the provider-sourced guard fails where the old scrape passed (146 pass / 1 fail, and the failing test is the guard). The comment rewrite in sqlite.ts is accurate too, and the connectionless claim holds since SQLiteProvider loads its driver in connect(), not the constructor.
One change before merge, on the second test.
I ran that same mutation against it, and a kind declared but left unmapped is caught, and the declaration's formatting cannot hide it passes while the exact defect is present. Its population is Object.keys(composedMap("sqlite")) plus a literal "fifth_kind", so it never reaches the declaration or the provider, and what it ends up asserting is that filter filters. unmappedKindIds has no other caller either, so the docblock calling it "the guard's comparison" does not quite hold.
Could you replace it with a control plus a mutant over the real inputs, and drop the helper?
test("a fifth declared kind that nothing maps fails the guard, however its entry is laid out", async () => {
const composed = composedMap("sqlite");
const declaredIds = await declaredSqliteKindIds();
// Control: the real declaration agrees, so the mutant below is the only difference.
expect(composed).toEqual(Object.fromEntries(declaredIds.map((id) => [id, id])));
// Mutant: one more declared kind, and the SAME comparison must stop holding.
expect(composed).not.toEqual(Object.fromEntries([...declaredIds, "fifth_kind"].map((id) => [id, id])));
});One small thing alongside it: the describe docblock still says "Source-level, because the agent side must not import a provider module", which no longer describes the sqlite arm now that it goes through the factory.
Everything else looks good to me, happy to approve once that lands.
|
Thanks for re-running the mutation. Both of those are right: that test's population never reached the declaration or the provider, so it was checking the filter, not the guard. Replacing it with your control plus the mutant over |
…nd guard Review feedback: the previous test's population was built from composedMap plus a literal, so it never reached the declaration or the provider and only asserted the filter. Replaced with the control plus mutant over declaredSqliteKindIds() and the real composed map, so the comparison itself is what stops holding when a fifth kind appears. unmappedKindIds is dropped with it, and the describe docblock no longer claims a source-level read for the sqlite arm.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
cevheri
left a comment
There was a problem hiding this comment.
Both changes landed as described, and I re-ran the mutation against this head rather than reading the diff: with a fifth_kind entry wrapped across two lines in SQLITE_OBJECT_KINDS, the file goes from 147 pass to 145 pass / 2 fail, and the two failures are the identity guard and the new control-plus-mutant test. Clean head is 147 pass. The helper is gone and the describe docblock now scopes the source-level remark to the postgres arm.
Thanks for the quick turnaround.
Closes #981
What was wrong
The guard read
SQLITE_OBJECT_KINDSout ofsqlite.tswith/\{ id: "(\w+)"/g, which matches a single-line entry only. Measured against the same declaration, using the same pattern:fifth_kindA fifth kind written across two lines is invisible to the scrape, and
COMPOSED_KIND_WORDSnever gains it either. Both sides shrink together, and the guard passes over the exact defect it exists to catch.What changed
tests/unit/lib/agent/context-snapshot.test.tsThe guard asks the constructed provider instead of the file's text:
Capabilities are type-driven, so the provider is never connected. The connection carries
databaseonly becauseSQLiteProvidervalidates its config in the constructor and wants a file path;:memory:satisfies that without naming one.Two tests, because the failure has two halves:
src/lib/db/providers/sql/sqlite.tsThe comment that kept the entries on one line is rewritten. It still carries what it did (why
SOURCE_SQLis one constant, and the #789 Phase 2 fact about the definition text) and now states what protects the vocabulary rather than what constrains the formatting.Testing
bun run testover the whole tree is red here, on files this change does not touch. I compared those files against a clean checkout ofmainbefore opening this, and the counts are identical on both trees:maintests/integration/db/sqlite-provider.test.tstests/isolated/object-source-declarations.test.tstests/unit/launcher-utils.test.tstests/unit/test-runner-cli.test.tstests/unit/db/sqlite-driver.test.tstests/isolated/monaco-language-ids.test.tstests/isolated/object-edit-declarations.test.ts(4),tests/isolated/agent-investigation-e2e.test.ts(2),tests/security/agent-statement-boundary.test.ts(1) andtests/integration/db/mongodb-provider.test.tsare also red on this machine. I did not compare those four against a clean tree, so I am not claiming they are unrelated, only that I did not introduce anything the six above did not already show. CI is the authority on them.Coverage should be unchanged: the only
src/edit is a comment, and the new executable lines are in the test file.