Skip to content

feat(domains): domain.yaml as bounded context with exports, suppliers, glossary (FEAT-035) - #27

Open
easel wants to merge 5 commits into
mainfrom
worktree-ddd-domains
Open

easel wants to merge 5 commits into
mainfrom
worktree-ddd-domains

Conversation

@easel

@easel easel commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

What changed

Domain-Driven Design integration for tablespec. A domain is a directory of UMF tables with a domain.yaml: the bounded context. Its exports are the published language (versioned), its suppliers are the context map (advisory patterns, with consumer pins), and its glossary is the vocabulary that term fields resolve against and that the guidebook and prompts show.

name: claims
owner: claims-data-team
version: 1.0.0                    # published-language version, MAJOR.MINOR.PATCH
exports: [medical_claims]         # each exported table must declare a primary key
glossary: glossary.yaml
suppliers:
  eligibility:
    pattern: customer_supplier    # advisory; only separate_ways is enforced
    consumes: [member]
    version: ">=1.0.0,<2.0.0"     # accepted supplier versions

Models (ADR-020)

  • models/domain.py: DomainMetadata, SupplierRelationship, IntegrationPattern, Glossary; load_domain (name must equal directory).
  • ForeignKey: references_domain + integration. extra="forbid", so an unknown key fails on load instead of vanishing on save. Legacy references_pipeline is read the same way and never rewritten: a spec that does not use references_domain loads, saves, and compiles byte-for-byte as before.
  • UMF.term / UMFColumn.term; canonical_name untouched.
  • umf.schema.json regenerated and pinned by a new test (it was already stale).

Validation: one behavior, no mode

tablespec validate <path> validates every table beneath the path at any depth, and applies the rules between domains whenever a domain.yaml applies to the path (the path is a domain, is inside one, or has domains beneath it). Sibling domains are loaded from the directory that holds them, so validating one domain or one table still checks its references into the others. Findings are narrowed to what the path covers.

An earlier commit on this branch used a separate "domain mode". It was removed because it made a metadata file decide whether tables were validated at all, and reported a lone domain's suppliers as "not found". SD-035 records the rejected design.

Rules: DOM-EXPORT, DOM-SUPPLIER, DOM-XREF, DOM-TERM, DOM-WAYS, DOM-PIN, DOM-LOAD, DOM-COMPAT (with --baseline), warnings DOM-VERSION, DOM-DRIFT. No new CLI command.

Published-language versioning (ADR-021)

domain.version is strict SemVer core; suppliers.<d>.version is a small explicit range grammar (not PEP 440). tablespec validate <path> --baseline <old> compares each covered domain's exports across revisions and fails DOM-COMPAT when a breaking change lacks a MAJOR bump. Read-only; no changelog change.

Integration patterns are advisory: only the consumer declares an edge, so bilateral or supplier-side facts cannot be verified. Only separate_ways is enforced. This and the deferral of UMF.kind and subdomain kind came out of a Codex adversarial review of the follow-up plan (BLOCK, 11 blocking findings, all accepted): docs/helix/06-iterate/alignment-reviews/AR-2026-09-15-ddd-follow-up-plan-review.md.

Guidebook and prompts

domains.html domain map (supplier/consumer wording; upstream/downstream stay reserved for lineage per ADR-018). Table pages show each cited term's definition. generate_documentation_prompt(..., glossary=) lists only cited terms.

Docs and governance

ADR-020, ADR-021, FEAT-035, US-051, US-052, SD-035 (surface contract), PRD FR-24.1–24.7, registry/index rows, docs/guide/domains.md, microsite concepts/domains page with a DDD-to-tablespec mapping and sources, CLI reference.

User-facing impact

If you add no domain.yaml:

  • Behavior change: directory validation is now recursive. A repo whose tables sit in group folders previously validated zero tables in silence; it will now validate them and may surface real failures. validate_pipeline(..., recursive=False) keeps the old lookup for API callers.
  • Breaking: an unknown key on a foreign key now fails at load (previously dropped in silence). No spec in this repo is affected. Check production specs before merging; the one-liner is in the PR conversation.
  • Everything else (artifacts, guidebook, prompts, routing, canonical_name, command count) is unchanged.

If you adopt domains: cross-domain references are checked, breaking export changes require a MAJOR bump that consumers must accept, and the guidebook gains the map and term definitions.

Decisions a reviewer should weigh

  • Unit is named domain, not context ("context" is the line-of-business axis in this codebase).
  • Domains never drive routing (ADR-013 preserved).
  • Recursive directory validation changes results for existing grouped layouts.
  • One commit carries a throwaway wip: subject; a squash merge makes it moot.

Verification

Check Result
Full pytest suite 3991 passed, 119 skipped
ruff format --check / ruff check clean
pyright src 0 errors
Microsite content / link-check 16 / 11 passed

🤖 Generated with Claude Code

easel and others added 5 commits September 15, 2026 23:06
…, glossary (FEAT-035)

A domain is a directory of UMF tables with a domain.yaml: the DDD bounded
context. Its exports are the published language (each exported table must
declare a primary key), its suppliers are the context map (with a DDD
integration pattern per edge), and its glossary is the vocabulary that new
`term` fields on tables and columns resolve against.

Models
- models/domain.py: DomainMetadata, SupplierRelationship, IntegrationPattern,
  Glossary/GlossaryTerm, load_domain (name must equal directory), load_glossary.
- ForeignKey: references_domain + integration; legacy references_pipeline is
  reconciled both ways, disagreement is an error, cross_pipeline forced true;
  extra="forbid" so unknown keys fail on load instead of vanishing on save.
- UMF.term / UMFColumn.term persisted through split format; canonical_name
  untouched.
- umf.schema.json regenerated; new test pins it to UMF.model_json_schema().

