Skip to content

feat: add Definition entity with CEL expression support (#117)#133

Merged
github-actions[bot] merged 1 commit intomainfrom
feat/117-definition-entity
Feb 3, 2026
Merged

feat: add Definition entity with CEL expression support (#117)#133
github-actions[bot] merged 1 commit intomainfrom
feat/117-definition-entity

Conversation

@stack72
Copy link
Copy Markdown
Contributor

@stack72 stack72 commented Feb 3, 2026

Fixes: #117

Implements the Definition entity that provides a declarative way to define model configurations with support for CEL expressions and parameterized inputs. This closes #117.

  • Add Definition entity with branded ID, Zod validation, and JSON Schema support for inputs
  • Add YAML-based repositories for raw and evaluated definitions
  • Integrate with existing expression evaluation service for CEL expression support
  • Add domain events for definition lifecycle (created, updated, deleted)
  • Add comprehensive unit tests (28) and integration tests (10)

Plan Comparison

Planned Item Status Notes
Files to Create
src/domain/definitions/definition.ts ✅ Created Includes entity, schema, and branded ID (combined vs separate files)
src/domain/definitions/definition_schema.ts ✅ Merged Combined into definition.ts for cohesion
src/domain/definitions/definition_id.ts ✅ Merged Combined into definition.ts for cohesion
src/infrastructure/persistence/yaml_definition_repository.ts ✅ Created Storage at .swamp/definitions/{type}/{id}.yaml
src/infrastructure/persistence/yaml_evaluated_definition_repository.ts ✅ Created Storage at .swamp/definitions-evaluated/{type}/{id}.yaml
src/domain/definitions/definition_test.ts ✅ Created 28 unit tests
Files to Update
src/domain/expressions/expression_evaluation_service.ts ✅ Updated Added evaluateDefinition(), evaluateAllDefinitions(), hasDefinitionExpressions()
Additional Files
src/domain/definitions/repositories.ts ✅ Created Repository interface (DDD pattern)
integration/definition_test.ts ✅ Created 10 integration tests for CEL evaluation
src/domain/expressions/model_resolver.ts ✅ Updated Added definition support to context
src/domain/events/types.ts ✅ Updated Added definition domain events
.gitignore ✅ Updated Added .swamp/definitions-evaluated/

Acceptance Criteria

  • Definition entity supports all design doc fields (id, name, tags, attributes, version, inputs)
  • Repository saves/loads definitions as YAML
  • CEL expressions preserved in raw definitions
  • Evaluated definitions cached in .swamp/definitions-evaluated/
  • Integration with existing expression evaluation service
  • Unit tests for Definition entity (28 tests)
  • Integration test: create definition with CEL expressions, evaluate expressions (10 tests)

Expression Context Support

Context Path Status Notes
inputs.{param} ✅ Supported Access definition input values
self.{field} ✅ Supported Self-reference within definition
model.{name}.definition.attributes ✅ Supported Reference other definitions
model.{name}.input.attributes ✅ Supported Reference model inputs
model.{name}.resource.attributes ✅ Supported Reference model resources
vault.get(vault_name, key) ✅ Supported Already existed
model.{name}.data.{data-name}.attributes 📋 Future Documented as architectural enhancement

Test plan

  • Run deno check - Type checking passes
  • Run deno lint - Linting passes
  • Run deno fmt - Formatting applied
  • Run deno run test - All 1378 tests pass
  • Run deno run compile - Binary compiles successfully

🤖 Generated with Claude Code

Fixes: #117

Implements the Definition entity that provides a declarative way to define model configurations with support for CEL expressions and parameterized inputs. This closes #117.

- Add `Definition` entity with branded ID, Zod validation, and JSON Schema support for inputs
- Add YAML-based repositories for raw and evaluated definitions
- Integrate with existing expression evaluation service for CEL expression support
- Add domain events for definition lifecycle (created, updated, deleted)
- Add comprehensive unit tests (28) and integration tests (10)

## Plan Comparison

| Planned Item | Status | Notes |
|--------------|--------|-------|
| **Files to Create** | | |
| `src/domain/definitions/definition.ts` | ✅ Created | Includes entity, schema, and branded ID (combined vs separate files) |
| `src/domain/definitions/definition_schema.ts` | ✅ Merged | Combined into `definition.ts` for cohesion |
| `src/domain/definitions/definition_id.ts` | ✅ Merged | Combined into `definition.ts` for cohesion |
| `src/infrastructure/persistence/yaml_definition_repository.ts` | ✅ Created | Storage at `.swamp/definitions/{type}/{id}.yaml` |
| `src/infrastructure/persistence/yaml_evaluated_definition_repository.ts` | ✅ Created | Storage at `.swamp/definitions-evaluated/{type}/{id}.yaml` |
| `src/domain/definitions/definition_test.ts` | ✅ Created | 28 unit tests |
| **Files to Update** | | |
| `src/domain/expressions/expression_evaluation_service.ts` | ✅ Updated | Added `evaluateDefinition()`, `evaluateAllDefinitions()`, `hasDefinitionExpressions()` |
| **Additional Files** | | |
| `src/domain/definitions/repositories.ts` | ✅ Created | Repository interface (DDD pattern) |
| `integration/definition_test.ts` | ✅ Created | 10 integration tests for CEL evaluation |
| `src/domain/expressions/model_resolver.ts` | ✅ Updated | Added definition support to context |
| `src/domain/events/types.ts` | ✅ Updated | Added definition domain events |
| `.gitignore` | ✅ Updated | Added `.swamp/definitions-evaluated/` |

### Acceptance Criteria

- [x] Definition entity supports all design doc fields (`id`, `name`, `tags`, `attributes`, `version`, `inputs`)
- [x] Repository saves/loads definitions as YAML
- [x] CEL expressions preserved in raw definitions
- [x] Evaluated definitions cached in `.swamp/definitions-evaluated/`
- [x] Integration with existing expression evaluation service
- [x] Unit tests for Definition entity (28 tests)
- [x] Integration test: create definition with CEL expressions, evaluate expressions (10 tests)

### Expression Context Support

| Context Path | Status | Notes |
|--------------|--------|-------|
| `inputs.{param}` | ✅ Supported | Access definition input values |
| `self.{field}` | ✅ Supported | Self-reference within definition |
| `model.{name}.definition.attributes` | ✅ Supported | Reference other definitions |
| `model.{name}.input.attributes` | ✅ Supported | Reference model inputs |
| `model.{name}.resource.attributes` | ✅ Supported | Reference model resources |
| `vault.get(vault_name, key)` | ✅ Supported | Already existed |
| `model.{name}.data.{data-name}.attributes` | 📋 Future | Documented as architectural enhancement |

## Test plan

- [x] Run `deno check` - Type checking passes
- [x] Run `deno lint` - Linting passes
- [x] Run `deno fmt` - Formatting applied
- [x] Run `deno run test` - All 1378 tests pass
- [x] Run `deno run compile` - Binary compiles successfully

🤖 Generated with [Claude Code](https://claude.ai/code)
@stack72 stack72 force-pushed the feat/117-definition-entity branch from e22c5ee to 210f596 Compare February 3, 2026 17:43
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR implements the Definition entity with CEL expression support. The implementation is well-structured and follows established patterns in the codebase.

✅ Strengths

Domain-Driven Design Compliance:

  • Definition entity follows established DDD patterns (branded ID, private constructor with factory methods, Zod validation)
  • Repository interface (DefinitionRepository) properly abstracts persistence for the aggregate root
  • Domain events (DefinitionCreated, DefinitionUpdated, DefinitionDeleted) capture lifecycle changes for cross-aggregate communication

Code Quality:

  • Uses named exports throughout (no default exports)
  • No any types - TypeScript strict mode requirements followed
  • Proper encapsulation with getters returning copies of mutable state (structuredClone for nested objects)
  • Consistent patterns with existing entities like ModelInput

Test Coverage:

  • 28 unit tests for the Definition entity
  • 10 integration tests covering CEL expression evaluation flows
  • Tests cover edge cases (empty attributes, self-references, cross-model references)

Integration:

  • Clean integration with existing ExpressionEvaluationService
  • Model resolver properly updated to support definition context
  • .gitignore correctly updated for evaluated definitions cache

📋 Suggestions (Non-blocking)

  1. Consider adding repository unit tests: The project has a pattern of having unit tests next to source files (e.g., yaml_input_repository_test.ts). While the integration tests cover the repositories well, adding yaml_definition_repository_test.ts and yaml_evaluated_definition_repository_test.ts would align with the codebase convention and provide faster feedback during development.

  2. Interface consistency: YamlEvaluatedDefinitionRepository has a similar API to YamlDefinitionRepository but doesn't implement DefinitionRepository. This is likely intentional since evaluated repos serve a different purpose (caching), but consider documenting this design decision or creating a separate interface to prevent API drift.

  3. Attribute copy consistency: Definition.attributes uses structuredClone(this._attributes) while ModelInput.attributes uses { ...this._attributes }. The Definition approach is more robust for deeply nested structures. Consider updating ModelInput to match this pattern in a future PR for consistency.


Overall, this is a well-implemented feature that adheres to the project's DDD principles and coding standards. The test coverage is comprehensive and the integration with existing systems is clean.

🤖 Reviewed by Claude Code

@github-actions github-actions Bot merged commit 2d5783b into main Feb 3, 2026
3 checks passed
@github-actions github-actions Bot deleted the feat/117-definition-entity branch February 3, 2026 17:48
ianarsenault pushed a commit to ianarsenault/swamp that referenced this pull request Apr 21, 2026
…lve (swamp-club#133) (systeminit#1197)

Fixes [swamp-club#133](https://swamp.club/lab/issues/133).

## Summary

Auto-resolve treated any `pulled-extensions/<name>/` directory that
existed on disk as "installed" — regardless of whether its contents were
complete. A tree with `manifest.yaml` missing or a declared kind
subdirectory missing still passed the `isInstalled` check, so the
resolver emitted `reason: already_installed` and the downstream loader
produced a confusing `Unknown datastore type "@swamp/s3-datastore"` /
`Unknown model type: @swamp/issue-lifecycle` error with no pointer at
what was actually wrong.

This is a regression from [PR
systeminit#1187](systeminit#1187) (swamp-club#121).
Before that change, the adapter called `installExtension` with `force:
true` on any load failure — a truncated tree self-healed on the next
command. After systeminit#1187 the strict "never overwrite" guard caught the
truncated case alongside the user-edit case it was meant for, and the
truncation recovery path was lost.

## Approach

The fix extends the "never overwrite" guarantee to truncation while
giving users an actionable error. It is **read-only** — no filesystem
mutation in the auto-resolve path. `--force` remains the only way
auto-install state can overwrite a pulled extension.

- Introduces a tri-state `InstallationInspection` result (`missing` |
`intact` | `truncated`) on `ExtensionInstallerPort`, replacing
`isInstalled` + `installedPath`. Both the path and the missing-files
list ride back on the result.
- The adapter drives the check from the lockfile's file list
(authoritative file-level granularity, so partial truncation inside a
kind subdir is caught too — not just whole-subdir removal).
- The domain service branches on the three states: `missing` → install,
`intact` → `alreadyInstalledButFailed` (unchanged systeminit#121 path for user
WIP), `truncated` → new `alreadyInstalledTruncated` event naming every
missing file plus the `swamp extension pull <name> --force` recovery
command.

Auto-repair was considered and rejected. The user-journey analysis
surfaced that mutating the filesystem in response to a read-shaped
command has too many quiet side effects for shared-repo / multi-worktree
setups: silent version bumps (install is always `version: null` →
latest), concurrent-worktree-rm reversal, masking of deliberate user
state changes, and latency surprise. A loud, actionable error with an
explicit recovery command is safer and keeps the systeminit#121 invariant intact.

## What changed

- `src/domain/extensions/extension_auto_resolver.ts` — new
`InstallationInspection` type, port method `inspectInstallation`
(replaces `isInstalled` + `installedPath`), new
`alreadyInstalledTruncated` event on `AutoResolveOutputPort`, three-way
branch in `installAndLoad`.
- `src/cli/auto_resolver_adapters.ts` — implements `inspectInstallation`
against the lockfile + per-extension directory + per-file stats; wires
the new renderer through `createAutoResolveOutputAdapter`.
- `src/presentation/renderers/extension_auto_resolve.ts` — new
`renderAutoResolveTruncated` for both `log` (three-line actionable
error) and `json`
(`{"event":"auto_resolve","status":"failed","reason":"truncated","missing":[...]}`).
- `src/cli/auto_resolver_adapters_test.ts` — 7 new `inspectInstallation`
tests replacing the 5 prior `isInstalled`/`installedPath` cases; covers
missing / intact / truncated / partial-truncation / all-files-missing /
empty-files edge cases.
- `src/domain/extensions/extension_auto_resolver_test.ts` — updated mock
port; new regression guards for the three-way branch (`intact →
alreadyInstalledButFailed` preserved from systeminit#121, `truncated →
alreadyInstalledTruncated` is new).
- `integration/auto_resolver_truncated_test.ts` — new hermetic
integration test: hand-written scratch fixture, deletes one declared
file, asserts the truncated event fires with the missing file AND the
on-disk tree is byte-identical after the resolve attempt.
- `integration/auto_resolver_no_clobber_test.ts` — ported to the new
port method (no behavior change; same systeminit#121 invariant).
- `design/extension.md` — "Safety: never overwrite on-disk extensions"
section extended with the tri-state taxonomy, truncation predicate, and
JSON event shape for scripting consumers.

## Coverage

Works across all kinds that go through auto-resolve (models, vaults,
datastores) — any file missing anywhere in the extension tree is
detected when auto-resolve fires for a type from that extension, because
`inspectInstallation` walks the whole lockfile file list. Driver-only or
report-only extensions (no auto-resolve trigger) are not covered by this
PR; extending to those would require new `resolveDriverType` /
`resolveReportType` plumbing and is scope creep for systeminit#133. Worth a
follow-up if belt-and-braces coverage is desired.

## Test plan

- [x] `deno fmt` clean
- [x] `deno check` clean
- [x] `deno lint` clean
- [x] `deno run test` — 4445 pass / 0 fail (6 new adapter tests, 2 new
domain-service tests, 1 new integration test; rewrote 5 systeminit#121 regression
cases against the new port shape)
- [x] `deno run compile` — binary built
- [x] Manual reproduction against `/tmp/swamp-repro-issue-133` with the
compiled binary:
- **Before truncation:** `swamp extension pull @swamp/digitalocean` then
`swamp model create @swamp/digitalocean/droplet test-droplet` succeeds.
- **Truncate:** `rm -rf
.swamp/pulled-extensions/@swamp/digitalocean/models`.
- **Re-run `model create`:** json mode emits
`{"event":"auto_resolve","status":"failed","reason":"truncated","missing":[68
files]}`; log mode emits three ERR lines naming the missing files and
the `swamp extension pull @swamp/digitalocean --force` recovery.
- **Follow the recovery command:** `swamp extension pull
@swamp/digitalocean --force` restores the tree.
  - **Re-run `model create`:** succeeds.
- [x] Tree on disk is byte-identical before and after the resolve
attempt — no auto-repair, no silent overwrite.

## Follow-up

- Driver-only / report-only extensions remain uncovered by auto-resolve
(see "Coverage" above). Worth a separate issue if exhaustive cross-kind
coverage is desired.
- Root cause of truncation itself is still unknown (partial extract,
interrupted install, concurrent worktree op). This PR makes the symptom
actionable so the cause is easier to investigate when it next occurs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
ianarsenault pushed a commit to ianarsenault/swamp that referenced this pull request Apr 21, 2026
…tion check (systeminit#1199)

## Summary

The tri-state `inspectInstallation` introduced in systeminit#1197 stats every path
in the lockfile's file list to detect a truncated install. That list
includes compiled bundles under `.swamp/bundles/**`,
`.swamp/vault-bundles/**`, `.swamp/driver-bundles/**`,
`.swamp/datastore-bundles/**`, and `.swamp/report-bundles/**` —
regenerable build artifacts, not source.

When a user clears any of those caches (normal hygiene, stale rebuild,
fresh worktree), auto-resolve flipped from `intact` → `truncated` and
emitted:

```
incomplete — missing 1 file(s): ".swamp/bundles/91139983/system_usage.js"
```

…instead of the `alreadyInstalledButFailed` ("already installed at … run
`--force` to recover") path that protects user WIP per systeminit#121. The
motivating swamp-club#133 symptoms are all source-tree artifacts
(manifest, kind subdirs, declared source), so the fix is to scope the
truncation predicate to source files only.

This filters bundle prefixes out of the stat loop in
`inspectInstallation`. Prefixes are derived from
`SWAMP_SUBDIRS.{bundles, vaultBundles, driverBundles, datastoreBundles,
reportBundles}` so a future bundle-dir addition stays in sync. No
filesystem mutation, no API/event/wire-format changes.

## Test Plan

- [x] `deno fmt --check`, `deno lint`, `deno check` clean
- [x] `src/cli/auto_resolver_adapters_test.ts` — 18/18 pass, including 2
new regression cases:
- `inspectInstallation ignores absent bundle artifacts and stays intact`
(covers all 5 bundle subdirs)
- `inspectInstallation reports truncated only for missing source, not
missing bundles`
- [x] `integration/auto_resolver_truncated_test.ts` — passes (systeminit#1197/systeminit#133
source-truncation behaviour preserved)
- [x] `integration/auto_resolver_no_clobber_test.ts` — passes (systeminit#121
no-clobber invariant preserved)
- [x] `deno run compile` succeeded
- [x] **End-to-end repro against the rebuilt binary:** `swamp extension
pull @stack72/[email protected]` → edit pulled `.ts` with
WIP marker + syntax error → `rm -rf .swamp/bundles` → `swamp model
create @stack72/system-usage instance1` now produces:
  ```
Extension "@stack72/system-extensions" is already installed at … but
failed to load.
Local edits may be preventing it from registering — inspect the source
and fix errors.
To reset to the registry version and discard local changes, run: swamp
extension pull "@stack72/system-extensions" --force
  ```
…instead of the buggy `incomplete — missing 1 file(s):
".swamp/bundles/91139983/system_usage.js"`.
- [ ] UAT `tests/cli/extension/pull_wip_preservation_test.ts:156` in
swamp-uat (CI gate — local run blocked by membership auto-trust
collision unrelated to this fix)

## Cross-references

- Introducing PR: systeminit#1197
- Fixes regression to swamp-club#121 / systeminit#1187 user-WIP preservation
- swamp-uat coverage: swamp-uat#149

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
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.

Issue 2: Create Definition entity to replace ModelInput

1 participant