Repository navigation
feat: RFC-0045 step 4, field operations on references and repeaters - #214
Merged
Merged
Conversation
…#213) When the read of the published site node failed, the working row decided. A draft that had dropped its reference fields then sent the closure-less stored plan, and referrer pages stayed stale until MAX_EDGE_AGE. The read is now retried once, and a second failure purges everything. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
… and repeaters The four RFC-0041 field operations now run on reference and repeater fields. The types stay refused on every production write path until rollout step 7: the dry run checks the definitions the operation ends with against checkFieldDefinitions and the store's capability. - sub: rename, convert or remove a repeater's sub-field in every item (FieldOperation.sub, FieldMigration.sub); empty items are dropped. - change_field_type takes collections, maxItems, subFields and itemLabel. - New conversion rows: reference single <-> multiple, text <-> reference (prefetched maps, read once per pass and by EntryEngine.write while a conversion runs), text -> repeater, repeater -> text, and multiple reference <-> repeater. - Phase B guards the ids a rewrite newly names; set_field_localized updates entry_refs.shared in its site write's batch; revert drops restored references with no member left (FieldRevertResult.dropped). - CLI: --collections, --max-items, --sub-fields <file.json>, --sub; the admin API's operation route parses the same inputs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- Values that stop converting while an operation runs: phase C's pending check reads text <-> single reference values (group-id GLOB, top-level and in items); passes report `unconvertible`; continue and revert take onInvalid "clear" (API body, CLI --clear), so the marker and field_operation_running can always be lifted. - No "fits" shortcut for text <-> reference: every value goes through the table, which keeps a value already in the new type; ids in a value converting from text are guarded as newly chosen. - Revert of a sub-field removal restores the sub-field; hasReferenceFields counts repeater sources; shared follows a rename's revert (fromKey); release deps and edition copies read through the markers; the 100-references cap applies to conversions; collections widen only where ids stay; reference -> text titles from every member with a fixed fallback; repeater -> reference dedupes; multiple is ignored where it does not apply; malformed subFields are a 400. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
One rule decides whether a text <-> reference value is already in the new type (fields/text-reference.ts), used by the dry run, phase B, the revert, writers, resolveFields with and without maps and the renderer. Phase C's pending check follows entry_refs rows for single values and sub items. The cap applies in the revert too and clears only converted sub-values; a sub revert restores only its sub-field; renderer maps read archived entries; edition copies carry unconverted text; repeater -> reference is always multiple. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
Text <-> reference values are decided by the entry_refs rows phase C reads, not by whole-entry pristineness: each operation pass loads the collection's rows for the converting path once, a person's write reads its node's. A legacy id-shaped value is checked and written as a reference when usable, else it fails and is reported; the own group fails first everywhere. A value the decision rejects keeps its rows as they are in a person's write. Edition copies carry unconverted text only when the edition's maps convert it to exactly one group. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
Reference -> text is decided per value (a string with its own row is still a reference; a list always goes through the table). A person's write holds rows per (path, group id) the decision failed, converts a repeater item by item, and skips held values in its guard; its already-held ids come from the node's rows on a text -> reference path, as do the edition copy's. Rebuilds read a converting path through its earlier type. The edition's textConverts sets the entry's group aside. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
Rebuilds keep an accepted list's rows and every group-id row converting from references; text -> multiple reference's pending check follows the rows. A person's write holds a failing value only when it is the stored one, per occurrence, runs the own-group check over held uses too, and converts items one by one only when the failing ones still fit. loadNodeRows filters source_collection; phase B's guard treats an id with its row as carried; the edition's held check is per path. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
Per-occurrence held subtraction only on paths converting to references; rebuilds read reference (multiple) <-> repeater markers through the old type; the edition's per-sub-field held check runs only under a text -> reference marker. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
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 4: the four RFC-0041 field operations work on reference and repeater fields, with the new conversion rows. The types stay refused on every production write path until step 7. The shared suites enable them with
SiteGraphStoreOptions.fieldTypes.Closes #213 (first commit).
What changed
RFC-0045: release purge gate-read failure with a draft that dropped reference fields sends the closure-less stored plan #213 (
references/release-purge.ts): when the release gate's read of the published site node fails, it is retried once, then the run purges everything. It never falls back to the working row. "Amended in 3b" and watch item 2 are updated, and there are two new cases inreferences-purge.test.ts.sub(fields/migration-sub.ts,fields-operations/plan-sub.ts):{ id, sub, fromKey | fromType }, or{ id, sub }for a removal. A removed sub-field keeps its definition until phase C.rewriteFieldsrule. An item left empty is dropped, and an emptied list is no value.fromKey(renamedFrom). This coversresolveFields, rows, list values,migratedFieldValues, release deps, Studio's form model, and the rowsINSERT'sfromKeybranch (AND migration.sub IS NULL).referenceUses,definitionOfPathandfieldAssetRefsread the old sub-key while the rename runs.change_field_typeinputs (fields-operations/retype.ts):collections,maxItems,subFieldsanditemLabel. The dry run checks the end state against the contract and againstcheckFieldDefinitionswith the store's capability, which is wherefields_unsupportedcomes from now. Thefield_operation_unsupportedrefusal is lifted.Conversion rows (
fields/conversions-references.ts):The maps come from
references/conversion-load.ts. They are read once per dry run, phase-B pass and revert pass, and byEntryEngine.writeonly while such a conversion runs.Phase B's target guard: fresh ids only (
rewriteTargetGuardmodefresh).set_field_localizedphase A:references/localize.tsdiffs the old and new site bodies inwriteSite, anchored on the new site hash. The revert's first step uses the same diff.Revert
dropped:references/revert-drops.ts, with members read once per pass and guard modepresent.field_operation_running: unchanged from 2a. It is now tested through the real operation.CLI:
--collections,--max-items,--sub-fields <file.json>and--sub. The admin API's operation route parses the same inputs, plusitemLabel. MCP's tool inputs are unchanged (step 5); the tools compile and do not pass the new inputs.Deviations and decisions (in "Amended in 4")
hasReferenceFieldsincludes a field still converting from references. Without this, reference → text left stale rows on sites with no other reference field. The suite caught it.multiline: true.multiple.ConvertContext.group).shared/fromKey. The tests assert exactly that.referencedBylists a converting referrer only after phase B, as documented. The live-versus-snapshot comparison runs without a marker, so nothing breaks.Tests
testing-field-operations-references.ts(new, 14 cases), run on SQLite, Miniflare D1 and DO SQLite. It covers:subrename, convert and remove, with rows and asset rows following;dropped);set_field_localizedphase A'ssharedin both directions, with a racing holder write;fromKey;fields-conversions-references.test.ts(pure): every row, plus thesubmarkers inrewriteFields,normalizeThroughMarkersandresolveFields.fields-cli.test.ts: the new flags, and--sub-fieldsrefused by the local store.Size: about 2.3k added lines, of which about 1k are tests. Splitting the CLI and revert
droppedinto a 4b would move only about 300 lines, so this stays one PR. I can split it if you prefer.pnpm check:@cloudhal/ui'sanalytics.test.tsx(a date-picker in a package this PR does not touch) failed once and passes alone, 31/31.@cloudhal/api'sfields-apitest still expected the route to refuse reference targets. It is now updated to expect 409fields_unsupportedfrom the store's dry run, and the full API suite passes alone, 641/641.--continue, and they all passed except that API test.🤖 Generated with Claude Code