Validation
- domains.py: find_domain_dirs (root + direct children), load_domain_dir,
  discover_domains.
- domain_validator.py: DOM-EXPORT, DOM-SUPPLIER, DOM-XREF (cross-domain FK
  must target an exported table's PK via a declared supplier), DOM-TERM,
  DOM-DRIFT (warning), DOM-LOAD.
- validator.validate_domain_root; `tablespec validate <root>` enters domain
  mode when the root is or contains domain.yaml directories.

Guidebook
- domain_map.py renders domains.html (owners, exports, supplier edges,
  cross-domain references) linked from the top index. Supplier/consumer
  wording; upstream/downstream stay reserved for lineage (ADR-018).
- reverse_lineage reads references_domain / target_table.

Governance and docs
- ADR-020, FEAT-035, US-051, SD-035 (surface contract), PRD FR-24.1..24.5,
  registry + frame index rows, implementation-plan module map, brand-voice
  approved terms, docs/guide/domains.md, CLI reference, guidebook guide.

Domains do not drive catalog/schema routing (ADR-013 preserved).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sary surfaces (ADR-021)

Second FEAT-035 slice, re-scoped after a Codex adversarial review of the
DDD follow-up plan (BLOCK; 11 blocking findings, all accepted). Record:
docs/helix/06-iterate/alignment-reviews/AR-2026-09-15-ddd-follow-up-plan-review.md

Versioning
- domain.version is strict MAJOR.MINOR.PATCH; suppliers.<d>.version is a
  small explicit range grammar (>=, <=, ==, !=, >, < clauses), not PEP 440.
- DOM-PIN: supplier version outside the consumer's range, or unversioned
  supplier pinned. DOM-VERSION (warning): exports without a version.
- `tablespec validate <root> --baseline <old>`: pairs domains by name,
  compares the union of export lists, runs check_compatibility on tables
  exported on both sides (forward-only breaks downgraded to warning), prints
  "Published-language changes", and fails DOM-COMPAT when a breaking change
  lacks a MAJOR bump or a version is missing on either side. Read-only; no
  changelog mutation; no new command.

Patterns
- Integration patterns are advisory (consumer-only declaration cannot
  establish bilateral or supplier-side facts). Only separate_ways is
  enforced: DOM-WAYS on a consumes list or a crossing foreign key.

Glossary surfaces
- render_table_page(glossary=...): cited terms render as chips with the
  definition as tooltip and an inline definition line; generator passes the
  owning domain's glossary.
- generate_documentation_prompt(umf_data, *, glossary=None): lists only the
  terms the table and columns cite, sorted, aliases canonicalized; output
  unchanged without a glossary.

Deferred (ADR-021 decision 7): UMF.kind entity/event/reference, subdomain
kind, executable conformist/anti-corruption/shared-kernel rules.

Docs and governance
- ADR-021, US-052 (AC1-AC5 cited by tests), FEAT-035 areas VER/GLOSS,
  SD-035 contracts (version grammar, rule IDs, published-language boundary,
  CLI, glossary markup), PRD FR-24.6/24.7, registry and index rows.
- Microsite concepts/domains page with DDD-to-tablespec mapping and sources
  (primary DDD sources separated from adjacent ideas); concepts card,
  link-check seed, Playwright coverage; CLI reference --baseline row.
- docs/guide/domains.md: versioning, glossary surfaces, mapping table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing-aware domain rules

`tablespec validate <path>` previously switched into a separate mode when a
domain.yaml sat at or directly under the path. That design was wrong in three
ways, each reproduced before the fix:

- A metadata file decided whether tables were validated at all. Directory
  validation looked one level down only, so tables organized into group
  folders (`eligibility/member`, `claims/medical_claims`) validated ZERO
  tables unless a domain.yaml happened to be present.
- Validating one domain directory loaded only what sat under that path, so
  its suppliers (one directory up) were reported as "not found"
  (spurious DOM-SUPPLIER and DOM-XREF).
- Validating a single table never checked that table's keys into other
  domains, and `--baseline` was silently ignored outside the mode.

Now there is one behavior:

- `validate_pipeline` finds table directories at any depth (hidden and
  vendored dirs skipped; no descent into a table dir). Direct children keep
  their bare `table_name` key; nested tables are `<parent path>/<table_name>`.
  `recursive=False` keeps the old one-level lookup for API callers.
- `domains.resolve_domain_scopes(path)` finds the domain roots a path
  involves: walking up when the path is a domain or inside one (stopping at
  a repository root), walking down otherwise. Each root's full sibling set is
  loaded, because a consumer's supplier lives next to it, not beneath it.
- `validator.validate_domain_scopes(path, baseline=)` runs the rules over the
  full sibling set and narrows findings to the domains, or the single table,
  the path covers. A path above several roots labels each domain with its
  root. A domain.yaml that fails to load is a DOM-LOAD error instead of a
  silent skip.
- `--baseline` resolves its roots the same way, pairs them with the current
  ones, covers a domain removed from a covered root, and prints a note when
  no domain.yaml applies on either side.
- Removed the unreleased `is_domain_root`, `validate_domain_root`, NO_DOMAIN.

Behavior change for existing users: directory validation is now recursive, so
a repo that silently validated nothing may surface real failures.

Docs: SD-035 CLI contract rewritten (and records the rejected mode design),
ADR-020 decision 7, F035-VAL-01/05, US-051-AC4, US-052-AC4, domains guide,
CLI guide, microsite CLI reference. No doc says "domain mode" any more.

Tests: tests/unit/test_validate_scopes.py pins each failure above plus
multi-root labelling, pruning, nested domain.yaml, DOM-LOAD, and baseline
pairing. Full suite: 3991 passed, 119 skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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