@m0saic/knowledge 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ Root `COMMANDS.md` (monorepo document, not published) documents the inline-DSL s
17
17
 
18
18
  ## Entry routing — inline-DSL mode is first-class
19
19
 
20
- Before commander ever runs, `index.ts:5915-5920` inspects `process.argv[2]`:
20
+ Before commander ever runs, `index.ts:~7279-7285` inspects `process.argv[2]`:
21
21
  `isInlineM0()` (`inlineDsl.ts:36-41`) treats ANY first arg that is not a flag and
22
22
  not in `KNOWN_SUBCOMMANDS` as an inline m0 string and dispatches to
23
23
  `handleInlineDsl` instead of `program.parseAsync`.
@@ -26,7 +26,7 @@ not in `KNOWN_SUBCOMMANDS` as an inline m0 string and dispatches to
26
26
  m0saic "2(1,1)" # static wireframe → output.png (default mode "wire")
27
27
  m0saic "2(1,1)" --anim # animated wireframe → output.mp4
28
28
  m0saic "2(1,1)" --wire # explicit static (same as default)
29
- m0saic layout.m0 # .m0 / .m0c paths also accepted (index.ts:5787-5805)
29
+ m0saic layout.m0 # .m0 / .m0c paths also accepted (index.ts:~7184-7270)
30
30
  ```
31
31
 
32
32
  - Args after the string parse via `parseInlineArgs` (`inlineDsl.ts:123`): `-w/-h`
@@ -34,39 +34,48 @@ m0saic layout.m0 # .m0 / .m0c paths also accepted (index.ts:5787-5805)
34
34
  `--save-m0`, `--prefer-pretty-m0`/`--prefer-canonical-m0`, `--disable-ui`,
35
35
  `--format`, `--alpha`, `--validate-only`, `--report`, `--toolchain`, plus the
36
36
  dev-gated dump flags (below).
37
- - The string is validated with `validateM0String` before render (index.ts:5808);
37
+ - The string is validated with `validateM0String` before render (index.ts:~7222);
38
38
  whitespace is stripped. Always shell-quote the DSL (parens/brackets).
39
39
  - Output extension is coerced to match mode (`resolveInlineOutput`,
40
40
  `inlineDsl.ts:86` — `--anim -o out.png` → `out.mp4`).
41
41
  - Mode maps onto the wireframe handlers: `wire` → `handleWireframe`, `anim` →
42
- `handleAnimatedWireframe` (index.ts:5846-5850).
42
+ `handleAnimatedWireframe` (index.ts:~7264-7266).
43
43
 
44
44
  ## Command table
45
45
 
46
- 14 `.command(` registrations in `index.ts` + `telemetry` registered by
47
- `registerTelemetryCommand(program)` (index.ts:5784 → `utils/telemetry.ts:372`).
48
- Two of the 14 sit inside `if (DEV_MODE) { … }` and are absent from the public
46
+ 18 `.command(` registrations in `index.ts` + `telemetry` registered by
47
+ `registerTelemetryCommand(program)` (index.ts:~7138 → `utils/telemetry.ts:~372`).
48
+ Two of the 18 sit inside `if (DEV_MODE) { … }` and are absent from the public
49
49
  help — see the DEV-GATED rows below.
50
50
 
51
+ > **Line numbers are loose signposts, written `~NNNN`** (founder ruling
52
+ > 2026-09-25). `index.ts` is ~7,200 lines and every number in this doc had drifted
53
+ > within weeks. **The greppable symbol is the anchor; the number only says
54
+ > roughly where to look.** A number that has moved is not a defect and does not
55
+ > earn a fix pass — correct one when you are editing that row anyway, and
56
+ > otherwise leave it.
57
+
51
58
  | Command (index.ts line) | What it does |
52
59
  |---|---|
53
- | `make <input>` (4143) | Primary render: template id / `.mosaic` / `.mosaicx` → video or image. Full flag surface below. |
60
+ | `make <input>` (~5095) | Primary render: template id / `.mosaic` / `.mosaicx` → video or image. Full flag surface below. |
54
61
  | `hello-world` | The first render — a thin alias for `make @m0saic/hello-world/v1` (the brand card; output defaults to `hello-world.mp4`). With `--template-repo <path...>` / `--community-repo <path>` it renders the loaded repo's **front door** instead: the first repo (load order) whose `repo.helloWorld` names a template that registered, announced as `Front door: <id> — <repo>'s hello-world`; no repos, or none that name one → the core card with a one-line why. Takes `-o -w -h --fps --durationMs --props --quiet --verbose` (no engine surface). Picker: the CLI source (not published). |
55
- | `make-wireframe` (4372) | Static wireframe PNG from `--m0 <string>` or `--mfile <path>`. |
56
- | `make-wireframe-animated` (4448) | Animated wireframe MP4 from `--m0`/`--mfile`; extra `--disable-ui`. |
57
- | `flatten <input>` (4531) | Inline all `type:mosaic` children of a `.mosaic`/template into one flat JSON doc — no render. |
58
- | `resolve <input>` (4642) | `.mosaicx` recipe → resolved `.mosaic` provenance doc, no ffmpeg; `--flatten` also inlines children. |
59
- | `list-templates` (4751) | Print registered template ids (`--json`); annotates primitive/internal/deprecated. |
60
- | `doctor <repoDir>` | **Public since 0.2.0** (dev-gated at 0.1.0; founder ruling 2026-09-16, BURN-DOWN D5 → publish). Run the template conventions over a template repo folder — the same checks its build gate (`tools/check-registry.mjs`) runs, from outside (agents, reviewers, forks, CI on a fork): definition-time + render-time conventions (the 13 throw-posture ones incl. `latticeSmooth`) and, when the repo commits `layout-fingerprints/` or `.layout.m0` sidecars, the fingerprint diff. `--json` (one object: `ok, repo, mode, loadDiagnostics, rendered, skipped, errors, warnings, notes, fingerprints`), `--sweep` (also render on the standard 1080p canvases → `canvasEnvelope`), `--entry <path>`, `--src <dir>`. Loads through `loadTemplateRepoFromPath` under the external origin scope, so conventions RECORD rather than throw and one bad template never hides the rest. **Runs the repo's template module bodies in-process** — prints a trust warning to stderr (not under `--json`). Exit 1 on any error-severity finding or a fatal load diagnostic. It is the pack-publishing contract for community/starter authors ("the publish requirement for packs"). Impl: the CLI source (not published); locked public by `__tests__/cli.surface.test.js`. |
61
- | `browse-templates` (4794) | **DEV-GATED** (whole command inside `if (DEV_MODE)`, 4792): interactive TTY picker (template → variant → confirm → delegates to `make`). It drives the E2E variant matrix and writes to `test-output/` — a development harness, not a user surface. |
62
- | `momo <message…>` (5042) | **DEV-GATED** (whole command inside `if (DEV_MODE)`, 5040): relay English geometry instruction to Momo in a running Mosaic Desktop over the loopback bridge; exit 0 applied / 2 not-applied / 1 couldn't-run. |
63
- | `open [file]` (5345) | Open a file in Mosaic Desktop, or `--template` / `--make` to open the Make page via the bridge. See below. |
64
- | `setup` (5496) | Download + install the pinned ffmpeg toolchain (`--gpl` default / `--lgpl` / `--yes` / `--platform-key`). Idempotent: detects installed golden slot or a PATH ffmpeg with libx264. **The only path that downloads** — renders never do. TTY asks `Download the pinned GPL ffmpeg build now?`; non-TTY / `CI` need explicit approval — `--yes` (or the `--gpl` / `--lgpl` repair flags), otherwise `No terminal to ask for approval on.` + exit 1, nothing downloaded. Ends `✓ ffmpeg is ready. Now run your m0saic command again.` |
65
- | `activate <key>` (5612) | Validate + store a license key at `~/m0saic/license.json` (`M0SAIC_PRODUCT_KEY` env wins over the file). |
66
- | `license` (5659) | Show tier/holder/expiry; `--remove` returns to free tier. |
67
- | `update` (5711) | Check npm for a newer release; offers `npm i -g m0saic@latest`. |
68
- | `versions` (5750) | Print package versions + ffmpeg baseline/runtime + resolved toolchain (`--json`). |
69
- | `telemetry` (via helper) | Subcommands `status` (default) · `set-mode <standard\|local-only\|ghost>` · `list` · `preview` · `flush` · `clear`. Since 0.2.0 PUBLISHED builds transmit (Standard mode): day summary + today-so-far rollups after renders via a detached worker, to `https://m0saic.io/api/telemetry`. Workspace builds are dormant. Gates for harnesses: `M0SAIC_TELEMETRY=ghost` (nothing recorded), `M0SAIC_TELEMETRY_ENDPOINT=off` (nothing sent), `CI` (no send unless the endpoint is set by env). Full reference: `TELEMETRY.md` at the repo root. |
62
+ | `make-wireframe` (~5462) | Static wireframe PNG from `--m0 <string>` or `--mfile <path>`. |
63
+ | `make-wireframe-animated` (~5568) | Animated wireframe MP4 from `--m0`/`--mfile`; extra `--disable-ui`. |
64
+ | `flatten <input>` (~5680) | Inline all `type:mosaic` children of a `.mosaic`/template into one flat JSON doc — no render. |
65
+ | `resolve <input>` (~5800) | `.mosaicx` recipe → resolved `.mosaic` provenance doc, no ffmpeg; `--flatten` also inlines children. |
66
+ | `list-templates` (~5946) | Print registered template ids (`--json`); annotates primitive/internal/deprecated. |
67
+ | `doctor <repoDir>` | **Public since 0.2.0** (dev-gated at 0.1.0; founder ruling 2026-09-16, BURN-DOWN D5 → publish). Run the template conventions over a template repo folder — the same checks its build gate (`tools/check-registry.mjs`) runs, from outside (agents, reviewers, forks, CI on a fork): definition-time + render-time conventions (the 13 throw-posture ones incl. `latticeSmooth`) and, when the repo commits `layout-fingerprints/` or `.layout.m0` sidecars, the fingerprint diff. `--json` (one object: `ok, repo, mode, loadDiagnostics, rendered, skipped, errors, warnings, notes, fingerprints, conventions`), `--sweep` (also render on the standard 1080p canvases → `canvasEnvelope`), `--entry <path>`, `--src <dir>`. Loads through `loadTemplateRepoFromPath` under the external origin scope, so conventions RECORD rather than throw and one bad template never hides the rest. **Runs the repo's template module bodies in-process** — prints a trust warning to stderr (not under `--json`). Exit 1 on any error-severity finding or a fatal load diagnostic. It is the pack-publishing contract for community/starter authors ("the publish requirement for packs"). Impl: the CLI source (not published); locked public by `__tests__/cli.surface.test.js`. |
68
+ | `browse-templates` (~6033) | **DEV-GATED** (whole command inside `if (DEV_MODE)`, 4792): interactive TTY picker (template → variant → confirm → delegates to `make`). It drives the E2E variant matrix and writes to `test-output/` — a development harness, not a user surface. |
69
+ | `momo <message…>` (~6282) | **DEV-GATED** (whole command inside `if (DEV_MODE)`, 5040): relay English geometry instruction to Momo in a running Mosaic Desktop over the loopback bridge; exit 0 applied / 2 not-applied / 1 couldn't-run. |
70
+ | `init <name>` (~6464) | Scaffold a template workspace — the compact starter twin, renamed to the user's handle. `--dir`, `--handle`, `--display-name`. Shares ONE scaffold with Desktop's Start pane (`scaffoldTemplateWorkspace` in `@m0saic/product`), so the two cannot drift. Deliberately does NOT write the trust store: loading an external repo is the user's consent, recorded by the app in `~/m0saic/template-trust.json`. Prints the install / build / fingerprint line and the Add-source hint. |
71
+ | `mcp` (~6538) | Run the CLI as an **MCP server** over stdio — the agent-facing surface. See below. |
72
+ | `open [file]` (~6608) | Open a file in Mosaic Desktop, or `--template` / `--make` to open the Make page via the bridge. See below. |
73
+ | `setup` (~6830) | Download + install the pinned ffmpeg toolchain (`--gpl` default / `--lgpl` / `--yes` / `--platform-key`). Idempotent: detects installed golden slot or a PATH ffmpeg with libx264. **The only path that downloads** — renders never do. TTY asks `Download the pinned GPL ffmpeg build now?`; non-TTY / `CI` need explicit approval — `--yes` (or the `--gpl` / `--lgpl` repair flags), otherwise `No terminal to ask for approval on.` + exit 1, nothing downloaded. Ends `✓ ffmpeg is ready. Now run your m0saic command again.` |
74
+ | `activate <key>` (~7000) | Validate + store a license key at `~/m0saic/license.json` (`M0SAIC_PRODUCT_KEY` env wins over the file). **A VERIFIED key — valid, in grace, expired or revoked — also sends ONE notice to m0saic.io from a detached worker (`utils/licenseActivation.ts` → `licenseActivationWorker.js`): the key's `keyId`, the outcome, `surface: "cli"`, the CLI's major.minor. Never the token, the email or the install id. Sent in EVERY telemetry mode — the one signal outside them (founder ruling 2026-09-26); honours `M0SAIC_TELEMETRY_ENDPOINT=off`, `CI` (unless the endpoint came from env) and dormancy (a dev checkout never sends); ignores the mode and `DO_NOT_TRACK`. The command prints "Told m0saic.io this key was used …" after the verdict, and `--help` says so. An unverified key sends nothing.** `TELEMETRY.md` §1.16. |
75
+ | `license` (~6991) | Show tier/holder/expiry; `--remove` returns to free tier. |
76
+ | `update` (~7050) | Check npm for a newer release; offers `npm i -g m0saic@latest`. |
77
+ | `versions` (~7116) | Print **every bundled `@m0saic/*` package**, grouped by tier (language / substrate / product — the CLI bundles no community package) + ffmpeg baseline/runtime + resolved toolchain. `--json`: `packages` holds ALL bundled packages (a superset of the old `templates`/`types` pair, so scripts keep working). Classification comes from `@m0saic/platform` `describeMosaicPackage`, shared with Desktop's Tools → About this build (2026-09-26). |
78
+ | `telemetry` (via helper) | Subcommands `status` (default) · `set-mode <standard\|local-only\|ghost>` · `list` · `preview` · `flush` · `clear`. Since 0.2.0 PUBLISHED builds transmit (Standard mode): day summary + today-so-far rollups after renders via a detached worker, to `https://m0saic.io/api/telemetry`. Workspace builds are dormant. Gates for harnesses: `M0SAIC_TELEMETRY=ghost` (nothing recorded), `M0SAIC_TELEMETRY_ENDPOINT=off` (nothing sent), `CI` (no send unless the endpoint is set by env). **Modes govern usage telemetry only: `activate` is in `CLI_USAGE_SKIPPED_COMMANDS`** — its notice is identity-less, and the rollup deliberately carries no `cli.command.activate` counter beside it (the pairing would link an install id to a key id). `status` / `preview` cannot list the notice: it is never queued. Full reference: `TELEMETRY.md` at the repo root (§1.16 for the notice). |
70
79
 
71
80
  > **`decode-watermark` is GONE** (removed 2026-07-28). Recovering a forensic
72
81
  > watermark is now an ordinary template — `@m0saic/forensic/watermark/verify/v1`,
@@ -74,18 +83,18 @@ help — see the DEV-GATED rows below.
74
83
  > family. The old `--ffmpeg <path>` override it carried is now the template's
75
84
  > `ffmpegPath` prop.
76
85
 
77
- ## `make` input routing (index.ts:4348-4367)
86
+ ## `make` input routing (index.ts:~5340-5372)
78
87
 
79
88
  1. Extension `.mosaic` → `handleMosaicFile` — parse JSON renderable, resolve
80
89
  relative media against the file's dir, plan + render.
81
- 2. Extension `.mosaicx` → `handleMosaicxFile` (3419) — the **resolve-then-render
90
+ 2. Extension `.mosaicx` → `handleMosaicxFile` (~4214) — the **resolve-then-render
82
91
  branch**: absolutize asset paths → apply the doc's `runner` block as option
83
92
  defaults (explicit flags win) → sibling `.m0v` auto-discovery → route
84
93
  `--inputs`/`--input-dir` into the single `template_invocation`'s
85
94
  `props.sourceIds` (ambiguous multi-invocation → error; zero inputs on an
86
95
  input-requiring template → renders a usage-card and exits non-zero) →
87
96
  `resolveMosaicx` → write resolved doc to a tmp `.mosaic` → delegate to
88
- `handleMosaicFile` (3631) so the post-resolve render path is shared.
97
+ `handleMosaicFile` (~4460, defined ~4475) so the post-resolve render path is shared.
89
98
  **Two roots accepted** (2026-08-14): `mosaicx_document` and
90
99
  `mosaicx_pipeline` (a root-level template chain — see
91
100
  `templates/data-pipeline.md`). The header echoes which one it read
@@ -157,7 +166,7 @@ the JSON:
157
166
  ## `make` flags
158
167
 
159
168
  **Core** — `-w/--width` and `-h/--height` are `requiredOption`s. Then:
160
- `-o/--output` (defaults `out.mp4` / `out.png`, index.ts:172-173), `--fps` (1–120),
169
+ `-o/--output` (defaults `out.mp4` / `out.png`, index.ts:~172-173), `--fps` (1–120),
161
170
  `--durationMs` (>0), `--format`/`--output-kind` (`video|image`),
162
171
  `--alpha`/`--no-alpha`, `--props <jsonOrPath>` (inline JSON **or** `@path/to/json`
163
172
  — `utils/readJsonArg.ts:18` strips the `@` and reads the file),
@@ -171,7 +180,7 @@ mosaic; prints `Validation DEGRADED`), `--report` (`.output.json` /
171
180
  `--template-repo-entry <file>` (external template repos), `--community-repo <path>`
172
181
  (ONE checkout of the official community repo — see the community gate below).
173
182
 
174
- **Encode surface** (index.ts:4231-4338, mirrors the Make page's Advanced panel;
183
+ **Encode surface** (index.ts:~4231-4338, mirrors the Make page's Advanced panel;
175
184
  precedence: flags > `.m0v` preset > doc-on-disk > engine default):
176
185
  `--target <preset>` (`web-mp4|web-webm|alpha-mov|image-png|image-jpeg|animated-gif|audio-mp3|audio-wav`),
177
186
  `--container`, `--video-codec`, `--audio-codec`, `--pixel-format`, `--bitrate`
@@ -189,10 +198,10 @@ over `--input-dir`), `--input-dir <path>` (+ `--recursive` for depth-first walk)
189
198
  **`.m0v`**: `--m0v <path>` loads a Mosaic Vocabulary file two ways — named
190
199
  outputs onto `ctx.userIntent.outputs` (template-consultative) AND a post-render
191
200
  merge onto the renderable **by index** when entry counts match
192
- (index.ts:4218-4221; `BaseRenderOptions` docstring 541-557).
201
+ (index.ts:~4218-4221; `BaseRenderOptions` docstring ~541-557).
193
202
 
194
203
  **Toolchain**: `--toolchain <name>` is a **global program option**
195
- (index.ts:526), not make-specific — a named entry from `m0saic.local.json`, with
204
+ (index.ts:~526), not make-specific — a named entry from `m0saic.local.json`, with
196
205
  implicit `gpl`/`lgpl` golden slots merged in. Resolution precedence: `--toolchain`
197
206
  flag > `M0SAIC_TOOLCHAIN` env > config `defaultToolchain` > PATH/golden fallback
198
207
  (`utils/toolchainConfig.ts:129`). There is **no** `--ffmpeg` anywhere on the CLI
@@ -212,7 +221,7 @@ Spell every `setup` hint with `cliSetupCommand()` (`utils/invocation.ts`):
212
221
  timing rolled up by mosaic node (names the bottleneck panel/template).
213
222
 
214
223
  **DEV-GATED** (each wrapped in `.hideHelp(!DEV_MODE)`; `DEV_MODE =
215
- process.env.M0SAIC_DEV === "1"` at index.ts:58, compile-stripped to `false` in
224
+ process.env.M0SAIC_DEV === "1"` at index.ts:~71, compile-stripped to `false` in
216
225
  the published artifact): `--dev` (commands + timings), `--print-commands`,
217
226
  `--save-plan [path]`, `--save-commands [path]`. Same gating inside inline-DSL
218
227
  mode (`inlineDsl.ts:148-154`). Also dev-gated: the whole `momo` command, the
@@ -230,7 +239,7 @@ and neither is `doctor` (public since 0.2.0).
230
239
 
231
240
  **Three reserved identities, exact-match, nothing else is checked:** template
232
241
  namespaces `@m0saic/` and `@m0saic-dev/`, repo id `@m0saic-community`
233
- (`index.ts:907-909`; registry compare is case-insensitive, `templateRegistry.ts:112`).
242
+ (`index.ts:~907-909`; registry compare is case-insensitive, `templateRegistry.ts:~112`).
234
243
  Every other namespace / repo id is open. `--template-repo` never grants anything.
235
244
 
236
245
  **The grant.** `--community-repo <path>` says "this IS the official repo"; the
@@ -338,7 +347,7 @@ ordinary repos); the community checkout loads only this way".
338
347
  `make-wireframe` / `make-wireframe-animated` take the layout as `--m0 <string>`
339
348
  (inline DSL — the flag is `--m0`, not `--m0saic`) or `--mfile <path>`, plus the
340
349
  same core/save/report/`--m0v`/`--output-pattern` surface as `make` (no encode
341
- surface, no `--inputs`). Animated adds `--disable-ui` (index.ts:4504).
350
+ surface, no `--inputs`). Animated adds `--disable-ui` (index.ts:~4504).
342
351
 
343
352
  ## `m0saic open` — pointer only
344
353
 
@@ -353,6 +362,72 @@ https://github.com/m0saic-project/m0saic-packages/blob/main/packages/templates/d
353
362
  <file.mosaicx>` opens an authored `.mosaicx` in Make and adopts it for the
354
363
  agent loop.
355
364
 
365
+ ## `m0saic mcp` — the agent surface (2026-09-25)
366
+
367
+ MCP has three primitives; this server implements **tools** only (model-invoked).
368
+ Transport is newline-delimited JSON-RPC 2.0 over stdio, hand-rolled in
369
+ `src/mcp/server.ts` (~230 lines, **zero runtime deps**: the official SDK carries
370
+ 17, including express, hono, cors, jose and ajv, and the CLI vendors its whole
371
+ closure into the published tarball). `initialize` echoes the client's
372
+ `protocolVersion`; a tool that throws returns `isError: true` rather than a
373
+ JSON-RPC error, so the model sees the failure instead of the transport eating it.
374
+
375
+ The 14 tools (`src/mcp/tools.ts`), in three groups:
376
+
377
+ - **Knowledge** — `knowledge_list`, `knowledge_read`, `knowledge_search`. The
378
+ `@m0saic/knowledge` docs, so an agent reads the handbook instead of guessing.
379
+ - **Local, no app needed** — `list_templates`, `template_props`, `validate_m0`,
380
+ `workspace_scaffold`, `render_still`, `doctor`.
381
+ - **Through the loopback bridge into a running Desktop** — `desktop_status`,
382
+ `desktop_open_in_make`, `desktop_reload_sources`, `desktop_read_verdict`,
383
+ `desktop_layout`.
384
+
385
+ Two rules the implementation enforces:
386
+
387
+ - **`render_still` pins `M0SAIC_CLI=/usr/bin/false`.** `@m0saic/benchmark/run/v1`
388
+ re-invokes `argv[1]`; without the pin a render from inside the CLI fork-bombs.
389
+ - **Failures return a HINT, never raw stderr** (`findFailureHint`). Engine text is
390
+ moat: the argv and the filtergraph never cross to a model.
391
+
392
+ `src/mcp/bridgeClient.ts` is the transport for the desktop tools. A 404 means
393
+ `route-missing` — "this Desktop is too old for that route" — not "the app is
394
+ down"; the compat warning prints once per process. The full route table is
395
+ `docs/compat-bridges.md`.
396
+
397
+ **`--agent-note` / `--agent-question` are PUBLIC** as of this line (the DEV_MODE
398
+ strip is gone and `__tests__/cli.surface.test.js` asserts they appear in the
399
+ published help — the lock was inverted, not removed). With `--template` they seed
400
+ a note / question into Make's File tab for the reviewer to answer.
401
+
402
+ ## `m0saic doctor` answers a VERSIONED question (2026-09-25)
403
+
404
+ Conventions change, so "does this repo pass?" is the wrong question — the right one is **which line
405
+ does it pass at.** `report.conventions` carries it:
406
+
407
+ ```
408
+ conventions: meets 0.2.0 (shipped at 0.2.0).
409
+ behind 0.3.0: bindingsDeclared, canvasFill
410
+ 9 of 96 template(s) behind — a vN+1 on each clears it.
411
+ ```
412
+
413
+ - **`meets`** — the newest m0saic line every template in the repo satisfies (error severity only; a
414
+ `record` warning is advice, not a failure to meet a line).
415
+ - **`behind`** — the lines it does not meet, with the rules that fail, oldest first.
416
+ - **`shippedAt`** — the repo's own `frozen.manifest.json` `release`.
417
+ - **`lagOnly`** — true when EVERY failure comes from a line newer than `shippedAt`. The footer then
418
+ says so explicitly: *"Not a defect: these templates met the conventions of their day. Fix at the
419
+ next vN."*
420
+
421
+ **A template is allowed to lag.** It met the conventions of its day; the rules moved; the fix is its
422
+ next `vN`. What the rules guarantee is that **NEW** templates meet the CURRENT line — which is why the
423
+ postures throw (founder: "we need to be greedy about adding conventions because agents, especially
424
+ non-frontier ones, won't"). The exit code is still 1 on any error, because the status is informational
425
+ and the message carries the meaning.
426
+
427
+ `TEMPLATE_CONVENTION_SINCE` in `@m0saic/template-utils` is the map of rule → line. Everything
428
+ pre-0.3.0 reads `0.2.0`: at that freeze the whole shipped fleet passed every rule then in force, so it
429
+ is a true lower bound rather than a guessed date.
430
+
356
431
  ## Failure modes
357
432
 
358
433
  - `spawnSync m0saic EACCES` — the CLI isn't built/linked on this machine:
@@ -365,7 +440,7 @@ agent loop.
365
440
  change, breaking probe-based assertions. Check `npm run tier:status` first;
366
441
  full rule in the maintainers' agent contract §7.7 (not published).
367
442
  - Non-zero ffmpeg exits print a core-computed failure classification after the
368
- exit-code line (`printFfmpegFailure`, index.ts:98) — read it before rerunning
443
+ exit-code line (`printFfmpegFailure`, index.ts:~113) — read it before rerunning
369
444
  with `--verbose`.
370
445
  - Invalid m0 strings fail fast at validation (`validateM0String`), not inside
371
446
  ffmpeg; `SPLIT_EXCEEDS_AXIS` means the split count exceeds the pixel axis —
@@ -71,8 +71,24 @@ doc.children["<child-key>"] = {
71
71
  };
72
72
  ```
73
73
 
74
- `color: "black@0"` is the idiomatic transparent base. Intermediates carry alpha by
75
- default, so the composite stays clean.
74
+ `color: "black@0"` is the idiomatic transparent base **for the child's own
75
+ framebuffer.** Whether the child's INTERMEDIATE keeps that transparency on the hop
76
+ back to the parent depends on the ROOT's deliverable, not on a default:
77
+
78
+ - under an image / `.mov` / alpha root it does;
79
+ - under an **mp4 root** (the single-flat path) only a child that paints an image,
80
+ text, or alpha media (`qtrle` / `png` / `.mov`) is encoded as an alpha carrier
81
+ (`qtrle` / `argb`). A child made only of lavfi tiles and masks comes back
82
+ **opaque** — 0.3.0 plan item **R4** (the Rainier pack works around it with one
83
+ image tile);
84
+ - a **stitched** child (a node past the chunk budget) composites its final stitch
85
+ over an opaque background regardless (`buildChunkCommands.ts` `isFinalOpaqueVideo`)
86
+ — plan item **R23**.
87
+
88
+ So a `black@0` base does not by itself make the composite clean. Until the engine
89
+ carries alpha for those cases: put one image or text tile in a transparent child,
90
+ and keep it under the chunk budget. (Corrected 2026-09-28 from the Rainier build;
91
+ was "intermediates carry alpha by default".)
76
92
 
77
93
  ### Cost, and when to skip it
78
94
 
@@ -59,6 +59,127 @@ Rules:
59
59
  Order the entries the way a double-click should read them: `fields[0]` is the
60
60
  primary action (a plain double-click); the badge row offers every kind.
61
61
 
62
+ ## The roll call — `bindingsDeclared` (THROW, 2026-09-25)
63
+
64
+ **Every prop that CAN carry a canvas handle is either bound, or declared.** A new template that does
65
+ neither fails the build. Founder ruling: "if it makes sense to bind, we do — or warn if it's not."
66
+
67
+ ```ts
68
+ bindings: { unbound: { fps: "timing", gap: "geometry", seed: "determinism" } }
69
+ ```
70
+
71
+ One honest word is the point — the reason is for a reviewer. Keys are dotted exactly as `propsSchema`
72
+ nests them (`titles.title`).
73
+
74
+ ### The complete surface — one ruling per control
75
+
76
+ `MUST ACCOUNT` = bind it, or name it in `bindings.unbound`. `NEVER` = not a canvas thing; the
77
+ right-hand panel is its surface, and the rule stays silent.
78
+
79
+ | Prop / flavour | Handle | Convention |
80
+ |---|---|---|
81
+ | `string`, free text | `string` | **MUST ACCOUNT** |
82
+ | `string` + `isColor` / `colorPicker` | `color` | **MUST ACCOUNT** |
83
+ | `number` | `number` | **MUST ACCOUNT** |
84
+ | `media` | `media` (also a drop target) | **MUST ACCOUNT** |
85
+ | `string[]` · `number[]` · `media[]` | per element, by index | **MUST ACCOUNT** (bound anywhere on the prop counts) |
86
+ | `json` · `list` · `array` | per leaf, `path` + `kind` | **MUST ACCOUNT** (any leaf counts) |
87
+ | `json` + `picker: "regions"` | `rect` | **MUST ACCOUNT** |
88
+ | `string` / `number` + closed set (`oneOf`, `options`, `optionsFrom…`) | — | **NEVER** — a picker is a mode, not a value on canvas |
89
+ | `boolean` | — | **NEVER** — a one-way door: `false` renders no rect to click |
90
+ | `group` | — | **NEVER** — a container; its fields are walked individually |
91
+ | `m0` · `m0c` · `m0p` | — | **NEVER** — the value IS the composition of every rect, not one of them |
92
+ | `code` | — | **NEVER** — read-only by contract; a handle implies editing |
93
+ | anything `ui.hidden` or `consumer: "human"` | — | **NEVER** |
94
+
95
+ Why the `m0` family is excluded, since it is the tempting one: a layout prop has no clickable element.
96
+ All 10 shipped layout props (wireframe, dsl-tutorial, screencap-grid v1/v2, watermark, qr-animate,
97
+ camera-debug) drive the ROOT composition — you would have to select the whole grid, which the canvas
98
+ cannot express. If a template ever nests a layout prop into ONE cell, that cell is a real handle and a
99
+ sixth kind (`"layout"`) becomes worth having. None does today.
100
+
101
+ **Bindable ≠ has a rect.** The predicate says a `number` is always inline-editable; whether any rect
102
+ *shows* it is the template's business. `durationMs`, `fps`, `gap`, `seed`, `padding` are all
103
+ accountable and correctly unbound — which is exactly why the rule takes a declaration instead of
104
+ guessing from the pixels.
105
+
106
+ **A colour that IS `document.backgroundColor` needs nothing** — no binding, no declaration. It has no
107
+ source and no rect, so no handle can exist, and it is the way a canvas SHOULD be filled: a full-frame
108
+ base rect becomes a click target that shadows everything behind it whenever the pointer is not on a
109
+ smaller tile (founder direction 2026-09-25). Matched case-insensitively, root or child.
110
+
111
+ **A binding in a nested CHILD counts.** A template that composes its content into a child document
112
+ (the hello-world card puts everything in a `card` child) reaches its props perfectly well — Make
113
+ resolves a binding through children. **But the audit renders at `defaultProps`,** so a rect the
114
+ template only creates when a prop is non-empty reads as unbound. That is the "bind even when the value
115
+ is empty" rule biting: bind it unconditionally, or declare it.
116
+
117
+ **A stale declaration is a violation too:** naming a prop that cannot carry a handle, naming one that
118
+ is in fact bound, or leaving the reason empty. An entry claims a reviewer looked at that prop.
119
+
120
+ **Exempt:** any template hashed in the repo's `frozen.manifest.json`. It shipped before the
121
+ convention, it is never edited in place, and the fix would be a vN+1 nobody will write. A repo with no
122
+ manifest has shipped nothing and holds every template to the rule.
123
+
124
+ ## The in-context line — `bindingHints` (THROW, 2026-09-27)
125
+
126
+ **Every bound rect shows one line in context.** Make puts it under the inline editor when a person
127
+ double-clicks, on the tile card's row, and in the handle's hover tooltip — what changing the value
128
+ does and what it looks like, so nobody has to open the settings pane or a manual to understand a knob.
129
+ Founder ruling: a production-grade template adheres to all the surfaces of the ecosystem, and the
130
+ canvas is now the primary one; "it's better to take this tax now."
131
+
132
+ The line comes from either of two places, and the gate accepts either:
133
+
134
+ | Where | How | Reaches |
135
+ |---|---|---|
136
+ | the prop | `propsSchema.<key>.description = "…"` | every rect bound to that prop |
137
+ | the binding | `bindProp(src, key, i, { hint: "…" })` · a `bindProps` entry's `hint` · `withBindingHint(src, "…")` (composes with every binder) | that rect only — the wording for THIS place |
138
+
139
+ One plain sentence, never markdown, never the value itself ("The month this calendar shows; the grid
140
+ re-flows to its weeks", not "October"). Make trims it and caps it near 200 characters. The tax is
141
+ therefore one honest sentence per bound prop, paid once in the schema; a per-rect `hint` is for the
142
+ cases where the same prop means something different in different places.
143
+
144
+ **Needs none:** a `companion` leaf (filled only by a media drop, never shown in a form), and any prop
145
+ that is not bound (an `unbound` declaration already carries its reason). A binding on an unknown prop
146
+ is `bindingsSound`'s finding, not this one.
147
+
148
+ **Exempt:** any template hashed in the repo's `frozen.manifest.json`, like the roll call — it reports
149
+ in `lagging`, never `findings`. The CLI ignores the field entirely; it is extra data there.
150
+
151
+ ## The `bindingsCover` convention — what the gate checks
152
+
153
+ The roll call above is the gate. `bindingsCover` is the WEAKER SECOND SIGNAL kept beside it
154
+ (posture `record` → a warning): it reads the drawn text, so it is the only rule that can catch a prop
155
+ bound to the WRONG rect, and it still fires on frozen templates where the roll call cannot. Both
156
+ `check-registry.mjs` copies and `m0saic doctor <repo>` run both.
157
+
158
+ - A **free-text `string`** prop — not `meta.ui.hidden`, not `consumer: "human"`, not a closed /
159
+ picked set — whose default is ≥2 characters and appears verbatim in drawn text must be bound.
160
+ - A **`number`** prop, same exclusions, whose default appears in drawn text in one of its honest
161
+ spellings: `String(v)`, `toLocaleString("en-US")`, and `toFixed(1|2)` **only when the value already
162
+ has decimals** (nothing draws an integer count as `"1.0"`, but chrome draws versions that read that
163
+ way). A one-character spelling never counts.
164
+ - Numbers match on a **digit boundary**: `12` is not read out of `2012` or `3.12`, and a drawn
165
+ `1,200` is not read as the prop `200`.
166
+ - Coverage is per PROP, not per rect — a prop bound anywhere on the root document is covered.
167
+
168
+ ### One rect, two props — a composite line
169
+
170
+ `"@qsbuilds · 2026 on GitHub"` is one rect drawing two props, and a single `editor.binding` names one
171
+ of them. A bare `bindProp` there would REPLACE the existing handle, so the rule says so and names the
172
+ fix that keeps both:
173
+
174
+ ```ts
175
+ bindProps(src, [{ propKey: "handle" }, { propKey: "year", kind: "number" }])
176
+ ```
177
+
178
+ — several handles on one rect — or split the line so each prop gets its own rect. (`year-card/v1` is
179
+ the live example; it is frozen, so its fix is v2 and it warns until then.)
180
+
181
+ Colour coverage is not checked: a colour is never drawn as text, so it needs a different detector.
182
+
62
183
  ## Authoring helpers (`@m0saic/template-utils`)
63
184
 
64
185
  - `bindProp(src, propKey, index?)` — the basic case.
@@ -127,6 +127,25 @@ seam's `outputHintsResolve` convention (THROW posture) checks it returns an
127
127
  object, is deterministic at `defaultProps`, and agrees with the static hints
128
128
  for every field it returns there. Precedence: [`../output-resolution-tree.md`](../output-resolution-tree.md) §size.
129
129
 
130
+ **Image or video is part of the same answer (2026-09-26).** `format` rides the same
131
+ resolver. A template whose kind depends on its inputs — a `media` prop that accepts
132
+ both kinds — declares it per props, `resolveOutputHints: (props) => ({ format: … })`,
133
+ and its static `outputHints.format` is that function's value at `defaultProps`, which
134
+ for an EMPTY media slot is **image** even when the template is "for" video (the
135
+ `outputHintsResolve` throw holds the two equal). The resolver is pure and prop-only:
136
+ it runs before any probe, so a path's extension is the evidence available. Exactly
137
+ three answers exist — the static hint at defaults, the resolver at the current
138
+ props, and the user's override — and nothing else authors one. Every host asks the
139
+ author through `resolveTemplateOutputHints(tmpl, props).format` and passes it into
140
+ `createDesignContext` / `createEngineContext` as `format`, so the `format` the
141
+ `defineMosaicTemplate` wrapper stamps on the rendered document is a RECORD of the
142
+ decision, never a source of it. `inferOutputKind` (`@m0saic/platform`) is a last
143
+ resort for a bare `.mosaic` opened cold with no template behind it, and since
144
+ 2026-09-26 it counts a `type: "media"` video source as motion (`videoMedia`) — the
145
+ omission that made footage-through-a-mask read as a still. The defect class and the
146
+ seven-surface audit that produced this rule:
147
+ (internal design history).
148
+
130
149
  ## The lattice declarations — `lattice` (2026-09-16)
131
150
 
132
151
  **Convention `latticeSmooth` (throw):** every split count above 12 in the rendered
@@ -151,6 +151,14 @@ Two authoring facts, both learned on the starters:
151
151
  - **Aspect-awareness.** Stress vertical / square / horizontal. A wide card may need a
152
152
  different layout (timeline's `orientation:"auto"`); clamp edge content so it can't
153
153
  spill the card.
154
+ - **Image or video → the same resolver (2026-09-26).** A template with a media slot
155
+ that accepts both kinds answers per props — `resolveOutputHints: (props) => ({ format })`
156
+ from a pure check on the media path's extension — and its static `outputHints.format`
157
+ is that value at `defaultProps`. An empty slot renders a still, so the honest static
158
+ answer is usually **image** (the seam's `outputHintsResolve` throw keeps the two
159
+ equal). Never let a host guess from the document: `inferOutputKind` is a last resort
160
+ for a bare `.mosaic`, not a decision. Reference: `reference/template-flags.md`
161
+ § "A canvas that is a knob".
154
162
  - **A canvas that is a knob → `resolveOutputHints(props)`.** When a prop picks the
155
163
  output size (a `platform` knob: YouTube 1920×1080 vs TikTok 1080×1920), declare
156
164
  the resolver on the template so hosts SEED the right target before rendering
@@ -158,6 +166,57 @@ Two authoring facts, both learned on the starters:
158
166
  `render` still reads `ctx.target` and lays out at any size. At `defaultProps` it
159
167
  must agree with the static `outputHints` (`outputHintsResolve`, throw). Rules:
160
168
  [`philosophy-and-contract.md`](philosophy-and-contract.md) §"Output contract".
169
+ - ⛔ **Filling the canvas: `document.backgroundColor`, never a full-frame rect.** This is
170
+ the **`canvasFill` convention, and it THROWS** (2026-09-25) — the build and
171
+ `m0saic doctor` both refuse a root document carrying a static, opaque, full-canvas colour
172
+ source. A base rect is a click target that shadows everything behind it, selected any time
173
+ the pointer is not on a smaller tile, and it is redundant: `doc.backgroundColor` fills the
174
+ canvas with no rect at all. Only a STATIC, opaque, unshaped fill counts — a curtain wipe
175
+ (`overlay.enable` / `window`), a scrim (`overlay.alpha`), a masked shape, a rounded card
176
+ (`effects`) and an inset fill (`placement`) are all legitimate and never flagged.
177
+ Templates already frozen are exempt. `bindingsDeclared` also accepts a colour prop that IS the document background
178
+ with neither a binding nor a declaration, because such a prop has no rect to bind.
179
+ - ⚠️ **The one case that needs a rect: a template COMPOSED INTO another.** The engine
180
+ lifts `backgroundColor` from the PRIMARY OUTPUT document only, so a child document's
181
+ own background is silently ignored (tracked at
182
+ (internal design history) — founder
183
+ owns the engine fix).
184
+ - **The declared escape** — an opt-in dev prop, NOT a rect in the default shape.
185
+ Defaulting it to `true` is ALSO how a template tells `canvasFill` that its rect is
186
+ deliberate; declaring it `false` is no escape:
187
+
188
+ ```ts
189
+ useNestedBackgroundColor: {
190
+ type: "boolean", required: false,
191
+ description: "Compose-time: also paint the background as a full-frame rect, for when this template is nested inside another (a child document's own backgroundColor is ignored by the engine).",
192
+ meta: { ui: { hidden: true, label: "Nested background rect" } },
193
+ }
194
+ ```
195
+
196
+ `defaultProps` leaves it **false / absent**, so a standalone render never grows the
197
+ rect. A template that composes this one passes `useNestedBackgroundColor: true` in the
198
+ child's props. Keep `backgroundColor` set as well — it costs nothing and keeps the
199
+ standalone path identical.
200
+ - **The m0 move is ONE wrap.** `F{…}` prepends exactly one full-canvas frame and the
201
+ wrapped layout follows in order (`F{2[1,1]}` → `1280x720@0,0`, `1280x360@0,0`,
202
+ `1280x360@0,360` at 720p), so frame 0 is the base and the content overlays it:
203
+
204
+ ```ts
205
+ if (props.useNestedBackgroundColor) {
206
+ return { ...doc, m0: `F{${doc.m0}}`, sources: [colorSource(bg), ...doc.sources] };
207
+ }
208
+ ```
209
+
210
+ `F` is pretty syntax: it **canonicalizes to `1{…}`**, and the file formats never emit
211
+ pretty form, so on disk the wrapper reads `1{…}`. Every source index shifts by one, so
212
+ anything keyed by `sourceIndex` (never a binding — those key by prop) moves with it.
213
+ - `meta.ui.hidden` is deliberate: this is a compose-time flag a parent sets
214
+ programmatically, not a knob a human picks (and hidden props are outside
215
+ `bindingsDeclared`'s roll call — a boolean is anyway).
216
+ - **When the engine fix lands, this prop goes away**: delete it, delete the rect, keep
217
+ `backgroundColor`. That is the whole point of it being opt-in and hidden rather than
218
+ baked into every template's shape.
219
+
161
220
  - **Prop bindings — the rect that SHOWS a prop is its handle in Make.**
162
221
  `bindProp(src, key)` / `bindProps(src, entries)` / `bindPropPath(src, key, path,
163
222
  kind)` / `bindPropRange(...)` (`@m0saic/template-utils`) stamp `editor.binding` on
@@ -166,7 +225,14 @@ Two authoring facts, both learned on the starters:
166
225
  are never bindable) and the preview shows one glyph per thing the tile can do (T
167
226
  text · 123 number · swatch colour · move rect · picture media), collapsing to ONE
168
227
  dot on a small tile. Bind even when the value is empty (the rect is a handle to
169
- ADD). The full contract — kinds, `onClear`, `seedDraft`, `kind: "media"` (a rect
228
+ ADD). ⛔ **`bindingsDeclared` (0.3.0) THROWS** when a prop that could carry a handle
229
+ is neither bound nor named in `template.bindings.unbound` with its reason
230
+ (`{ fps: "timing" }`) — free-text and colour strings, numbers, media, one element of a
231
+ basic list, one leaf of json/list/array, and a regions-picker rect must all be
232
+ accounted for; booleans, closed sets, groups, the m0 family, code and hidden/human
233
+ props never are. Templates in `frozen.manifest.json` are exempt. On a rect that
234
+ already binds another prop the fix is `bindProps`, never a second `bindProp` — it
235
+ would replace the first. The full contract — kinds, `onClear`, `seedDraft`, `kind: "media"` (a rect
170
236
  that takes a dropped file), `companion`, starter media — is
171
237
  [`reference/prop-bindings.md`](reference/prop-bindings.md).
172
238
  **`kind: "rect"` (2026-09-15) — a rendered cell edited IN PLACE:** a `json` prop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m0saic/knowledge",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "The m0saic knowledge base for agents and authors: the m0 handbook, the engine mental models, and the template-authoring contract, as plain Markdown. Point your coding agent at README.md before it writes a template.",