Repository navigation
feat(core-ts): reference consumers, gated off (RFC-0045 step 2b, #114) - #206
Merged
Merged
Conversation
Ports the step-2 consumers onto the amended 2a design: - fieldAssetRefs walks repeater items (fields.<key>.<sub>, item paths); fieldAssetIssues resolves sub-field types; skips match by prefix; - field-operation rewrites write entry_refs rows, AND the target guard when reference values change, and carry the definitions anchor (text anchor, read per pass) in sites with reference fields; a stale site skips the pass's entries for the next pass; - release-deps.ts: reference values are not release dependencies; - forEdition keeps only references the edition resolves (onDropped); - cloudhal import --dry-run warns about unresolved references and relinked originals; - siteFieldTypeSupport and FIELDS_FEATURE_VERSION / FIELDS_SUPPORTED (not passed anywhere before step 7); - API: entries/pick and entries/:id/references (content:read). Suite: repeater assets, a text-field rename keeping rows, R9 through rewritePass with a racing delete, editions; release depsOf unit tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…es, shownBy, picker folding) - #0: the MCP precheck builds its asset skip set from full issue paths, as the store does, so an item-level issue no longer hides another item's non-image (precheck test). - #1: fieldAssetRefs keeps every use's path (`paths`); fieldAssetIssues reports each use not under a skip path, the asset_refs rows still one per asset and field. - #2, #3: editionReferences applies the edition field's write rules (where, a single target, maxItems, the 100-reference cap, repeater maxItems), reads values through a running rename's fromKey (storedKey) and writes them back under that key; every drop carries its reason. - #7: shownBy leaves out only the row's own holder, so it no longer depends on pagination (suite case paging at 20 and 1). - #8: the picker folds q and the candidates' titles and slugs with foldName in JS (case, diacritics, Turkish İ/ı) over at most 5,000 candidates read as small columns; tests with ASCII and non-ASCII uppercase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…red target rule (PR #206 verify) - Picker (verify 1, 2): entry-pick.ts reads each allowed collection as a key range of nodes_site_kind_key (`<collection>:<locale>:<slug>`), at most 1,000 rows per request, folds q and every row's title and slug with foldName, and answers truncated: true with a cursor when the budget ends first, so any match is found by asking again. A q that folds to nothing matches nothing. Tests on better-sqlite3, D1 and DO, and an EXPLAIN QUERY PLAN check; no migration. - Editions (verify 3): forEdition passes the edition's stored entry; a key whose value is unchanged is kept as stored, and groups it already holds under that key are not fresh (no where or archived check), as the write. - Shared target rule (verify 4): references/targets.ts holds readMembers (any status) and targetProblem; the writer's useIssue and the edition mapping both use them, so an archived same-site original still lends its where field to a translation member. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…st (PR #206 verify 2) - Picker: collection ranges are visited in the order of their key prefixes, not their names ("post-x:" and "news2:" sort before "post:" and "news:"), so the cursor never skips a range. Test with post/post-x and news/news2 in one request and across one-row continuations, with and without a locale, on all three drivers. - Editions: the per-entry cap is spent on every unchanged key first (pass one), then the changed keys are carried (pass two), and it counts every reference value, repeated targets included; the Set of held groups is only the freshness test. Suite case: two unchanged repeaters with one target 50 times each fill the cap; the changed key is dropped (cap) and the edition write accepts the result. - RFC-0045 §5: the picker line gives cursor, {entries, cursor?, truncated?}, the 1,000-row budget and key order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
…#206 CI) GET …/entries/pick and GET …/entries/:entryId/references are read-only content:read routes; they join PLAYGROUND_ALLOW beside the other entry reads. 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 2b, the second half of "Store and integrity", for #114. It builds on 2a (PR #205, merged) and ports the seven "Moves to 2b" items onto 2a's amended design:
usableMembersas one parameter;unknown_entryfor referrer writes.Production still refuses
referenceandrepeateron every write path, and nothing passes aFieldTypeSupportbefore step 7. Each consumer here is therefore reachable only in the shared suites, which enable the types throughSiteGraphStoreOptions.fieldTypes.What 2b contains, per "Moves to 2b" item
referenceIssuesincloudhal import --dry-run. Incli/src/commands/import-references.ts, a dry run prints warnings for:These are warnings, never refusals.
Assets inside repeater items (
media/field-refs.ts).fieldAssetRefswalks items, recordsfields.<key>.<sub>(one row per asset and field), and returns each use's item path.fieldAssetIssuesresolves sub-field types, so a non-image in an image sub-field is refused. It reports at every item that uses the file:fieldAssetRefskeeps each use's path inpaths, whileasset_refsstays one row per asset and field.store/entry-fields.ts) and the MCP precheck (mcp-server/src/entry-fields.ts) pass full issue paths.Rows and the target guard in field-operation rewrites (
fields-operations/rewrite.ts).entry_refsrows withentryRefStatements, next to its asset rows.rewriteTargetGuardinto its CAS.Kind-aware release
depsOf(core-ts/src/release-deps.ts).fromKey, are stripped by the pinned site's definitions beforereferencedNodeIds.releases.tsonly imports it and stays at 902 lines.Edition id mapping (
references/editions.ts,forEdition). It keeps exactly what the edition's own write accepts:where;maxItemsfor a multiple one;maxItemsrepeater items.Emptied items are dropped. Values are read through a running rename's
fromKeyand written back under the same key. Every drop is reported with its path and reason through the new optionalonDropped.whereand archived checks are skipped for them, exactly as the edition's own write does.references/targets.ts:readMembersreads members of any status, andtargetProblemapplies the target rule. An archived same-site original therefore still lends itswherefield to a translation member.The
fields: 2capability.cloudflare/src/features.ts:siteFeaturesreadsfields, andsiteFieldTypeSupportis the twin ofsiteGateSupport(yes from 2, remembered, a no asked again).@cloudhal/core-ts/fieldsexportsFIELDS_FEATURE_VERSIONand the Node constantFIELDS_SUPPORTED.API. In
api/src/routes/entry-references.ts, both routes arecontent:readand declared inscopes.ts:GET /sites/:siteId/entries/pickreturns at most 20 cards from the allowed collections, never archived ones.qis at most 100 characters and refused before it is used. A per-memberrate_limitswindow applies. The search is inapi/src/entry-pick.ts:<collection>:<locale>:<slug>, andnodes_site_kind_key (site_id, kind, key)is a unique index. Each allowed collection, or collection and locale, is one index range, read in key order with a keyset cursor. The ranges are visited in the order of their key prefixes, not their names, because "post-x:" sorts before "post:". TheWHEREnames only indexed columns, so every row read is a row returned. An EXPLAIN QUERY PLAN test checks the index search.limitmatches, the answer carriestruncated: trueand acursor. Asking again withcursorcontinues where the last request stopped, so any matching entry can be found.qand each row's title and slug are folded in JS with the samefoldName(case, diacritics, Turkish İ and ı) and matched as a substring, with no pattern (rule 13). Aqwith nothing left after folding, such as only punctuation, matches nothing.GET /sites/:siteId/entries/:entryId/referencesreturns the group's rows, published first, each with the same-site members that show its value, paged. A member is left out of a row'sshownByonly when it is that row's holder, so the result does not depend on where the pages are cut.R9 and where it is tested
R9 is "field-operation phase B against deleting a target". It cannot happen through an operation before step 4, because operations that convert into references are refused. The rewrite path those operations will use is tested directly:
testing-references.ts, every backend) callsrewritePasswith a decision that changesvenue, while a racing delete removes the new target.rewriteTargetGuardfrom the CAS makes the test fail; I checked by doing that.Other new suite cases:
asset_refs, kept through a rebuild, and blocking its asset's delete;fields/speakers/1/photo;where, single target,maxItems) and reading throughfromKey;wherefield to a translation member;references-store.test.ts: releasedepsOfdrops reference values and keeps every other dependency.api/test/entry-pick.test.tsruns on better-sqlite3, D1 and DO. It covers:limitwith a cursor across collections;The API suite (
api/src/testing-references.ts, sqlite and D1) covers:Two more tests cover the review fixes.
mcp-server/test/entry-fields-assets.test.tschecks that the precheck reports an item-level issue and another item's non-image together.references-store.test.tschecks every use's path and per-use skips.cli/test/import-references.test.tscovers the dry-run warnings, andcloudflare/test/features.test.tscoversfields: 2.Deviations
truncatedwith acursorwhen that runs out first.forEditionreports drops through anonDroppedcallback. The MCP translation tool will show them in step 5. OQ 14's relinked originals are reported inimport --dry-runonly; Studio's collection screen is step 6.referrerslists holders with the members that show their value (shownBy). It does not list the inheriting translations as their own rows.Tests run (locally, one file at a time)
These ran on 9d02bf0.
The new SQL ran on all three drivers:
test/store.test.ts -t "<backend>.*RFC-0045 step 2": 49/49 on each of better-sqlite3, D1 (Miniflare) and Durable Object (Miniflare);api/test/entry-pick.test.ts: 10/10, three cases on each driver plus the query plan;api/test/references-api.test.ts: better-sqlite3 and D1, 8/8.Other runs:
store.test.ts -t better-sqlite3214 passed;references-storeandeditions27;translation-tools8;pnpm size:checkpasses, and biome reports no errors.I checked that each of these fails without its fix:
slice(0, 2);shownBy, with the old holder filter;readMembers;editions.ts.Not run locally: the D1 and DO variants of the rest of
store.test.ts, and the full package suites (left to CI).🤖 Generated with Claude Code