Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3090a9a
docs: metadata source resolution — prior art, design, and phase-1 plan
dmealing Aug 18, 2026
fc13abc
feat(metadata): register phase-1 source-resolution error codes across…
dmealing Aug 18, 2026
2ff8a1d
feat(sdk): package-pattern scope engine (* = one segment, ** = one or…
dmealing Aug 18, 2026
daf69ac
test(conformance): scope-pattern corpus pins * and ** semantics cross…
dmealing Aug 18, 2026
b650ae1
feat(sdk): resolve a source SET to a canonically-sorted file list
dmealing Aug 18, 2026
cb08e94
fix(sdk): make the overlapping-file spec attribution order-independent
dmealing Aug 18, 2026
de92960
feat(sdk): config accepts a source SET, a scope block, and migrate.scope
dmealing Aug 18, 2026
e32febb
fix(sdk): restore .strict() on SourceSpecSchema arms; fail closed on …
dmealing Aug 18, 2026
9be8d35
feat(sdk): nearest-ancestor config discovery, bounded by the repo root
dmealing Aug 18, 2026
4300b7b
feat(sdk): resolveCollection() — one authority for where metadata lives
dmealing Aug 18, 2026
6c6f8c0
fix(sdk): resolveCollection propagates a malformed config, never swal…
dmealing Aug 18, 2026
4651484
test(sdk): pin order independence — permuted source sets serialize by…
dmealing Aug 18, 2026
814719e
fix(sdk): order-independence gate — assert content resolution, not si…
dmealing Aug 18, 2026
b3082e1
fix(sdk): correctness fixes from code review — symlinks, scope patter…
dmealing Aug 18, 2026
f496b1d
refactor(sdk): dedupe fileExists/CONFIG_FILE, thread config-existence…
dmealing Aug 18, 2026
9200627
feat(sdk): loadMemory accepts an explicit resolved file set
dmealing Aug 18, 2026
44a8bc5
feat(cli): route gen/docs/export and the project probe through resolv…
dmealing Aug 18, 2026
833e430
test(cli): add sdk to ensureFreshDist gate
dmealing Aug 18, 2026
218a29f
fix(cli): detect-stack reads the resolved collection, fixing nested-s…
dmealing Aug 18, 2026
ad4695c
fix(metadata): add as const to error code array in typecheck test
dmealing Aug 18, 2026
8375772
feat(cli): migrate.scope narrows both sides so unowned tables are nev…
dmealing Aug 18, 2026
dfb31c5
feat(codegen): collection scope filters generated output, and verify …
dmealing Aug 18, 2026
9a95a58
test(sdk): dogfood reach+scope against the in-repo examples metadata …
dmealing Aug 18, 2026
0fc08d9
test(scope-conformance): pin case-sensitive scope matching
dmealing Aug 18, 2026
ceeae33
docs: metadata sources, scope, discovery, and the vendoring workflow
dmealing Aug 18, 2026
fece4e5
fix(sdk): resolved sources keep the loader's per-level walk order
dmealing Aug 18, 2026
202ad65
fix(migrate): a migrate.scope matching nothing no longer proposes DRO…
dmealing Aug 18, 2026
181d6bb
fix(cli): route prompt-snapshot through resolveCollection; close two …
dmealing Aug 18, 2026
0c8fd13
fix(cli): read per-project config from the resolved config dir, not a…
dmealing Aug 18, 2026
5cbdeac
docs: an Upgrading section for the three adopter-visible changes
dmealing Aug 18, 2026
e929bd8
fix: the seven fix-before-merge minors from the whole-branch review
dmealing Aug 18, 2026
5ee93a8
refactor(cli): compute the snapshot gate's schema scope once
dmealing Aug 18, 2026
bdef4fc
fix: nine correctness findings from the whole-branch quality pass
dmealing Aug 18, 2026
aab1bfa
refactor: collapse the rules this branch enforced by repetition
dmealing Aug 18, 2026
ab6fc04
test(cli): one fixture for the two migrate.scope integration suites
dmealing Aug 18, 2026
ce5724d
fix(cli): the docs-site theme is read where --scaffold-site writes it
dmealing Aug 18, 2026
39678a7
fix(sdk): the config is the only authority — at the walk and at the l…
dmealing Aug 18, 2026
e550fd6
fix(cli): what the CLI prints must not assert a directory the project…
dmealing Aug 18, 2026
3c702ec
docs: state the rule where the next reader will meet it
dmealing Aug 18, 2026
066fded
test(sdk): make "metaobjects/ is a config default" enforceable, not a…
dmealing Aug 18, 2026
d66b4e0
fix: two minors from the branch re-review
dmealing Aug 18, 2026
8d2ffc6
fix: close five residuals from the branch re-review
dmealing Aug 18, 2026
4baf19a
no-mistakes(review): address stale changelog bullet and migrate scope…
dmealing Aug 19, 2026
232a1af
no-mistakes(document): Updated requirements.md to clarify metaobjects…
dmealing Aug 19, 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
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ import { EntityFetcherProvider, EntityGrid } from "@metaobjectsdev/tanstack";
- **Codegen substrate**: ts-poet for greenfield emit, ts-morph for in-place edits, Biome for format pass, `git merge-file --diff3` for hand-edit-preserving regen.
- **Runtime substrate**: Kysely for TS (user-provided connection, async-only).
- **Migration substrate**: Postgres + SQLite for TS v0.3.
- **Metadata location**: resolved via `resolveCollection()` (`@metaobjectsdev/sdk`) — the single authority. `metaobjects/` is the **default value of `sources`** and nothing else: no other module, command or user-facing message may assert that a directory of that name exists or is where metadata lives. Exactly six sites may name it — `sdk/src/metadata-files.ts` (`DEFAULT_METADATA_DIR`, its single definition), `sdk/src/sources.ts` (`DEFAULT_SOURCES`, **the** default), `sdk/src/collection.ts` (inside `resolveCollection`, *applying* that default), `sdk/src/index.ts` (the barrel re-export of the constant, no use), `cli/src/commands/init.ts` (the scaffolder **writing** the layout), and `sdk/src/agent-docs/body.ts` (the agent-docs prose `meta init` scaffolds beside that layout). Enforced by `sdk/test/no-hardcoded-metadata-dir.test.ts`, whose allowlist demands a written reason per entry. See [docs/features/metadata-sources.md](docs/features/metadata-sources.md).

## Explicitly out of scope

Expand All @@ -224,6 +225,8 @@ import { EntityFetcherProvider, EntityGrid } from "@metaobjectsdev/tanstack";

**Default convention**: one file per domain concept under `metaobjects/`. Multiple objects per file when they share a domain. Projections (`source.dbView`) live inline with their base entity.

`metaobjects/` is the **default value** of `sources` in `.metaobjects/config.json` — never a requirement. A project declaring `sources` explicitly can point anywhere (and need not have such a directory at all); `"sources": []`, which is what `meta init` scaffolds, takes the default.

```
project-root/
├── metaobjects/ # VISIBLE — entity declarations
Expand Down
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,59 @@ the local release path published `docs-site` ahead of `metadata` and `render`, t
packages it depends on. The tier is declared now, and an undeclared one is an error
instead of an accidental position.

### Metadata source resolution — adopter-visible changes

`.metaobjects/config.json` gains `sources`, `scope` and `migrate.scope`, and every
command resolves where metadata lives through one authority instead of reading a
hardcoded `metaobjects/` directory. A project with one config at its root, no
`sources` and no `scope` resolves the same files, generates the same code and emits
the same migrations. Three changes are visible even to that project. Adopter guide:
[`docs/features/metadata-sources.md`](docs/features/metadata-sources.md#upgrading).

- **The workspace `extends:` walk is retired.** `loadMemory` used to have a second,
undocumented way of finding metadata: a `package.meta.json` declaring `extends:`
dependencies, inside a discoverable workspace (`pnpm-workspace.yaml` or
`package.json` `workspaces`), pulled in each peer package's `metaobjects/`
directory first, in topological order. Every CLI read path now resolves through
`sources`, which does no such walk. It fails LOUDLY — `ERR_UNRESOLVED_SUPER`
naming the target it cannot find, never a half-resolved model — and the
replacement is an explicit `{ "path": "../shared-model/metaobjects" }` source,
which works in any layout and needs no topological ordering.
- **`.metaobjects/config.json` rejects unknown keys.** `ConfigSchema` is `.strict()`
at every level, so a key that was previously stripped in silence is now a load
error naming the key. Silently dropping a key means the setting you wrote does not
exist: `{ "migrate": { "scopee": [...] } }` used to mean *unscoped*, governing
every table in a database you were trying to share.
- **`ExpectedView.fqn` is required.** On the public `@metaobjectsdev/codegen-ts`
export, the declaring object's fully-qualified name is no longer optional —
`migrate.scope` decides ownership on that name, and a view arriving without one
cannot be scoped at all. `buildProjectionViews` already supplies it; only
hand-built `ExpectedView` values need the field added.
- **`meta export` output order changed, and `_pending/` is excluded.** `export` now
serializes the file set `resolveCollection` resolved rather than scanning a
directory through `DirectorySource`, so siblings emit files-before-subdirectories
(the overlay-safe order the loader has always been given) instead of a flat
basename sort, and staged `_pending/` files — skipped by every other read path —
are no longer exported. The canonical JSON content is unchanged; a committed
export diffed against a fresh one shows a reordering.
- **The migrations directory follows the project root.** `.metaobjects/migrations`
and the schema snapshot resolve from the directory whose `.metaobjects/config.json`
governs the run, found by walking up from the working directory. `meta migrate
apply-pending` and `--rollback` load no metadata and previously used the working
directory unconditionally, so a subdirectory holding a ledger but no config of its
own now replays the project root's history. `migrate` says so out loud when the
resolved directory differs from `<cwd>/.metaobjects/migrations` and that local
directory exists; `--out-dir` overrides, and giving the subdirectory its own
`.metaobjects/config.json` makes it a project root.
- **A project boundary is a `.metaobjects/config.json` — a bare `metaobjects/`
directory is not one.** Discovery walks up for a config and stops at nothing
else short of the `.git` boundary, so a command run inside a nested directory
that holds metadata but declares no config of its own resolves the nearest
ancestor config — adopting its `sources` and `outDir`. `metaobjects/` is the
default *value* of `sources`, so a directory of that name says nothing about
whether a project lives there. If a subdirectory should own its metadata, give
it a config: `meta init` writes one, and a `"sources": []` config is enough to
claim the directory and take the default.

## [0.23.2] — npm `0.23.2` · PyPI `0.23.2` · NuGet `0.23.2` · Maven `7.23.2`

Expand Down
30 changes: 27 additions & 3 deletions docs/CONFORMANCE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Conformance coverage

The MetaObjects standard ships **19 shared conformance corpora** under
The MetaObjects standard ships **20 shared conformance corpora** under
[`fixtures/`](../fixtures/). Every port runs every corpus that is *applicable to
it* and asserts the same expected behaviour against the same fixtures. **This page
is the inverse index**: fixture → feature doc + per-port pass status, and it is the
Expand Down Expand Up @@ -42,6 +42,7 @@ regenerate with `ls -d fixtures/<corpus>/*/ | wc -l`.
| [`fixtures/template-output-render-conformance/`](../fixtures/template-output-render-conformance/) | 5 | ✓ | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/generator-registry-conformance/`](../fixtures/generator-registry-conformance/) | 1 canonical manifest | ✓ | ✓ | ✓ | ✓ | ✓ |
| [`fixtures/provider-composition-conformance/`](../fixtures/provider-composition-conformance/) | 9 (5 error-shape + 4 compose-load) | ✓ | ✓ | — (JVM registry via Java) | ✓ | ✓ |
| [`fixtures/scope-conformance/`](../fixtures/scope-conformance/) | 10 cases | ✓ (reference implementation) | — | — | — | — |
| [`fixtures/agent-context-conformance/`](../fixtures/agent-context-conformance/) | 4 | ✓ (the emitter is TS-owned) | — | — | — | — |
| [`fixtures/metamodel-docs/`](../fixtures/metamodel-docs/) | 1 | ✓ (docs emit is TS-owned) | — | — | — | — |

Expand Down Expand Up @@ -138,10 +139,33 @@ inheritance), `m2m/` (3), `jsonb/` (2, typed value-object columns) and
Kotlin, C#, Python — run it in BOTH lanes: a hand-rolled reference server and
the port's own GENERATED API artifact booted over HTTP.

### `fixtures/scope-conformance/` (10 cases)

All 10 cases → [features/metadata-sources.md](features/metadata-sources.md) (the
`scope` pattern grammar). The corpus is file-shaped: one committed `cases.json`,
read directly by every port's runner, with no per-port fixture and no ledger.

It pins the semantics of a consumer's `include`/`exclude` scope over
fully-qualified names — **`*` matches within one `::` segment and never crosses
it; a segment that is exactly `**` matches one or more whole segments (so
`acme::**` does not match the bare `acme`); every other character is literal,
regex metacharacters included; an absent or empty `include` means everything;
multiple `include` patterns are a union and `exclude` is applied after it; and
matching is case-sensitive.** These are exactly the rules four independent
implementations would otherwise each get slightly wrong — the failure mode that
produced the cross-port `LIKE`/`ILIKE` divergence fixed in 0.21.6.

**TypeScript is the only port with a runner today.** The reference implementation is
[`server/typescript/packages/sdk/src/scope.ts`](../server/typescript/packages/sdk/src/scope.ts)
(`compilePattern` / `compileScope` / `matchesScope`), and the corpus was authored
against it. Java, Kotlin, C# and Python have no runner yet; when each gains one, this corpus is
what it implements against — it exists now precisely so those four land on one
grammar rather than four.

## Orphaned fixtures (tested but not yet documented)

The fixtures in the six corpora mapped above (metamodel 255 + yaml 15 + verify 31
+ render 15 + persistence 33 + api-contract 41) each map to a feature doc. None
The fixtures in the seven corpora mapped above (metamodel 255 + yaml 15 + verify 31
+ render 15 + persistence 33 + api-contract 41 + scope 10) each map to a feature doc. None
are orphaned today. The remaining corpora in the totals table gate tooling
contracts (registry manifests, provider composition, agent context, docs emit)
rather than user-facing metamodel behaviour, so they have no feature-doc row.
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ docs/
│ ├── downstream-metadata-decisions.md # guidance for adopters extending the metamodel
│ ├── generated-mutations.md # generated POST/PATCH mutation surface
│ ├── image-upload.md # view.image form control (TS-web)
│ ├── metadata-sources.md # where metadata comes from: sources, scope, discovery
│ └── own-your-codegen.md # scaffold-and-own generator ownership (ADR-0034)
└── ports/ # one file per language/framework port
├── typescript.md
Expand All @@ -58,6 +59,7 @@ this tree is documentation, not the source of truth.
| Understand what `object.entity`, `source.rdb`, `template.prompt` mean | [`features/`](features/) |
| Compare what TS vs Java vs Kotlin vs C# vs Python emit for the same metadata | any [`features/*.md`](features/) — every feature shows all five ports side-by-side |
| Author metadata in YAML instead of JSON | [`features/yaml-authoring.md`](features/yaml-authoring.md) |
| Point the toolchain at metadata that lives somewhere other than `metaobjects/`, or scope what a project generates and migrates | [`features/metadata-sources.md`](features/metadata-sources.md) |
| Record what the system is supposed to do, and stop agents reviving retired features | [`features/requirements.md`](features/requirements.md) |
| Wire prompt construction (FR-004) | [`features/templates-and-payloads.md`](features/templates-and-payloads.md) |
| Share a metadata shape across multiple instances (abstracts, `extends:`) | [`features/abstracts-and-inheritance.md`](features/abstracts-and-inheritance.md) |
Expand Down
Loading
Loading