Repository navigation
feat(contracts): reference and repeater field types, gated off (RFC-0045 step 1, #114) - #203
Merged
Merged
Conversation
Adds the ReferenceField and RepeaterField variants to FieldDefinition, the SubFieldDefinition union (nine *SubField variants, scalar values only, no recursion), and FieldMigration's reference/repeater fromType and sub. The content entry's fields description names the new values. Fixtures: one valid stage-4 site, twelve invalid ones. TS and Rust regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…RFC-0045 step 1, #114) The gate: checkFieldDefinitions and checkBundle's new assertFieldTypes refuse reference and repeater definitions, and markers naming them, with fields_unsupported unless a capability allows them; none is passed yet, so site writes (Studio, MCP, API), field operations and every importSite bundle (import, pull, push, --replace, --revert, restores, edition copies) refuse. Pure core in core-ts/src/references/ and fields/: the §1 definition rules (targets, where, itemLabel, sub-field and leaf caps), props compilation and value checks for both types, per-item requiredToPublish, too_many_references, resolveFields skipping unknown types, the strict fieldsJsonSchema profile with strictSchemaCost, pickMember and referenceIssues. Dispatch tables: FIELD_QUERY_OPS and structured data get empty entries; EntryFieldsForm shows a read-only stub; FIELD_TYPES stays the nine enabled types (EnabledFieldType). MCP: update_collection's $defs come from defsClosure and list only enabled types; a test checks every tool schema's $refs resolve. API: 409 for fields_unsupported, 422 for the other two new codes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Madraka
added a commit
that referenced
this pull request
Nov 11, 2026
…ps (PR #203 review) - assertFieldTypes(siteId, bundle, support) now checks the working site row and every site body a snapshot ref names in bundle.blobs, so a clean working copy cannot carry a reference or repeater definition (or a marker naming one) into a snapshot a publish or rollback could make live. Entry values need no own check: they mean nothing without a definition. - The admin bundle route keeps fields_unsupported (409 with details.issues) instead of turning it into 400 bad_request. - validateProps checks own keys (Object.hasOwn), so constructor, toString or __proto__ are unknown props; a repeater item counts as empty unless it holds a declared sub-field. - A reference's where refuses now only on date and datetime fields; on select, text and url it is a literal. - The fields_unsupported MCP hint names update_collection's input and the enabled types, interpolated from FIELD_TYPES. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…ps (PR #203 review) - assertFieldTypes(siteId, bundle, support) now checks the working site row and every site body a snapshot ref names in bundle.blobs, so a clean working copy cannot carry a reference or repeater definition (or a marker naming one) into a snapshot a publish or rollback could make live. Entry values need no own check: they mean nothing without a definition. - The admin bundle route keeps fields_unsupported (409 with details.issues) instead of turning it into 400 bad_request. - validateProps checks own keys (Object.hasOwn), so constructor, toString or __proto__ are unknown props; a repeater item counts as empty unless it holds a declared sub-field. - A reference's where refuses now only on date and datetime fields; on select, text and url it is a literal. - The fields_unsupported MCP hint names update_collection's input and the enabled types, interpolated from FIELD_TYPES. - The list fingerprint (RFC-0042 §4) leaves out RFC-0045's where and itemLabel, which never change a resolved value; collections, multiple, maxItems and fields count. Its schema-walking test covers the eleven types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
Madraka
force-pushed
the
claude/rfc45-contract-core-18f2ba
branch
from
November 11, 2026 01:19
425ee00 to
c3b0b65
Compare
…s (PR #203 verify) - The snapshot part of the RFC-0045 gate moves to prepareImport as assertSnapshotFieldTypes, over the bodies bodiesToWrite has hash-checked and validated as sites. An invalid blob is refused by its schema again ("is not a valid site", never a TypeError), a forged text under a hash the target holds is not read, and the refusal's details.issues is capped at 50 with total. - validateProps reads own values only (also for required), so a prop named isPrototypeOf the value leaves out is absent; repeater value checks, referenceUses and per-item requiredToPublish read item values the same way (ownValue). toLocaleString, isPrototypeOf and propertyIsEnumerable join RESERVED_FIELD_KEYS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC-0045 rollout step 1, "Contract and pure core" (#114). The two new field types exist in the contract. Every write path refuses them until step 7. Nothing changes for existing sites.
What changed
Contract (
site.schema.json,content-entry.schema.json; TS and Rust regenerated withpnpm gen)FieldDefinitiongains two variants:ReferenceFieldandRepeaterField.SubFieldDefinition: nine*SubFieldvariants with their own titles, scalar values only, and no repeater variant, so there is no recursion.FieldMigration.fromTypegainsreferenceandrepeater;FieldMigrationgainssub.collection-stage4.json. It has single, multiple andwherereferences, two repeaters, every sub-field type, a marker withsub, and fractional bounds;collections, six collections, a repeated collection,maxItems51 on a reference and on a repeater, a nested repeater, adatetimesub-field, alocalizedsub-field, a multiple select sub-field, eleven sub-fields, zero sub-fields, andfromType: "blocks".oneOfwith a one-valuetypediscriminator, nodefault,additionalProperties: false, and cross-file$refonly tocommon.schema.json#/$defs/*.The gate (
fields_unsupported; API 409, with an MCP hint)checkFieldDefinitions(site, before, { referenceTypes })refuses both types, and markers that name them, unless the caller says its capability allows them. No caller does yet.checkBundlegainsassertFieldTypes(siteId, siteRow, support). An absentFieldTypeSupportrefuses, unlikeassertGates. This one check covers import, pull, push,--replace,--revert, restores and edition copies, which all go throughimportSite.change_field_typeto either type is refused incheckOperation. The API and CLI already accept only the nine types.Pure core (
core-ts/src/references/plusfields/, exported from@cloudhal/core-ts/fields)Definition rules (
references/definitions.ts):postscollection (reference_target_unknown);whereonly with one target, on its list fields, withoutnow(invalid_query);maxItemsonly withmultiple;itemLabelnames a text, select or reference sub-field;Values:
fieldPropSchemacompiles a reference to a group-id pattern, or a list of them, and a repeater to ADR-0030's array of flat objects;valueIssueschecks repeats, empty items and each sub-value's own rules, and gives rich text in items its 50,000-character limit;requiredToPublish;too_many_references(100 per entry).resolveFieldsskips types it does not know.fieldsJsonSchema(…, { strict: true, collectionName }), withstrictFieldsSchemaandstrictSchemaCost. The RFC's example costs{8, 4, 4, 6}. The golden snapshot for existing types is unchanged.pickMember(membership byCOALESCE(translation.group, id), the locale rule, the collection filter) andreferenceIssues(missing,hiddenandlocalereasons).Dispatch tables:
COMPILEandEMITget real entries;FIELD_QUERY_OPSgets[], and structured data gets "no value";EntryFieldsForm'sCONTROLSgets a read-only JSON stub with no new words.FIELD_TYPESstays the nine enabled types, now typedEnabledFieldType, andALL_FIELD_TYPEShas all eleven. In Studio,FieldRowandListFieldsSectionshow the type badge only for enabled types; their words come with step 6.MCP
update_collection's$defsare nowdefsClosure(SiteSchema, <the enabled FieldDefinition>)instead of a hand-written list, and the input lists only the nine types.$ref. Without this change,pnpm genalone would have left#/$defs/FieldConditionand#/$defs/SubFieldDefinitiondangling.Implementation watch list
Every item on the watch list belongs to phases 2 to 4: batch-time guard SQL, purges, marker interplay and the phase-2 behaviour changes. This PR adds no SQL, no store writes and no rows.
The one interplay item that applies here: markers naming the new types (
fromType,sub) are refused by the gate on both the definitions path and the bundle path.Left for later steps
fieldDefinitionChangesdoes not yet compare referencemultiple,collectionsor sub-fields (§2.8). That needs stored values and lands with step 2.SiteGraphStoreOptions.fieldTypesandCopySiteOptions.fieldTypesare step 2. Today nothing passes a capability.Tests run locally
One file at a time, SQLite only; D1 and DO are left to CI:
core-contracts: vitest, 539 tests.cargo test --workspace(19 fixture tests) andcargo clippy -p cloudhal-schema --all-targets -D warnings.core-ts:test/references-core.test.ts(21);fields-definitions,fields-query,fields-resolve(golden unchanged),fields-values,fields-gate,fields-format,fields-conversions,fields-translation;store.test.ts -t better-sqlite3(164, including the new sharedtesting-reference-gate.ts: create/update site, field operation, import merge and replace, a marker);sync,sync-plan,sync-restore,structured-data.mcp-server: newtest/tool-schemas.test.ts(5), plusfields,collections,tools,listings.api:fields-api.test.ts -t better-sqlite3, with a new case for 409fields_unsupportedon dry run and save, and 400 forchange_field_typetoreference.ui:entry-fields-form.test.tsx, with a new stub case.studio:fields-studio.test.tsx.core-ts,api,cli,mcp-server,ui,astro-renderer,cloudflare,schedules,studio,starters/blog.pnpm drift,pnpm size:check,pnpm design:linton the touched UI files,biome checkon every touched file,mcp-tools-doc --checkanddemo-archive --check.Changeset:
@cloudhal/core-contractsminor, because the contract changed; core-ts, mcp-server and api patch. The packages are a fixed group, so this makes the next release 0.8.0. Change it to patch if you'd rather not cut a minor for a gated contract.🤖 Generated with Claude Code