Skip to content

docs(rfc): RFC-0045 collections stage 4, references and repeaters (#114) - #193

Merged
Madraka merged 8 commits into
mainfrom
docs/rfc-0045-references-repeaters
Nov 10, 2026
Merged

Madraka merged 8 commits into
mainfrom
docs/rfc-0045-references-repeaters

Conversation

@Madraka

@Madraka Madraka commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Draft RFC for #114: reference fields and repeaters, the part RFC-0042 §6 left out. Docs only; nothing is implemented.

What it decides

  • Contract. It adds ReferenceField and RepeaterField to FieldDefinition, plus SubFieldDefinition, a fixed depth with no recursion: repeater items are ADR-0030's flat objects of scalars. It also adds FieldMigration.sub for sub-field operations. The exact diffs are against site.schema.json, with the TS and Rust impact and the fixtures to add.
  • Integrity. Strong references, backed by an entry_refs reverse index written in each entry's batch and anchored on the new hash, as asset_refs is. Deleting a referenced entry, or moving it out of the collections its referrers allow, is refused. Archiving or unpublishing a target is allowed, and the renderer resolves only what the snapshot shows. Publish warns but never fails. Rollback, restores and imports rebuild the index.
  • Locale rule. A reference names a translation group, and resolves to the version in the page's language, then follows localization.fallback.
  • Render cost. Depth 1 by default and 2 at most, breadth-first over a visited set, at most 200 targets per page. The snapshot source resolves from in-memory indexes; the live source reads at most twice per page. Reference fields can be list fields (eq/in); sorting or filtering by a referenced entry's field is not offered.
  • Studio. New blocks: EntryPicker, ReferenceList, RepeaterField, FieldControl (extracted) and UsedByList, with accessibility and 390 px layout covered.
  • MCP. The emitted schema's root is always an object, with no $ref and depth ≤ 4. A strict profile (every property required, null unions) comes with strictCost. update_collection's input uses anyOf over its disjoint variants.
  • Field operations. The conversion table gets new rows, a sub parameter addresses repeater sub-fields, and revert drops targets deleted in the meantime.
  • Also: rollout in 7 phases, a test plan on the shared suites (sqlite, D1, DO), a threat model and 11 open questions, each with a recommended answer.

Not verified. pnpm gen and the fixture tests were not run, because the owner's machine was busy with a test-heavy session. Phase 1 runs contract-change first.

🤖 Generated with Claude Code

Draft RFC for reference fields and repeaters: the contract diff
(ReferenceField, RepeaterField, SubFieldDefinition, FieldMigration.sub),
the entry_refs reverse index with hash-anchored guards, the locale rule
(translation groups), render depth and budgets, Studio blocks, strict
structured-output schemas, field operations, rollout, tests and threat
model. Docs only; generators not run (phase 1 runs contract-change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
@Madraka Madraka added the rfc A design proposal in docs/rfcs label Nov 10, 2026
Madraka and others added 5 commits November 10, 2026 15:15
Every upheld and split finding gets a decision, listed in a review log:
- references store the translation group id, resolved and guarded
  per site
- entry writes anchor on the collection's definitions
- only published referrers lock deletes and moves
- resolution is gated per view, and cache tags purge by group
- repeater assets are recorded in asset_refs
- release closures exclude references
- strict output gets its own input (fieldsStrict, clearFields)
- every guard filters site_id, and rebuilds join same-site members
- the types stay refused until a final enabling step
- the update_collection $defs are fixed, and the anyOf plan is dropped
- Event addresses come from a Place via targetField

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Folds in every unresolved finding and new issue from the verification
of fe8ac92, extending the review log:
- group changes are guarded like deletes, with the planned site's hash
- checkBundle gates the new types on import, sync, push, pull and
  restore, with a one-time per-site rebuild at enable
- entry_refs rows carry shared/root_key mirroring resolveFields; one
  "shown by a published entry" fragment serves guards, live
  referencedBy and purges
- the definitions anchor binds the planned Site's definitions
- rows are kept for every stored value; entry_refs joins SITE_TABLES
- cloudhal-ref tags are dropped: the purge planner adds referrers'
  node tags through entry_refs (no new prefixes, no header ids)
- repeater asset checks and the purge asset rule are specified
- the asset_refs site scoping becomes rollout step 0
- races R1-R11 are listed with their guards
- edition chains, emptied items, the conversion context, the
  fieldsStrict edge cases and the Event address data path are settled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Folds in the four blocking clusters and the non-blocking items of the
verification of 9e457d1, extending the review log:
- one membership rule everywhere, COALESCE(translation.group, id) = G,
  in an index-friendly form; relinking a node with dependants is
  refused, and readsOriginal requires an original to own its group
- shared is a pure function of localized; the fragment decides who
  inherits at batch time
- delete, move and group-change guards bind the planned pairs, so an
  unseen field's row forces a retry (R13)
- purge closure to the render depth, "used by" pages, index mode for
  publishes and snapshot mode for rollbacks, release runs (re-planned
  at run time) and restores, bounded by maxReads/maxTags
- set_field_localized flips shared in phase A; conversions into
  references refuse target deletes until phase C (R12)
- an absent capability refuses in the bundle gate (CopySiteOptions,
  FIELDS_SUPPORTED); importSite stores canonical bodies
- races R12-R15, the size-ratchet modules, the test cases and the open
  questions are added

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
- every delete, move and group change of an entry carries the site
  anchor, closing R12 for deletes planned before the conversion marker
- purges use one method for every publish path: index rows corrected
  by a hash check against `to`, with a from/to body scan of the entries
  that differ (an older snapshot publish and writes between snapshot
  and publish are exact)
- `shared` is read from the site row inside the single INSERT ... SELECT
  at batch time (R16)
- the group check and a written-group guard run on any change to
  translation.group or source
- the step-7 job canonicalizes rows; the field-refs.ts:81 skip filter
  uses item paths; "used by" purges use resolved values
- wording, archived-move alignment, size-ratchet modules, phase-2
  compatibility notes and the refusal list are completed; review log
  "Fourth round"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
- the rows INSERT is a plain INSERT; shared is matched by key or
  migration.fromKey, and a NULL fails the batch (ConflictError, retry)
  instead of being skipped by OR IGNORE (R16, rename case)
- purges read only the immutable from/to bodies, through snapshot
  bundles, on every publish path; the hash-check hybrid and index mode
  are dropped (a probe cannot see referrers edited away or deleted, and
  cheap whole-site change checks are unsound)
- any field-operation marker on from or to purges everything
- deletes, moves and group changes anchor on json($.collections), not
  the site hash, so publishes no longer cause 409s
- non-blocking: the written-group guard is exempt for a node's own id;
  CAS canonicalization runs from phase 2; prefix skip match; the
  rebuild reads through fromKey; wording fixes
- new "Implementation watch list" section; review log "Fifth round"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Madraka and others added 2 commits November 10, 2026 16:28
- entryRefsRebuild runs only when its import batch writes the node set
  (no rebuild on an unchanged import); the step-7 job is per node and
  anchored on each node's read hash, with no site-wide DELETE (R10, R17);
  step 0 closes the same asset_refs hole on main
- purges use one hash-keyed body cache over both snapshots: one bundle
  (to's first), then only the missing hashes; per-shard streaming with
  byte and node budgets; short-circuit without reference definitions;
  RELEASE_EXECUTOR_COST covers the run-time re-plan
- shared prefers the exact key; NULL -> ConflictError only for
  entry_refs.shared; dedup after canonicalization; SQL NULL for an
  absent collections; "used by" covers asset-rule entries
- stale index-era wording removed; new "Owner decisions" section;
  review log "Sixth round"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
The owner accepted the RFC on 2026-11-10 with the three owner decisions
ratified: snapshot-only purges with their cost and fallbacks, archiving
the last member does not release a lock, and the phase-2 behaviour
changes on existing sites. Sets the status, turns "Owner decisions"
into "Decisions (2026-11-10)", and links #114 and #196 (fixed by
rollout step 0). The docs site lists RFCs from the folder itself
(apps/docs/src/lib/pages.ts), so there is no index file to update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
@Madraka
Madraka merged commit 1e31f72 into main Nov 10, 2026
8 checks passed
@Madraka
Madraka deleted the docs/rfc-0045-references-repeaters branch November 10, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rfc A design proposal in docs/rfcs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant