Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,25 @@ here.**

### Added

- **A model explorer site, built by `docsSiteFile()`.** The docs site now shows what the
software does and how the model is designed: a main page with count tiles and the feature and
quality cards with their roll-ups, a model map with slices (package, requirement subtree,
prompt) and colour-by (kind, API, claims, capability, status), a page per object, projection,
report, prompt and requirement with a one-hop mini-map, the traceability matrix, a trace from a
requirement to the files generated from it, the API index and a gaps page. Every page is drawn
from `explorer.json`, which the generator builds in the same run from the loaded model, the
requirement gate's ledger, the files the run wrote and the routes it mounts; it runs after the
other generators wherever it is listed. Declared status is a filled chip with its glyph, gate
verdicts are outlines, roll-ups are bars, and no colour reports a test result. Pages are marked
for Pagefind, work at phone width, depend on the model alone (so `meta verify --codegen` holds
them) and are link-checked. The graphs load Cytoscape.js from `assetBase`. `meta eject
docs-site` now copies the explorer's `explorer-*.mustache` templates and `explorer.css` /
`explorer.js` beside the classic theme. `buildExplorer` takes an optional `routes` input, and
nodes carry their declaration `order`. Each `requirements.rows` entry in `meta verify
--format json` now carries `node`: the entity's resolution key on a
`WARN_REQUIREMENT_OBJECT_UNCLAIMED` row, `""` on every other row, and each finding in a
`requirementLedger` row carries its `message`. See `docs/features/explorer-site.md`.

- **Generated record models enforce the four cross-field validators in every port.**
`validator.comparison`, `validator.requiredWhen`, `validator.presentIff` and
`validator.atLeastOne` used to render only as DB `CHECK` constraints, so a store with no `CHECK`
Expand Down Expand Up @@ -183,6 +202,14 @@ here.**

### Changed

- **`docsSiteFile()` builds the model explorer by default.** A project that lists
`docsSiteFile()` gets a different site under `site/` on its next `meta gen`, with new page
paths (object pages move under `model/`); `meta gen` removes the old pages it wrote, as it
does any untouched generated file it no longer emits, and `meta verify --codegen` reports
drift until it has run. Pass `docsSiteFile({ site: "classic" })` to keep the earlier
site byte for byte. A project that ejected the classic theme gets a warning and the packaged
explorer theme; run `meta eject docs-site` again to copy the explorer's files.

- **Python and Java report a missing provider's errors in the same order as the other ports.**
When a file holds both an unknown subtype and unknown attributes, Python used to drop the whole
file at the unknown subtype and never report the attributes; it now reports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ generate, docs included). Each factory is an ordinary entry in `generators`, fro
| `requirementsFile()` | `requirements` | the declared `requirement.*` ledger, each entry headed by its dotted path and `title`. Writes nothing for a model with no `requirement.*` node. |
| `agentDocsFile()` | `agent-docs` | `agent/schema.md`, `agent/ui.md` (only when the run also lists a client-tier generator, OR the config sets `includeUiTier: true` — the escape hatch for a docs-only config whose UI is generated by a separate config; skipping it with a UI surface in the model and `includeUiTier` unset prints an advisory naming `includeUiTier`; `includeUiTier: false` says there is no UI anywhere and skips it without one) and `agent/requirements.md` — the three `agent/` pages the always-on pointer reads before touching a tier. |
| `metamodelDocsFile()` | `metamodel-docs` | the built-in vocabulary reference, under `metamodel/` (was `meta docs --metamodel`). |
| `docsSiteFile()` | `docs-site` | an HTML site under `site/` (was `meta docs --site`). Option: `prompts`. `meta eject docs-site` copies its templates and assets to `codegen/docs-site/` (was `meta docs --scaffold-site`). |
| `docsSiteFile()` | `docs-site` | an HTML site under `site/` (was `meta docs --site`): the model explorer by default (what the software does, the model map, requirement pages, matrix, trace, API, gaps), drawn from an `explorer.json` built in the same run; `site: "classic"` writes the earlier site. Options: `site`, `prompts`. `meta eject docs-site` copies its templates and assets to `codegen/docs-site/` (was `meta docs --scaffold-site`). |

```ts
import { defineConfig } from "@metaobjectsdev/cli";
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ docs/
│ ├── contract-emitters.md # the contract-schema and openapi generators: JSON Schema and OpenAPI for the REST contract (FR-022)
│ ├── cube-export.md # the cube-model generator: reporting vocabulary as Cube data-model files (FR-044)
│ ├── explorer-data.md # explorer.json and its inputs: export --effective, the verify ledger rows, manifest provenance
│ ├── explorer-site.md # the model explorer site docsSiteFile() builds from explorer.json
│ └── own-your-codegen.md # scaffold-and-own generator ownership (ADR-0034)
└── ports/ # one file per language/framework port
├── typescript.md
Expand Down Expand Up @@ -80,6 +81,7 @@ this tree is documentation, not the source of truth.
| Publish the API as JSON Schema or OpenAPI, and check a change against the last release | [`features/contract-emitters.md`](features/contract-emitters.md), [`recipes/check-api-against-last-release.md`](recipes/check-api-against-last-release.md) |
| Export the reporting vocabulary to Cube (`cube-model`: cubes, joins, rollups; the `cube` lane that checks it against a real Cube) | [`features/cube-export.md`](features/cube-export.md) |
| Build the data a model explorer reads (`explorer.json` from `meta export --effective`, `meta verify --format json` and the generated-file manifest) | [`features/explorer-data.md`](features/explorer-data.md) |
| Build the model explorer site: what the software does, the model map, requirement pages, the matrix, trace and gaps (`docsSiteFile()`) | [`features/explorer-site.md`](features/explorer-site.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) |
| Add a custom metamodel subtype or attribute to a downstream project | [`features/extending-with-providers.md`](features/extending-with-providers.md) + [`recipes/extending-metaobjects-with-providers.md`](recipes/extending-metaobjects-with-providers.md) |
Expand Down
2 changes: 1 addition & 1 deletion docs/features/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ documentation never lands in the code's output directory by default.
| `requirements` | `requirementsFile()` | The declared ledger, as `requirements.md` + `requirements.toon`; nothing for a model with no `requirement.*` node. |
| `agent-docs` | `agentDocsFile()` | Three pages an agent reads BEFORE touching a tier (below), under `agent/`. |
| `metamodel-docs` | `metamodelDocsFile()` | The reference of the BUILT-IN metamodel (every registered type, subtype and attribute), not your model, under `metamodel/`. Its rendered form is published at <https://metaobjects.dev/reference>. |
| `docs-site` | `docsSiteFile()` | The HTML site under `site/`; `meta eject docs-site` copies its templates and assets to `codegen/docs-site/` for you to change. |
| `docs-site` | `docsSiteFile()` | The HTML site under `site/`: the model explorer by default ([explorer-site.md](explorer-site.md)), the earlier site with `site: "classic"`; `meta eject docs-site` copies its templates and assets to `codegen/docs-site/` for you to change. |

```ts
targets: { docs: { outDir: "./docs/generated" } },
Expand Down
39 changes: 28 additions & 11 deletions docs/features/explorer-data.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Explorer data: `explorer.json` and the three inputs it reads
# Explorer data: `explorer.json` and the inputs it reads

A model explorer shows one project so a reader can see what it does and how it was designed:
the objects and how they connect, the prompts and what they send and get back, the requirement
tree and whether each claim holds, and which generated files each node produced. All of that
is read from one data file, `explorer.json`. This page documents that file and the three
inputs it is built from.
is read from one data file, `explorer.json`. This page documents that file and the inputs it is
built from.

This is the data layer only. No screen reads it yet.
This page is the data layer. The site that reads it, built by `docsSiteFile()`, is
[explorer-site.md](explorer-site.md); that generator builds the file during `meta gen`, so
you only build it by hand to feed a tool of your own.

## The inputs are port-neutral

