fix: make CatalogStore required on FileSystemUnifiedDataRepository#1157
fix: make CatalogStore required on FileSystemUnifiedDataRepository#1157
Conversation
The catalog SQLite index was an optional constructor parameter, which meant repository instances created without it silently skipped write-through catalog updates. In particular, executeModelMethod in WorkflowExecutionService created a data repo without the catalog, so data written by workflow steps was never indexed — causing data.latest() to return null and produce "No such key: attributes" errors in subsequent steps. Making CatalogStore required turns this class of bug into a compile error. Every FileSystemUnifiedDataRepository now maintains write-through catalog consistency. A createCatalogStore() factory helper centralizes construction. Also makes queryData required on ModelMethodRunDeps and catalogStore required on WorkflowRunDeps, removes redundant optional chaining across CLI/serve code, and updates design/data-query.md to reflect the new semantics. Fixes swamp-club#39 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
None.
Verdict
PASS — This PR is a pure internal refactoring. All CLI command changes are limited to removing ?. optional chaining on catalogStore (now a required field) and simplifying DataQueryService construction in model_method_run.ts. No user-visible output, error messages, flags, help text, or JSON schema are affected.
There was a problem hiding this comment.
Code Review
This is a well-executed bug fix that makes CatalogStore a required dependency at the type level, turning the runtime bug (swamp-club#39) into a compile-time error. The approach is sound: enforce the invariant in the type system rather than relying on callers to remember the optional parameter.
Blocking Issues
None.
Suggestions
-
Temp dir leak in
run_test.ts:createTestDeps()insrc/libswamp/workflows/run_test.ts(lines 174-175) creates temp directories withDeno.makeTempDirSync()but never cleans them up. This is a pre-existing pattern so not a regression, but worth noting for a future cleanup pass. -
DDD alignment: Good use of the factory pattern with
createCatalogStore()to centralize catalog construction. MakingCatalogStorerequired onFileSystemUnifiedDataRepositorycorrectly enforces the aggregate invariant that every data mutation must maintain catalog consistency — this is the right place to enforce it.
Verification
- ✅ No libswamp import boundary violations — CLI/presentation code imports only from
src/libswamp/mod.ts - ✅ All
?.optional chaining oncatalogStorereplaced with.(required access) - ✅
WorkflowRunDeps.catalogStore,ModelMethodRunDeps.queryData, andStepExecutionContext.catalogStoreall made required - ✅ Design doc (
design/data-query.md) updated to reflect required semantics - ✅ All 120+ test call sites updated with
CatalogStoreinstances - ✅
createCatalogStore()factory properly resolves datastore paths - ✅ No
anytypes introduced, no security concerns, license headers intact
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
src/libswamp/workflows/run_test.ts:174,441 — Temp directory leak with SQLite handle
createTestDeps()andcreateTestDepsWithCapture()both callDeno.makeTempDirSync()and open aCatalogStore(SQLite DB) but never clean up the directory or close the database. Each test invocation leaves a temp dir with an open SQLite file behind.Breaking example: Not a production issue, but if the test suite grows, accumulated temp dirs and unclosed SQLite handles could exhaust file descriptors or disk in CI environments.
Suggested fix: Wrap in a cleanup pattern, or use the existing
withTempDirhelper that other test files use, and callcatalogStore.close()in a finally block.
Low
-
src/libswamp/
createXxxDepsfunctions — Multiple CatalogStore instances per repo*Each
createXxxDeps()factory (gc, get, list, rename, versions, delete, evaluate, output_data, output_logs, validate, reports/search) callscreateCatalogStore()which opens a fresh SQLite connection. If multiple operations run concurrently against the same repo, there will be multiple open connections to the same_catalog.db. This is pre-existing behavior that WAL mode +busy_timeout=5000handles, and the PR doesn't change the lifecycle pattern — it just makes the parameter required. Not a new risk. -
Tests using hardcoded
/tmppaths for CatalogStore (e.g.,unified_data_repository_test.ts:50,execution_service_test.ts:1323)A few tests construct
CatalogStore(join("/tmp/test-repo", "_catalog.db"))or similar. TheCatalogStoreconstructor callsensureDirSyncwhich will create these directories as a side effect. Not harmful in practice since/tmpis ephemeral, but leaves debris between test runs.
Verdict
PASS — This is a clean, well-structured bug fix. The core change (making CatalogStore required on FileSystemUnifiedDataRepository) is sound: it turns a runtime "missing catalog" silent failure into a compile-time error. The root cause fix in DefaultStepExecutor.executeModelMethod correctly threads the catalogStore through StepExecutionContext. The createCatalogStore() factory helper correctly replicates the previous inline catalog construction pattern. All 41 changed files are consistent with the new contract, and the type system (deno check passing) enforces correctness at all 120+ call sites. No logic errors, security issues, or data integrity concerns.
Summary
CatalogStorea required constructor parameter onFileSystemUnifiedDataRepository, turning missing-catalog bugs into compile errorsexecuteModelMethodinWorkflowExecutionServicecreated a data repo without catalog write-through, sodata.latest()returned null for data written by workflow stepscreateCatalogStore()factory helper to centralize catalog constructionqueryDatarequired onModelMethodRunDepsandcatalogStorerequired onWorkflowRunDeps?.optional chaining oncatalogStoreacross CLI/serve codedesign/data-query.mdto reflect required semanticsTest Plan
deno checkpasses (type system enforces catalog presence at all 120+ call sites)deno lintcleandeno fmtcleandata.latest()read step now succeeds (previously failed withNo such key: attributes)🤖 Generated with Claude Code