Skip to content

Commit 6f080b9

Browse files
authored
Merge pull request #20 from metaobjectsdev/feat/fr-032-canonical-fqn-refs
FR-032 canonical FQN refs (5 ports) + FR-024 entity surfaces + api-docs backlog
2 parents 315dcc0 + 07b52b8 commit 6f080b9

642 files changed

Lines changed: 16749 additions & 2090 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,10 +418,12 @@ Preserve the following contracts exactly across all language ports:
418418

419419
**Metamodel subtype vocabularies (must be identical across languages):** the `registry-conformance` gate (`fixtures/registry-conformance/`) is the structural enforcer of this rule — each port emits its registry as a canonical manifest byte-matched to `expected-registry.json`. **All five ports (TS / C# / Java / Kotlin / Python) are live + green** (SP-G Java/Kotlin reconciliation complete; the JVM runners compose from the defined metamodel provider set so codegen-base/om classpath SPI does not pollute the measured vocabulary). See `fixtures/registry-conformance/README.md`.
420420
- Filter operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `like`, `isNull`
421+
- Object subtypes: `entity` (owns data: own identity, writable sources, lifecycle), `value` (pure shape: NO identity, NO source, ever; constructed — by caller/assembly/embedding — never populated; may `extends` entity fields for shape), `projection` (derived read-only representation: fields `extends`-bound / origin-derived / self-declared-under-external-assembly, all read-only at subtype level; identity optional and MUST extend an entity identity; sources restricted to read-only `@kind`s; the declared field set IS the exposure — inclusive list, fail-closed). A field carrying `origin.*` is derived ⇒ read-only wherever it lives (incl. on entities). An entity's primary source must be a writable `@kind` (read-only kinds only in read role). See [ADR-0028](spec/decisions/ADR-0028-object-taxonomy-projection-value-purity.md). (FR-024 — planned; not yet in `expected-registry.json`.)
421422
- Source subtypes: `rdb` (paradigm; ADR-0007). The pre-v2 `dbTable`/`dbView` subtypes are RETIRED — `source.rdb` + `@kind: table|view|materializedView|storedProc|tableFunction` is the form, with read-only-ness derived from `@kind`. Multi-source via `@role` (exactly one `primary` per object). Source physical name = `@table` (NOT `@name`); field physical name = `@column` (renamed from `@dbColumn`). Referential actions on relationships: `@onDelete` / `@onUpdate`.
422423
- Origin subtypes: `passthrough`, `aggregate`
423424
- Relationship subtypes: `association`, `aggregation`, `composition`. Cardinality via `@cardinality: one|many`; target via `@objectRef`. **M:N (FR-018) slim vocabulary:** `@cardinality: "many"` + `@objectRef` (target) + `@through` (the junction/through entity — a third entity that MUST declare two `identity.reference` children, one per FK side). The relationship's FK fields are **derived** from those references (the `identity.reference` SSOT for FK direction), never restated. `@sourceRefField` (optional) disambiguates a *directed* self-join by naming the source-side FK field on the junction (the other reference is the target side). `@symmetric` (optional boolean) marks an *undirected* self-join (union-on-read) — valid only when `@objectRef` == the declaring entity, and mutually exclusive with `@sourceRefField`. The pre-FR-018 `@joinEntity`/`@joinFields` attrs are REMOVED. Validation errors: symmetric-on-hetero / symmetric+sourceRefField → `ERR_BAD_ATTR_VALUE`; junction-missing-two-references / sourceRefField-not-matching / M:N-attr-on-1:N → `ERR_INVALID_RELATIONSHIP`.
424425
- Layout subtypes: `dataGrid`
426+
- API subtypes: `api.base` / `api.operational` (request/response surface; subtype axis = interaction model, NEVER protocol — protocol lives in `binding.*` per operation: `rest` now, `messaging`/`grpc` reserved). Children: `operation.query` (outputRef → `object.projection`) / `operation.command` (inputRef → `object.value`, may also outputRef). Derived CRUD (FR-008/009) stays the zero-config default; declared `api` extends it. Org-tier modeling (application/service/network/deployment) stays OUT of core — provider SPI, FQN references. See [ADR-0030](spec/decisions/ADR-0030-declared-api-surface-and-org-tier-boundary.md). (FR-024 — planned; not yet in `expected-registry.json`.)
425427
- Currency attrs: `@currency` (ISO 4217), `@locale` (BCP 47)
426428
- Schema attrs: `@schema` on `source.rdb` (DB schema name; Postgres default `public`, SQLite rejects non-default values)
427429
- Storage attrs: `@storage` on `field.object` (with `@objectRef`) — values `flattened` / `jsonb` / `subdocument`. Unifies "owned types" (flattened storage) and "structured JSONB" (jsonb storage). Defaults to single-jsonb-column when absent (back-compat).
@@ -436,6 +438,7 @@ Preserve the following contracts exactly across all language ports:
436438
**Grammar:**
437439
- Dotted-path syntax for `@via`: `"Program.weeks"` or `"Program.weeks.workouts"`.
438440
- Dotted-path syntax for `@of`: `"Week.id"`.
441+
- `extends` may target a nested child to ANY depth: `Customer.id`, cross-package `acme::sales::Customer.id`, triple-nest `Customer.priceCents.display` (object → field → view). Addressing model: a package qualifies the ROOT-level node only; each subsequent dotted segment traverses CHILD NAMES (nested children carry BARE names — packages never fold onto non-root nodes). Intermediate segments resolve by unique name (cross-type collision = unresolved); the FINAL segment is type-scoped to the referrer (a field resolves fields; an identity identities; a view views). `extends` is THE inheritance mechanism; `origin.*` never inherits. `@via` lives on `origin.*` only and may be omitted ONLY when exactly one single-hop relationship leads from the base entity to the `from`/`of` entity (multi-hop always explicit). See [ADR-0029](spec/decisions/ADR-0029-entity-child-extends-and-via-inference.md).
439442
- Package segments: `::` separator — `acme::common::id`.
440443
- **Canonical JSON:** reserved structural keywords are bare (`name`/`package`/`extends`/`abstract`/`overlay`/`isArray`/`children`/`value`); inline attributes are `@`-prefixed. `@`-prefixing a reserved word (e.g. `@isArray`) is invalid (`ERR_RESERVED_ATTR`). **YAML authoring is sigil-free** — bare attrs; the desugar re-adds `@` when lowering to canonical JSON; canonical JSON is the on-disk interchange (YAML is the universal authoring front-end across ports). See [ADR-0006](spec/decisions/ADR-0006-ai-first-yaml-authoring.md).
441444

agent-context/skills/metaobjects-authoring/SKILL.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ subtype — there is **no** separate `subType` body key.
3434
{ "object.entity": { "name": "User" } }
3535
{ "field.string": { "name": "email", "@required": true } }
3636
{ "field.enum": { "name": "status", "@values": ["OPEN", "CLOSED"] } }
37-
{ "identity.primary": { "@fields": ["id"] } }
37+
{ "identity.primary": { "name": "id", "@fields": ["id"] } }
3838
```
3939

4040
A complete entity in canonical JSON:
@@ -52,7 +52,7 @@ A complete entity in canonical JSON:
5252
{ "field.long": { "name": "id" } },
5353
{ "field.string": { "name": "name", "@required": true, "@maxLength": 200 } },
5454
{ "field.string": { "name": "bio", "@maxLength": 2000 } },
55-
{ "identity.primary": { "@fields": ["id"], "@generation": "increment" } }
55+
{ "identity.primary": { "name": "id", "@fields": ["id"], "@generation": "increment" } }
5656
]
5757
}
5858
}
@@ -74,7 +74,7 @@ metadata:
7474
- field.long: { name: id }
7575
- field.string: { name: name, required: true, maxLength: 200 }
7676
- field.string: { name: bio, maxLength: 2000 }
77-
- identity.primary: { fields: id, generation: increment }
77+
- identity.primary: { name: id, fields: id, generation: increment }
7878
```
7979
8080
## Reserved structural keys vs. attributes
@@ -206,8 +206,8 @@ reference for navigation/typing/codegen only. Referential actions
206206
`relationship.*` node (see Relationships below).
207207

208208
```json
209-
{ "identity.primary": { "@fields": ["id"], "@generation": "increment" } }
210-
{ "identity.secondary": { "@fields": ["email"] } }
209+
{ "identity.primary": { "name": "id", "@fields": ["id"], "@generation": "increment" } }
210+
{ "identity.secondary": { "name": "byEmail", "@fields": ["email"] } }
211211
{ "identity.reference": { "name": "fkAuthor", "@fields": ["authorId"], "@references": "Author", "@enforce": true } }
212212
```
213213

@@ -251,8 +251,15 @@ each with a `@role`, exactly one `primary`.
251251
{ "source.rdb": { "@kind": "view", "@table": "v_author", "@schema": "blog" } }
252252
```
253253

254-
A `view`-kind entity's fields carry `origin.*` children (`passthrough` /
255-
`aggregate` / `collection`) declaring where each value comes from.
254+
**An entity's PRIMARY source must be writable** (`table`) — read-only kinds are
255+
legal only in non-primary roles (e.g. table `primary` + view `replica` for
256+
read-through). A derived read model over a view/proc is an **`object.projection`**
257+
(FR-024): its fields `extends` entity fields (`extends: "Author.id"` — dotted
258+
child traversal, package only on the root segment) and/or carry `origin.*`
259+
children (`passthrough` / `aggregate` / `collection`) declaring assembly; its
260+
identity passes through via `extends` (`identity.primary: { name: id, extends:
261+
"Author.id" }`); it is read-only by construction and the declared field set IS
262+
the exposure (fail-closed).
256263

257264
## Abstracts + `extends` (deferred resolution) + `overlay`
258265

@@ -275,7 +282,7 @@ change.
275282
children:
276283
- source.rdb: { table: authors }
277284
- field.string: { name: name, required: true }
278-
- identity.primary: { fields: id }
285+
- identity.primary: { name: id, fields: id }
279286
```
280287

281288
Resolution facts:

docs/ROADMAP-PROCESS.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Roadmap process — keeping GitHub in sync with the roadmap
2+
3+
How we track planned work, and how the GitHub surfaces stay in sync with the canonical roadmap.
4+
5+
## The model: one source of truth, mirrored
6+
7+
**`spec/roadmap.md` is the single source of truth.** It holds the **FR registry** (every feature
8+
request + status + target release + tracking issue), the **Shipped / Active / Planned** detail, and
9+
the **Release plan (1.0 → 1.x)**. Every roadmap fact lives here; the GitHub surfaces *mirror* it.
10+
11+
Three GitHub surfaces mirror the roadmap, each with one job:
12+
13+
| Surface | Role | Maps to |
14+
|---|---|---|
15+
| **Milestones** | the releases | `1.0` `1.1` `1.2` `1.3` `1.4` `1.x (later)` (= the Release plan) |
16+
| **Issues** | one per FR (execution unit) | a row in the FR registry; label `FR` + an `area:*` label; milestone = target release; body links the design spec |
17+
| **Project board** | a saved view (Now / Next / Later, grouped by milestone) | a view over the `FR`-labelled issues |
18+
19+
Design depth (the *why* and *how*) lives in `docs/superpowers/specs/*` and ADRs in
20+
`spec/decisions/*` — issues link to these, they are not duplicated into issues.
21+
22+
## Current GitHub state (bootstrapped 2026-06-13)
23+
24+
- **Milestones:** `1.0`(#1) `1.1`(#2) `1.2`(#3) `1.3`(#4) `1.4`(#5) `1.x (later)`(#6).
25+
- **Labels:** `FR`, `area:metamodel`, `area:serializers`, `area:ui`, `area:grid`, `area:perf`,
26+
`area:tooling`, `area:codegen`.
27+
- **Issues:** one per planned FR — FR-019→#5, FR-020→#6, FR-021→#7, FR-022→#8, FR-023→#9,
28+
FR-024→#10, FR-025→#11, FR-026→#12, FR-027→#13, FR-028→#14, FR-029→#15, FR-030→#16,
29+
FR-031→#17, MCP→#18.
30+
31+
## Sync rules (do these together, in the same change)
32+
33+
1. **Adding an FR.** Allocate the next FR number (highest in the registry + 1 — currently the next
34+
is **FR-032**). In the *same* PR: add a registry row + a `## Planned` entry + a Release-plan
35+
slot in `spec/roadmap.md`, write/locate the design spec under `docs/superpowers/specs/`, and
36+
create the issue:
37+
```sh
38+
gh issue create --title "FR-0NN — <title>" \
39+
--body "<one-line summary>
40+
41+
**Spec / design:** <blob URL of the spec>
42+
**Target release:** <milestone>
43+
44+
_Tracked in spec/roadmap.md (FR registry); status lives there._" \
45+
--milestone "<1.x>" --label "FR,area:<x>"
46+
```
47+
Put the issue number back into the registry row.
48+
49+
2. **Changing status or target release.** Update the registry row (status/release) **and** move the
50+
issue's milestone (`gh issue edit <n> --milestone "<new>"`). The roadmap row is authoritative; if
51+
the two disagree, the roadmap wins and the issue is corrected.
52+
53+
3. **Shipping an FR.** Move it from Planned → Shipped in `spec/roadmap.md` (with the ship note), flip
54+
the registry status to ✅, and `gh issue close <n>` with a comment linking the shipping commit/PR.
55+
56+
4. **Cutting a release.** Close the milestone when its issues are done; bump the registry rows to ✅.
57+
58+
**Rule of thumb:** a PR that changes an FR's existence, status, or release **must** edit
59+
`spec/roadmap.md`. The GitHub change (issue/milestone) accompanies it but is never the only record.
60+
61+
## Project board — one-time setup (manual)
62+
63+
The board could not be created via API from CI: the available token has
64+
`repo`/`admin:org`/`workflow` scopes but **not `project`** (Projects v2 mutations require the
65+
`project` scope). To create + auto-populate it once:
66+
67+
1. (If scripting later) grant the scope: `gh auth refresh -s project,read:project`.
68+
2. In the org → **Projects → New project → Board**, name it **"MetaObjects Roadmap"**.
69+
3. Add a built-in **workflow → "Auto-add to project"** filtering `repo:metaobjectsdev/metaobjects
70+
is:issue label:FR` — every `FR` issue then flows in automatically (no per-issue wiring, and new
71+
FR issues self-add).
72+
4. Group the board **by Milestone** (gives the Now/Next/Later release columns) and add a `Status`
73+
single-select (Todo / In progress / Done) if desired.
74+
75+
Because the board auto-adds by the `FR` label, the **issues are the durable record** and the board
76+
is a disposable view — losing/recreating it costs nothing.
77+
78+
## Public roadmap (website)
79+
80+
A curated, adopter-facing roadmap on `metaobjects.dev/roadmap` (Now / Next / Later) is summarized
81+
**from** this file — it is not a separate source of truth. Refresh it when the Release plan changes.

0 commit comments

Comments
 (0)