fix(data): accept string type in getContent, findByName, findAllForModel (swamp-club#364)#1393
Conversation
…ndAllForModel (swamp-club#364) The production UnifiedDataRepository required a ModelType object for these methods, but workflow-scope reports only have step.modelType as a string. The testing helper accepted strings, masking the mismatch — extension tests passed but production threw TypeError: type.toDirectoryPath is not a function. Add ModelTypeInput (string | ModelType) type alias and coerce strings to ModelType at the three extension-facing method boundaries. Closes #364 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
Clean, well-scoped fix for a runtime type mismatch between the domain port and its callers. The approach is sound from a DDD perspective — widening the repository port to accept ModelTypeInput (a union type) while coercing to the ModelType value object at the infrastructure boundary preserves the domain model's invariants. The coercion happens exactly once per entry point, keeping the internal implementation clean.
Blocking Issues
None.
Suggestions
-
Sync method consistency: The sync counterparts (
findByNameSync,getContentSync,findAllForModelSync) still requireModelTypeonly. If the same workflow-scope reports could call these paths, they'd hit the sametoDirectoryPath is not a functionerror. Consider widening them in a follow-up if callers need it. -
Test helper reuse: The three new tests duplicate the tmpDir + CatalogStore + cleanup boilerplate. The file already has a
withDataRepohelper (line 759) that encapsulates this pattern — the new tests could use it for consistency and less code. Minor — doesn't block merge. -
findAllForModelparameter reassignment: InfindAllForModel,type = coerceModelType(type)reassigns the parameter. This works because the widenedModelTypeInputtype allows it, but using a separateconst coerced = coerceModelType(type)(likefindByNamedoes by passing tofindByNameWithDepth) would be more consistent across the three methods. Stylistic only.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
- Sync method variants not widened (
src/domain/data/repositories.ts:333-392): The interface widensfindByName,getContent, andfindAllForModelto acceptModelTypeInput, but the sync counterparts (findByNameSync,getContentSync,findAllForModelSync) still require strictModelType. If the same workflow-scope report code that triggered the original bug ever calls a sync method with a raw string, it will hit the sametype.toDirectoryPath is not a functioncrash. This is low-risk today — sync callers I found (data_query_service.ts,data_record_mapper.ts,model_resolver.ts) all pass properModelTypeobjects — but it creates a latent inconsistency in the API surface. Consider widening the sync methods too for parity, or documenting that they intentionally remain strict.
Low
-
Double coercion on internal calls (
src/infrastructure/persistence/unified_data_repository.ts:460-470): InfindAllForModel, aftertype = coerceModelType(type)on line 460, the loop callsthis.findByName(type, modelId, dataName)on line 470, which coerces again insidefindByNameWithDepth. This is an extrainstanceofcheck per data item — harmless but redundant. Same applies tofindAllForModelSince(line 287) callingfindByNameafter already having aModelType. Not worth fixing, just noting. -
coerceModelTypethrows on empty/whitespace-only strings (src/domain/models/model_type.ts:178-181):ModelType.create("")throws"Model type cannot be empty". This is correct behavior and strictly better than the previoustype.toDirectoryPath is not a functionTypeError — but callers passing user-controlled strings won't get a domain-specific "model type not found" error, they'll get a generic validation error. The current callers passstep.modelTypewhich should always be a valid non-empty string, so this is theoretical.
Verdict
PASS — The fix is correct, targeted, and well-tested. coerceModelType properly handles both branches, TypeScript narrowing after parameter reassignment works correctly, and the tests cover all three widened methods. The original runtime crash (type.toDirectoryPath is not a function) is cleanly resolved.
Bump swampVersion 20260516.045246.0 -> 20260518.162558.0 (includes the upstream fix for systeminit/swamp#1393 — dataRepository.getContent now accepts string types in production, matching docs and the testing helper). Run `swamp init` with claude and opencode integrations: - AGENTS.md, CLAUDE.md: swamp-managed project rules for agents - .claude/skills/: swamp skill references for Claude Code - .opencode/plugins/swamp-audit.ts: opencode audit plugin - .gitignore: managed exclusions for agent worktrees and local state
Lab #364 is resolved by systeminit/swamp#1393 (shipped in swamp 20260518.162558.0): dataRepository.getContent now accepts string types, matching the documented contract and the testing helper. The production-only TypeError that forced us to bypass the API is gone. Drop the raw-file workaround: - Remove readDataFile and the bypass through .swamp/data/<...>/raw. collectBundles now calls context.dataRepository.getContent, which returns the same Uint8Array | null shape so decodeJson is unchanged. - Remove the defensive `typeof step.modelType === "string"` branch. step.modelType is and always was a string (option 3 from the issue was not taken); the toString fallback was never reachable. - Rewrite the three collectBundles recovery-path tests to back the context with an in-memory dataRepository fake instead of a temp repoDir + on-disk fixture files. - Retarget the README "Every finding is skip" troubleshooting row away from .swamp/data/ (no longer a path we depend on) toward the report's own findings[].message reasons and the workflow logs.
Summary
UnifiedDataRepository.getContent()required aModelTypeobject, but workflow-scope reports only havestep.modelTypeas a string — callinggetContent(string, ...)threwTypeError: type.toDirectoryPath is not a functionTestDataRepository) accepted strings, so extension tests passed but production failed at runtimeModelTypeInputtype alias (string | ModelType) andcoerceModelType()helper, then widenedfindByName,getContent, andfindAllForModelto accept strings at the domain port and coerce in the production implementationTest Plan
getContent,findByName, andfindAllForModelreturn correct resultsgetContent(step.modelType, ...)— confirmedtype.toDirectoryPath is not a functionerror before fix, and successful data reads after fix🤖 Generated with Claude Code