The builder (`buildExplorer` in `@metaobjectsdev/docs-site`) never loads metadata itself. It
reads three documents:
reads three documents, and a fourth optional input, the REST routes (see [`api`](#api)):

| Input | Produced by | What it contributes |
| --- | --- | --- |
Expand Down Expand Up @@ -74,12 +76,17 @@ does not have to infer "checked and clean" from a path that no diagnostic mentio
| `digest` | The same SHA-256 over the claim that each generated requirement test carries ([requirements.md](requirements.md)). |
| `implementedBy` | Each link as written (`ref`), whether it resolves, and the node it resolves to (`target`): the object's resolution key, followed by `.<member path>` for a member. The gate's own resolver produces these values. |
| `supersededBy` | The same, for `@supersededBy`; its `target` is the replacement's `qualifiedPath`. |
| `diagnostics` | Every finding reported against this requirement, as `{ code, severity, source }`. `source` is `gate` when the finding can fail the build and `lint` when it cannot. |
| `diagnostics` | Every finding reported against this requirement, as `{ code, severity, source, message }`. `source` is `gate` when the finding can fail the build and `lint` when it cannot. Two packages may declare a requirement at the same `path`, so read a requirement's findings here rather than matching `requirements.rows` by path. |
| `verdict` | From gate findings only: `error`, `warn`, or `clean`. |

A finding that is not about a single requirement, such as an entity nobody claims, appears
in `requirements.rows` and in no ledger row. The structured payload is TypeScript-only, as
the rest of `--format json` is.
in `requirements.rows` and in no ledger row. Each `requirements.rows` entry is `{ code, path,
node, severity, source, message }`. `path` is the requirement's dotted path, or `""` when the
subject is not a requirement. `node` names that other subject: a
`WARN_REQUIREMENT_OBJECT_UNCLAIMED` row carries the entity's resolution key there (for example
`"node": "acme::shop::AuditEntry"`, the entity's id in `explorer.json`), and every other row
carries `""`. Read the subject from `path` and `node`, never from `message`, whose wording may
change. The structured payload is TypeScript-only, as the rest of `--format json` is.

### Node provenance in `.hashes.json`

Expand Down Expand Up @@ -117,6 +124,7 @@ interface ExplorerJson {
edges: ExplorerEdge[];
ledger?: ExplorerLedger; // absent without a verify payload with requirements
generated?: ExplorerGenerated[]; // absent without a manifest
api?: ExplorerRoute[]; // absent without a routes input
slices: ExplorerSlice[];
}
```
Expand All @@ -134,6 +142,7 @@ each node nested inside one (field, identity, source, view, index, nested requir
| `id` | A top-level node's id is `<package>::<name>`. A nested node's id is its parent's id followed by `.<name>`, for example `acme::shop::Order.total`. A nested requirement's id equals its ledger row's `qualifiedPath`. A nested node with no name gets `#<index>` in place of `.<name>`. When siblings of different types share a name, the first one in the effective JSON keeps `.<name>`, because that is the node a dotted `@implementedBy` reference such as `Order.id` resolves to. Each later one adds its type, for example `acme::shop::AuditEntry.id[identity.primary]`. |
| `type`, `subType`, `name`, `package` | As in the model. `package` is the package of the top-level ancestor. |
| `parent` | The id of the node it is nested in. A top-level node has no `parent`. |
| `order` | Its position among its siblings in the effective tree, from 0: the declaration order, which the id sort does not keep. |
| `extends`, `abstract`, `isArray` | As in the effective JSON. |
| `inheritedFrom` | Set on a member that came from a super. It holds the id of the object the member is declared on, meaning the topmost ancestor that has a member with the same type, name and body. A member redeclared with a different body belongs to the node itself. |
| `attrs` | Every `@` attribute, without the `@`, with keys sorted. |
Expand Down Expand Up @@ -171,6 +180,11 @@ finding, including those about something other than a single requirement.
The manifest entries that carry `sourceNode`, grouped by node: `{ node, files: [{ path,
generatedBy }] }`, sorted by node and then by path. A plain entry has no node and is left out.

### `api`

One row per REST route: `{ node, method, path }`, with the project's `apiPrefix` in the path,
sorted by node, then path, then method. Absent when no routes were passed.

### `slices`

Slices are named node sets that a view can open on. They are all derived from the model, so
Expand All @@ -186,7 +200,7 @@ never in metadata, and are not built yet.

### Determinism

The same three inputs always produce the same bytes. Nodes are sorted by id. Edges are sorted
The same inputs always produce the same bytes. Nodes are sorted by id. Edges are sorted
by kind, then `from`, then `to`, then `via`, with duplicates removed. Slices are sorted by id,
and ledger rows stay in the verify payload's order. `serializeExplorer` writes JSON indented
two spaces, with one trailing newline. That makes the file safe to commit and diff.
Expand All @@ -205,14 +219,17 @@ writeFileSync("explorer.json", serializeExplorer(buildExplorer({
})));
```

A `verify` or `manifest` that is missing or malformed leaves its section out. A `model` that is
`routes` (optional) is a list of `{ node, method, path }`, one per REST route, and becomes the
`api` section; `docsSiteFile()` fills it from the routes the run mounts. The manifest records
files, not the routes inside them, so a hand build that wants routes passes them here.

A `verify`, `manifest` or `routes` that is missing or malformed leaves its section out. A `model` that is
not canonical metadata JSON makes the builder throw.

## Not built yet

- **`usage`**: which hand-written modules import a generated module. The design marks it
optional, and nothing builds it yet.
- **Routes per node** in `generated`: the manifest records files, not the routes inside them.
- **Provenance from the JVM, Python and C# generators.** Their manifests have no `sourceNode`
yet, so for those ports `generated` is empty.

Expand Down
Loading
Loading