Goldie Blocks
Authoring

Corpus Seeding

Editing authored corpus source files, compiling seed fixtures, and reseeding Convex

Library documents are authored as Markdown/YAML source files and compiled into Convex seed fixtures. Use this workflow when changing module definitions, block content, interview questions, data fields, or authored flow rules.

The Corpora

Two documents are seedable. Both compile through the same script.

CorpusModulesPurpose
services_agreement3Start here. Small demo fixture for fast iteration.
legal_zebra_will8Full production-shaped corpus.

Prefer the services agreement while iterating. It compiles to roughly 400 fixture lines against the Will's ~2,600, so a broken block is findable and a reseed is cheap. Reach for the Will when you need the scale or the specific content.

Each corpus owns a deterministic key prefix so the two cannot collide:

legal_zebra_will      doc_01krq700000000000000000001   docv_01krq700000000000000000002
services_agreement    doc_01ksa100000000000000000001   docv_01ksa100000000000000000002

Never change a corpus keyBase

A corpus's keyBase in scripts/compile-legal-zebra-seed.mjs determines every key it emits. Those keys are referenced by working documents already in the database, so changing one silently orphans them. Adding a corpus means adding a new prefix.

Source Files

tests/skills/demo_skill_v1/documents/services_agreement/document_input/document.md
tests/skills/legal_zebra_will_skill_v1/documents/legal_zebra_will/document_input/

Two Layouts

The compiler detects which layout a corpus uses:

  • One file per document (settled). No modules/ directory; modules are a modules: sequence inside document.md, and sequence position is the order. The services agreement is authored this way. See One File Per Document.
  • File per module (legacy). The compiler retains compatibility for old sources, but both seedable corpora use the settled one-file layout. The dormant goldiblocks_simple_will_skill_v1 fixture remains deliberately parked outside the seed compiler; do not half-migrate it.

Author new corpora in the one-file layout.

Authoring Rules

  • A module need not have a heading, and its data blocks may precede its prose. When its prose opens a section, write that heading as Markdown (# Heading) in a content block and set its display.start_level. Headings are transparent and render wherever their span has visible content. See Blocks → Headings as Structure.
  • Put conditional data/detail blocks after the heading block.
  • Never author placement. There is one venue and every block lands on the document, so display.surfaces and display.targets are both retired (2026-08-25) and display holds start_level and numbering only. See Permissions and Visibility → One venue.
  • Do not write interview recap prose as a block. A summary such as "you have told us you have no children" now renders into the instrument, because nothing keeps it out. Such text belongs in the data block's guide:, which is Builder- and agent-facing and never renders as document content.
  • Do not author condition: uiMode = .... It is retired: a condition tests data, never placement. See DSL Spec → Placement is never a condition.
  • For collection summaries, use joinFor with newline separators.
  • Author every reference with @: @data.field for local values, @module.data.field for cross-module values, @block_handle for local block citations, and explicit @item.field inside collection bodies.
  • Use only the closed modifier set (:label, :number, :handle, :upper, :lower). Do not author {{ }}, [[ ]], fallback filters, modifier arguments, or chained modifiers.
  • Use the existing field names in the same module unless the change intentionally adds or migrates fields.

`yes` / `no` are coerced at compile time

The yaml package parses on the YAML 1.2 core schema, where only true/false are booleans — bare yes and no arrive as strings. The compiler coerces them for the known boolean flags (the numbering flags), so the authored forms above are safe.

This matters because the write path tests === true: an uncoerced "yes" would read as false and silently turn a flag off. Anywhere outside those known flags, write true/false explicitly.

Unrelated to YesNo field values, which are the string literals "Yes" and "No" by design.

Compile and Reseed

bun run reseed:demo
bun run reseed

reseed:demo handles the services agreement; reseed handles the Will. Each does three things:

  1. Compiles the corpus into its fixture under convex-dev/.
  2. Touches convex/folio/seed.ts so the running bunx convex dev watcher redeploys. (convex-dev/ is outside the convex/ watch root, so changes there do not trigger a redeploy on their own.)
  3. Runs the corpus's unified seed mutation (folio/seed:seedServicesAgreement or folio/seed:seedLegalZebraWill) with reset:true.

The unified seed writes the Folio tables and nothing else (GB-152 Stage A): every Document, Module and Block becomes a folios row with one sealed version 1 and one live head cloned from it, every container edition gets its schedule of folioPins, and the Document gets a route (services-agreement, legal-zebra-will) and an authorised session (session_seed_<handle>). It never writes libraries, libraryPins or tags.

In plain terms: each sample document is filed into the new cabinet as a sealed first edition plus an open working copy, with the web address and the permission slip needed to open it.

reset:true deletes the seed's own rows by key — both editions of every card, their pins and the seeded session — and inserts afresh, so a reseed replaces rather than duplicates and discards every sample edit made since (owner ruling, 2026-09-02: the data is disposable). Without reset, a corpus that is already seeded is refused rather than patched, because a sealed edition is never rewritten in place.

Who owns a seeded Document (owner ruling 2026-09-25, GB-10). With no createdBy and no browserClientId, the seed belongs to the one signed-in account on the deployment and is filed in that account's Workspace, in a "Seeded examples" Project, so it opens signed in from any browser. On a fresh deployment no one has signed in to, it belongs to the seed user (user_dev_seed), whom no account answers to; sign in once and reseed to make it yours. With several accounts the seed refuses rather than guess: pass createdBy with the owning user's key.

In plain terms: a sample file goes in the one client's cabinet if there is exactly one client; with several, the clerk asks whose it is.

To give a signed-out browser its own copy instead, pass the same client id the canvas presents (getBrowserClientId() in the browser console):

bunx convex run folio/seed:seedServicesAgreement '{"reset":true,"browserClientId":"<your id>"}'

To compile without seeding — every corpus, or named ones:

bun scripts/compile-legal-zebra-seed.mjs
bun scripts/compile-legal-zebra-seed.mjs services_agreement

Do not hand-edit the generated files under convex-dev/. Change the Markdown source, then recompile.

A type error anywhere in convex/ blocks seeding

Convex typechecks the whole convex/ directory before pushing, so a type error in any file there — including a test file unrelated to seeding — stops the new functions from registering. The symptom is misleading: bunx convex run reports Could not find function, as if the mutation did not exist.

Run bunx convex dev --once to see the real error.

Verify

Run the reconciliation report against the deployment:

bunx convex run folio/seed:reconcile

It returns ok: true with the row counts, or a list of breaches. It fails on any folio without exactly one live head cloned from a sealed baseline, any version or pin naming a missing row, a child pinned under the wrong parent or twice, a sealed schedule pinning a live edition, a payload disagreeing with its kind, any difference in structure or wording from the authored corpus, and any row in libraries, libraryPins or tags. Name the corpora to check when only one is seeded:

bunx convex run folio/seed:reconcile '{"corpora":["services_agreement"]}'

The same report runs in convex/folio/seed.convex.test.ts on a test backend, beside the golden fixtures under convex/folio/golden/ — the canvas snapshot, version list and export input of each seeded Document, pinned as data. A change that alters what a seeded Document reads as fails those tests and is re-pinned deliberately (bunx vitest run -u on that file).

Optional local fixture checks:

rg '\{\{|\[\[' tests/skills convex-dev/seedLegalZebraWill.ts convex-dev/seedServicesAgreement.ts

This should return no matches. Authored source contains readable @ references; generated fixtures contain their stable compiled form.

Adding a Corpus

  1. Create document_input/document.md under a new tests/skills/{skill}/documents/{handle}/ directory, in the one-file layout.
  2. Add an entry to CORPORA in scripts/compile-legal-zebra-seed.mjs with a fresh keyBase.
  3. Add the corpus to SEED_CORPORA in convex/folio/seed.ts and export a seed mutation wrapping seedUnifiedCorpus.
  4. Add a reseed:* script to package.json, and the corpus's golden fixtures by running convex/folio/seed.convex.test.ts.

Working Documents

There is no library at MVP: the seeded Document is the working document, opened by its route. Reseeding replaces its rows outright, so any sample edit made since is gone — start again from the reseeded editions.

On this page