Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ac3bc0e
add spec and execution plan for contract domain-storage separation
wmadden Mar 31, 2026
8d9f6ba
add skill to prevent heredoc escaping in commit messages
wmadden Mar 31, 2026
4b19f2b
add typechecked type design for contract domain-storage separation
wmadden Mar 31, 2026
72660b2
add models intersection validation to type design
wmadden Mar 31, 2026
976d08c
add domain types and widen ContractBase with roots+models
wmadden Mar 31, 2026
284adff
extract domain validation from mongo-core to framework contract
wmadden Mar 31, 2026
e8ded02
add dual-format normalizeContract bridge and domain validation
wmadden Mar 31, 2026
9dc0f84
update emitter to produce ADR 172 contract structure
wmadden Mar 31, 2026
02da4ea
update emitter output fixtures and canonicalization for ADR 172
wmadden Mar 31, 2026
1716780
keep models off ContractBase to avoid index signature leakage
wmadden Mar 31, 2026
7d12884
add roots field to test contracts and update emitter test assertions
wmadden Mar 31, 2026
c98c83c
restore models on ContractBase via generic TModels parameter
wmadden Mar 31, 2026
c633804
refine design questions: SQL JSON relevance, M:N gap, strategy naming
wmadden Mar 31, 2026
c1b9bc7
ADR 177: ownership replaces relation strategy
wmadden Mar 31, 2026
c08f70f
update docs to use owner/storage.relations instead of strategy
wmadden Mar 31, 2026
a3d1424
add design question Q19: self-referential models
wmadden Mar 31, 2026
76d5fc1
resolve nested ownership open question in ADR 177
wmadden Mar 31, 2026
b1f6ac6
align spec and plan with ADR 177: owner replaces relation strategy
wmadden Mar 31, 2026
f08141e
resolve all open questions in contract-domain-extraction spec
wmadden Mar 31, 2026
3cd8f85
mark all spec open questions as resolved in plan
wmadden Mar 31, 2026
5e022c5
strip column from emitted model.fields for ADR 172 compliance
wmadden Mar 31, 2026
dead52b
add owner and ownership validation to spec acceptance criteria
wmadden Mar 31, 2026
dc37139
clean up constructContract roots cast and emitter-lanes round-trip test
wmadden Mar 31, 2026
8366f74
address review feedback: relation comment, domain shape helper, roots…
wmadden Mar 31, 2026
21ead96
implement ADR 177: remove strategy from relations, add owner to models
wmadden Mar 31, 2026
ca2954e
update tests for ADR 177: ownership validation and strategy removal
wmadden Mar 31, 2026
f405490
regenerate contract fixtures: remove strategy from emitted JSON
wmadden Mar 31, 2026
b5fff3b
fix JSON schema: ModelField no longer requires column
wmadden Mar 31, 2026
57a6af9
fix biome noBannedTypes: replace {} with Record<string, never>
wmadden Mar 31, 2026
64d00f4
fix(sql-contract): raise branch coverage to meet 95% per-file threshold
wmadden Mar 31, 2026
a208200
docs: fix review feedback - links, ADR consistency, glossary wording
wmadden Mar 31, 2026
28381bc
fix: address review feedback - own-property checks, owner schema/emis…
wmadden Mar 31, 2026
653fd07
docs: add M3 (Mongo emitter hook), renumber milestones M3-M6
wmadden Apr 1, 2026
f114262
fix(sql-contract): harden normalizeContract dual-format bridge
jkomyno Apr 1, 2026
0094f36
chore: regenerate fixtures after rebase onto origin/main
wmadden Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .agents/skills/multiline-commit-messages/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: multiline-commit-messages
description: >-
Use single-quoted strings for multiline git commit messages in the Shell tool.
Prevents heredoc escaping failures that produce garbled commit messages.
---

# Multiline commit messages

The Shell tool sends commands as a single string. Heredoc syntax (`<<'EOF'`) inside `$(cat ...)` is fragile and fails silently — the literal `$(cat <<'EOF' ...` ends up as the commit message instead of the intended text.

## Rule

**Never** use `$(cat <<'EOF' ...)` or `$(cat <<EOF ...)` for commit messages.

Use single-quoted strings with embedded newlines:

```bash
git commit -m 'short summary line

Longer body paragraph explaining why the change exists.
Additional context if needed.'
```

## Why heredocs fail

The Shell tool passes the command as a single string argument. When you write:

```bash
git commit -m "$(cat <<'EOF'
message
EOF
)"
```

The shell may not parse the heredoc correctly in this context — the `EOF` delimiter, newlines, and nested quoting interact unpredictably. The result is the raw `$(cat <<'EOF' ...` text appearing as the commit message.

Comment thread
wmadden marked this conversation as resolved.
Single-quoted strings with literal newlines are simple, portable, and always work.
6 changes: 3 additions & 3 deletions .agents/skills/write-architecture-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Architecture docs in this repo serve two audiences: team members working on the

**Write for a developer without prior context.** Imagine someone joining the team and reading this doc as their first exposure to this part of the system.

- Explain *why* before *what*. Before introducing a concept like `strategy: "embed"`, explain the problem it solves: "In SQL, related data lives in separate tables and is joined at query time. In MongoDB, the idiomatic pattern is to store related data inside the parent document."
- Explain *why* before *what*. Before introducing a concept like model ownership, explain the problem it solves: "In SQL, related data lives in separate tables and is joined at query time. In MongoDB, the idiomatic pattern is to store related data inside the parent document."
- Let ideas breathe. Don't compress three concepts into one sentence. If a sentence requires the reader to already understand three things to parse it, break it apart.
- Use concrete examples — code snippets, JSON fragments, "a developer writing X gets Y under the hood." Abstract descriptions are hard to pin understanding to.

Expand All @@ -46,8 +46,8 @@ Bad: "MongoDB is a database family in Prisma Next. The contract, ORM, execution

**Inline summaries with ADR links.** When referencing an ADR, summarize the key idea in the text and link the ADR for depth. The doc should be understandable without following any links.

Good: "Embedding is a property of the *relation*, not the model. A relation with `"strategy": "embed"` means the related model lives nested inside the parent's document. See [ADR 174](...)."
Bad: "See [ADR 174](...) for how embedding works."
Good: "An owned model declares `owner: \"User\"` — a domain fact about aggregate membership. Its data lives within the owner's storage. See [ADR 177](docs/architecture%20docs/adrs/ADR%20177%20-%20Ownership%20replaces%20relation%20strategy.md)."
Bad: "See [ADR 177](...) for how embedding works."

**References section.** Organize by durability:
1. Architecture decisions (ADRs) — first
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Each model's domain section should give a reader a complete picture of the field

The contract has three levels, each serving a different consumer:

1. **Domain level** (`roots`, `model.fields`, `model.relations`, `model.discriminator`/`variants`) — what the application models. Family-agnostic structure. Consumed by the ORM for type inference, by agents for understanding the data model, by any tool that doesn't need to know about storage.
1. **Domain level** (`roots`, `model.fields`, `model.relations`, `model.owner`, `model.discriminator`/`variants`) — what the application models. Family-agnostic structure. Consumed by the ORM for type inference, by agents for understanding the data model, by any tool that doesn't need to know about storage.
2. **Model storage bridge** (`model.storage`) — how domain fields connect to persistence. Sits on the model to preserve co-location. SQL carries field-to-column mappings because field names and column names can differ; Mongo carries only the collection name. `model.storage.fields` is available to Mongo should field name remapping ever be needed (e.g., `createdAt` → `_created_at`), but typically Mongo doesn't need it.
3. **Top-level storage** (`storage`) — the database schema itself. SQL: every table, every column with its native type, nullability constraint, default, plus indexes and foreign keys. Mongo: collection metadata (indexes, validators). Consumed by migration tooling, schema introspection, and DDL generation.

Expand Down Expand Up @@ -158,7 +158,8 @@ For Mongo, the redundancy is much smaller. There's no column indirection, so `mo

### Other domain-level properties

- **`model.relations`** — connections to other models with cardinality and strategy (see [ADR 174](ADR%20174%20-%20Aggregate%20roots%20and%20relation%20strategies.md)).
- **`model.relations`** — connections to other models with cardinality and optional join details (see [ADR 174](ADR%20174%20-%20Aggregate%20roots%20and%20relation%20strategies.md)).
- **`model.owner`** — declares aggregate membership: an owned model's data is co-located with its owner's storage (see [ADR 177](ADR%20177%20-%20Ownership%20replaces%20relation%20strategy.md)).
- **`model.discriminator`** + **`model.variants`** — optional polymorphism declaration (see [ADR 173](ADR%20173%20-%20Polymorphism%20via%20discriminator%20and%20variants.md)).

**Note — relations placement is a change from the current contract.** The current SQL emitter produces a top-level `relations` block as a sibling of `models`, keyed by model name (e.g., `contract.relations.user.posts`). The SQL ORM client consumes relations from this top-level block. This ADR moves relations onto each model (`model.relations`) because a model's relationships are part of its domain description — a reader should be able to understand a model completely without consulting a separate section. The current top-level placement was not a deliberate design choice; it diverged from the test fixtures (which use the nested form) during emitter development. The SQL emitter and ORM client will need to be updated to match.
Expand All @@ -167,7 +168,7 @@ For Mongo, the redundancy is much smaller. There's no column indirection, so `mo

### Benefits

- **Shared contract base is viable.** The domain level (`roots`, `models` with `fields`/`discriminator`/`variants`, `relations`) is structurally identical between families. A `ContractBase` type can capture this, with `model.storage` as a generic/family-specific extension point.
- **Shared contract base is viable.** The domain level (`roots`, `models` with `fields`/`discriminator`/`variants`/`owner`, `relations`) is structurally identical between families. A `ContractBase` type can capture this, with `model.storage` as a generic/family-specific extension point.
- **Consumer libraries can be family-agnostic** for domain-level operations (listing models, traversing relations, finding aggregate roots).
- **Each family controls its own storage representation** without compromising the other.
- **The storage divergence is narrower.** Moving `codecId` to the domain level means Mongo's `model.storage` is just a collection name. The remaining divergence (SQL's field-to-column mappings) reflects a genuine structural difference.
Expand All @@ -180,7 +181,7 @@ For Mongo, the redundancy is much smaller. There's no column indirection, so `mo
### What this requires

**Implemented in this PR (Mongo PoC):**
- `MongoContract` adopts the domain-storage separation with `model.fields` carrying `{ nullable, codecId }`, `model.relations` with strategy, and `model.storage` scoped per model.
- `MongoContract` adopts the domain-storage separation with `model.fields` carrying `{ nullable, codecId }`, `model.relations` as plain graph edges (cardinality + optional join details), and `model.storage` scoped per model.
- `validateContractDomain()` validates domain-level invariants (roots, variants, relations, discriminators) in a family-agnostic way.
- `validateMongoStorage()` validates Mongo-specific storage rules.

Expand All @@ -194,6 +195,7 @@ For Mongo, the redundancy is much smaller. There's no column indirection, so `mo

- [ADR 173 — Polymorphism via discriminator and variants](ADR%20173%20-%20Polymorphism%20via%20discriminator%20and%20variants.md)
- [ADR 174 — Aggregate roots and relation strategies](ADR%20174%20-%20Aggregate%20roots%20and%20relation%20strategies.md)
- [ADR 177 — Ownership replaces relation strategy](ADR%20177%20-%20Ownership%20replaces%20relation%20strategy.md) — `owner` on models replaces `strategy` on relations
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- [Data Contract subsystem doc](../subsystems/1.%20Data%20Contract.md) — contract structure and semantics
- [MongoDB Family subsystem doc](../subsystems/10.%20MongoDB%20Family.md) — Mongo contract, ORM, and execution pipeline

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A polymorphic Task model with Bug and Feature variants. Task declares which fiel
"Feature": { "value": "feature" }
},
"relations": {
"assignee": { "to": "User", "cardinality": "N:1", "strategy": "reference" }
"assignee": { "to": "User", "cardinality": "N:1", "on": { "localFields": ["assigneeId"], "targetFields": ["id"] } }
},
"storage": {
"table": "tasks",
Expand Down Expand Up @@ -157,7 +157,7 @@ All persistence-level polymorphism reduces to "multiple shapes in the same stora

A model can be simultaneously:
- Polymorphic (has `discriminator` + `variants`) AND an aggregate root (appears in `roots`)
- A variant AND embedded (parent has `"strategy": "embed"`)
- A variant AND owned (has `"owner": "ParentModel"`)
- Polymorphic AND embedded

These are independent properties. This composability is why we rejected labeled strategies — they create a false choice between roles that are actually orthogonal.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ADR 174 — Aggregate roots and relation strategies

> **Partial supersession:** The **relation strategy** design (`"strategy": "reference" | "embed"`) in this ADR has been superseded by [ADR 177 — Ownership replaces relation strategy](ADR%20177%20-%20Ownership%20replaces%20relation%20strategy.md). Embedding is now expressed via `"owner": "ParentModel"` on the owned model, with `storage.relations` on the parent mapping the relation to its physical location. The **aggregate roots** design (`roots` section) remains unchanged.

## At a glance

A User model as an aggregate root with a referenced relation (Post) and an embedded relation (Address). Post is also an aggregate root. Address is not — it only exists inside User documents.
Expand Down Expand Up @@ -180,6 +182,7 @@ This design means:

- [ADR 172 — Contract domain-storage separation](ADR%20172%20-%20Contract%20domain-storage%20separation.md) — why `model.storage` stays on the model
- [ADR 173 — Polymorphism via discriminator and variants](ADR%20173%20-%20Polymorphism%20via%20discriminator%20and%20variants.md) — why strategy labels are problematic
- [ADR 177 — Ownership replaces relation strategy](ADR%20177%20-%20Ownership%20replaces%20relation%20strategy.md) — supersedes the relation `strategy` design; `owner` on models replaces `strategy` on relations
- [design-questions.md § DQ #1](../../planning/mongo-target/1-design-docs/design-questions.md) — embedded documents resolution
- [cross-cutting-learnings.md § learning #1](../../planning/mongo-target/cross-cutting-learnings.md) — explicit aggregate roots
- [cross-cutting-learnings.md § learning #5](../../planning/mongo-target/cross-cutting-learnings.md) — models are entities, not just data descriptions
Loading
Loading