Skip to content

fix: DeleteNode cascade, go.mod build, DSN pragmas, chunking, backup visibility, key policy - #60

Merged
Patel230 merged 8 commits into
mainfrom
fix/audit-sweep-2026-08
Aug 16, 2026
Merged

fix: DeleteNode cascade, go.mod build, DSN pragmas, chunking, backup visibility, key policy#60
Patel230 merged 8 commits into
mainfrom
fix/audit-sweep-2026-08

Conversation

@Patel230

Copy link
Copy Markdown
Contributor

Summary

Deep code-quality audit fixes for yaad:

  • DeleteNode FK violationdeleteNodeQ now removes node_signatures, file_watch, embeddings, node_versions, node_metadata child rows (the FK-without-CASCADE path caused FOREIGN KEY constraint failed silently — regression test included). Error-swallowing callers in decay/sparsify/improve now log failures instead of reporting removed: 0
  • go.mod standalone build — pinned hawk-mcpkit v0.1.5 (was v0.0.0 — tag didn't exist, breaking go get github.com/GrayCodeAI/yaad)
  • Per-connection pragmas — moved synchronous/cache_size/temp_store/mmap_size/recursive_triggers into the DSN _pragma= list so all 5 pooled connections enforce them (not just the first)
  • Batch chunkingLoadNodeMetadata and CountEdgesBatch now chunk at maxSQLVariables=900
  • Backup scheduler — failures now visible via slog + BackupScheduler.Status(); fsync on temp file before rename
  • Encryption key policyEnvKeyProvider requires ≥32 bytes raw or exact 32 bytes decoded from base64/hex

Upgrade note: users with short YAAD_ENCRYPTION_KEY values (<32 bytes) will need to set a compliant key; existing rows encrypted under short keys can be recovered via a custom provider with the old material.

Test plan

  • go build ./... (GOWORK=off)
  • go test ./... — 33 packages, 0 fail
  • Hawk workspace go build ./... clean
  • Regression test TestDeleteNodeRemovesChildren — fails on old code, passes now

…swallowing delete errors

deleteNodeQ only deleted edges + nodes. The schema declares child tables
(embeddings, file_watch, node_signatures) without ON DELETE CASCADE, so with
foreign_keys(ON) the nodes delete failed whenever child rows existed, and
node_versions/node_metadata rows leaked as orphans. deleteNodeQ now deletes
every child table explicitly (leaf tables first) in the same transaction.

Engine passes that call DeleteNode (GarbageCollect, Sparsifier passes,
consolidateDuplicates, LLM consolidator) silently discarded errors and
mis-counted removed nodes; they now log failures via log/slog and count
only successful deletions.
…tandalone

go.mod required hawk-mcpkit v0.0.0, a version that does not exist on any
proxy and was only satisfiable through the monorepo replace directive, so
consuming yaad as a dependency module failed to resolve. Require v0.1.5
(the newest tag that ships ServeHTTPWithShutdown, which yaad uses), record
its go.sum hashes, and keep the replace for local development.

Also drop the ServeSSE/ServeSSEWithShutdown wrappers in internal/server:
they called mcpkit APIs added after v0.1.5 and had no callers in yaad,
which would have kept the pinned version from compiling standalone.
…is configured

synchronous, wal_autocheckpoint, temp_store, mmap_size, cache_size, and
recursive_triggers are per-connection settings, but they were issued once
through db.ExecContext, so only the pooled connection that served that
call was configured; the other four connections ran on SQLite defaults.
Move them into the _pragma= DSN list, which the modernc.org/sqlite driver
applies on every new connection. Drop page_size (a no-op once the
database file exists) and move PRAGMA optimize from startup to Close(),
where SQLite recommends running it. Regression test pins all five pooled
connections with open transactions and asserts each PRAGMA value.
…iables

LoadNodeMetadata (and FillNodeMetadata on top of it) and CountEdgesBatch
built a single IN (...) with every requested ID, ignoring the
maxSQLVariables (900) host-parameter budget that GetNodesBatch,
GetAllEdgesFor, and GetEdgesBetween already chunk at, so large ID sets
could exceed SQLite's per-statement parameter limit. Both helpers now
query in maxSQLVariables-sized chunks and merge per-chunk results; the
misleading chunkedArgs helper is gone. Regression tests cover 950 IDs
(two chunks) for both paths, with edges spanning the chunk boundary.
The backup scheduler discarded every RunNow error (_ = b.RunNow), so a
failing backup pipeline was indistinguishable from a healthy one. The
run loop now logs failures via log/slog and every RunNow outcome is
recorded in the new BackupScheduler.Status() accessor (last success /
error timestamps plus snapshot and failure counters).

RotateBackups ignored all os.Remove errors; unexpected ones are now
logged while a vanished file stays a non-event.

Store.Backup now fsyncs the snapshot temp file before renaming it into
place and best-effort fsyncs the backup directory afterwards, so a crash
cannot leave a visible but not-yet-durable backup.
EnvKeyProvider.KeyBytes used the raw environment value as master key
material no matter its length, so a short passphrase became an AES-256
key whose entropy was only that of the passphrase. Accept a raw string
of at least 32 bytes, or a base64/hex value decoding to exactly 32 bytes
(raw wins when a value qualifies both ways); anything else is rejected
with an error naming the env var.

Upgrade note: existing deployments with a shorter key are now rejected
(hawk disables the yaad memory bridge with a logged warning rather than
falling back to plaintext), and rows encrypted under a rejected short key
cannot be decrypted by EnvKeyProvider anymore.
…one lock

AcquireProcessLock keyed its in-process lock registry by the raw
dbPath + ".lock" string, so lexically equivalent paths (dir/db,
dir/./db, dir/x/../db) each opened the same lock file separately; flock
treats separate open file descriptions as separate holders, making the
second store open fail with a bogus 'database locked by another yaad
process'. Normalize the lock path with filepath.Abs before the registry
lookup.
- go.sum: drop hawk-mcpkit v0.1.5 hash lines — matches go mod tidy
  output in CI (the local replace makes them unnecessary)
- storage/crypto.go: gofumpt reformat (CI fmt gate)
- storage/backup.go: explicitly discard syncDir error (errcheck; the
  best-effort rationale is documented above the call)
@Patel230
Patel230 merged commit 42bdda9 into main Aug 16, 2026
12 checks passed
@Patel230
Patel230 deleted the fix/audit-sweep-2026-08 branch August 17, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant