Repository navigation
Commit cdc42eb
fix(metadata-protocol): duplicatePackage keeps a copied field's key and name, and a bare object's view item name, in step with what they name (#23559)
Fixes #23538
Clause-②: no
## What this does
`duplicatePackage` (`packages/metadata-protocol/src/protocol.ts`,
ADR-0070 D4) promises to rewrite "every intra-package reference". Three
copied names fell out of step with what they name. All edits are in the
`duplicatePackage` region: the free-text rename, the row-name rename and
`deepRewrite`. The source-row read is untouched.
1. **A field's key and its `name` agree.** The free-text regex had a
right identifier boundary and no left one, and an object's `fields` keys
are never renamed outside a reference site. So the field keyed
`dupsrc_customer` was copied with `name: 'dupdst_customer'`, and
`x_dupsrc_customer` with `name: 'x_dupdst_customer'`. Two rules now
hold:
- **A record entry's `name` that echoes its key follows the key**
(`deepRewrite`). Both are kept, "through none", the reading consistent
with the landed permission-field rule ("the field part is never read").
- **Free text renames an object's name only as a whole identifier.** A
left boundary is added: a negative lookbehind that refuses a match
preceded by a letter, digit or underscore. `x_dupsrc_customer` stays in
a description and in a view's `columns`, the way `renameName` swaps the
prefix only at the start of a name.
2. **A view item's `OBJECT.VIEWKEY` name follows its object.** For a
`view` row with a qualified name, the object part takes `renameMap`'s
rename, whole, and the view key is left as stored: `ticket.board` copies
as `dupdst_ticket.board`. That is exactly the rename its `object`
property takes. An object the copy does not rename keeps its items'
names. This also covers an outside object that carries the source
prefix: `dupsrc_ext.board` used to become `dupdst_ext.board` while its
`object` stayed `dupsrc_ext`.
3. **The noted view-item id inside a value reproduced, so it is carried
here (triage scope).** A value that is a renamed view item's whole name
follows it: a form action's `target: 'ticket.entry'` becomes
`dupdst_ticket.entry`. This applies to whole values only, and only to
names of view items this copy renames.
Mechanism assumptions (Zone 2), measured on `origin/main` `8226755ee9`:
- Item 1: **partly confirmed.** The missing left boundary explains
`x_dupsrc_customer` only. A value EQUAL to the object's name
(`dupsrc_customer`) needs no left boundary to match, so the left
boundary alone does not satisfy the Done line. The field's `name` has to
follow its key's decision. Both changes landed, each with its own pin
and ablation.
- Item 2: **confirmed.** `renameName` renames only names with the source
prefix. A bare object's item kept `ticket.board`, and `getMetaItem({
type: 'view', name: 'ticket.board' })` served the source (`packageId:
com.qa.dupsrc`, `object: ticket`).
## Evidence (real kernel: ObjectKernel + ObjectQLPlugin, memory driver)
New pin file
`packages/objectql/src/protocol-duplicate-package-names-in-step.test.ts`,
5 cases, each with its negative control:
| case | on `main` `8226755ee9` | on this head |
|---|---|---|
| field key and `name` agree; `x_dupsrc_customer` keeps both (neg.
control); the data plane inserts by the key; the copied set's FLS rules
land | red: `failed[]` holds `dupsrc_ops`, `security-fls-unknown-field`
on `dupdst_customer.dupsrc_customer` and
`dupdst_customer.x_dupsrc_customer` | green |
| free text renames only a whole identifier (description, view
`columns`) | red: `see x_dupdst_customer.` | green |
| bare object's view item is `dupdst_ticket.board`; the by-name read
serves the copy, and `ticket.board` serves the source | red: copy stored
as `ticket.board` | green |
| view items of objects outside the package keep their names
(`dupext_account.board`, `dupsrc_ext.board`) | red: `dupsrc_ext.board`
copied as `dupdst_ext.board` | green |
| a form action's view-item target follows (prefixed control;
outside-object negative control) | red: `target: 'ticket.entry'` | green
|
Main run: `Tests 5 failed (5)`. Head run: `Tests 29 passed (29)` over
the five duplicate suites (`names-in-step`, `unprefixed-object`,
`permission-field-keys`, `objects-key-collision`,
`protocol-package-lifecycle`).
**Ablations.** One per mutation, each through
`scripts/ablation-replace.mjs` (anchor hit 1 to 0, blob changed). Then
`pnpm --filter @objectstack/metadata-protocol build` and
`ablation-dist-preflight` (marker present in 2 built files). Then the
pin file. Then the restore, proven by blob == HEAD `3d7ac7e2e7b3` and an
empty `git diff HEAD`, then a rebuild and `--absent` preflight (marker
absent from all 24 built files, tree clean).
| ablation | predicted | observed |
|---|---|---|
| A1: drop the left boundary | case 2 red | **4 red, 1 green: more
diagnostics than predicted.** The copied list view's column
`x_dupdst_customer` names no field of the copy, so the save door refused
that view (`list-view-field-unknown`), which also reds cases 1, 3 and 4
|
| A2: drop "name follows key" | case 1 red | 1 red (case 1), 4 green |
| A3: view row back to the prefix swap | cases 3, 4 red | 2 red (cases
3, 4), 3 green |
| A4: drop the whole-value view-item rename | case 5 red | 1 red (case
5), 4 green |
## Tests and gates (final head `dbd69f7592`, after merging `origin/main`
`086fa4640f`)
- `pnpm --filter @objectstack/metadata-protocol exec vitest run`: `Test
Files 236 passed | 3 skipped (239)`, `Tests 28409 passed | 19 skipped`.
- `@objectstack/objectql` `--project local`, as two shards: 210 + 210
files, `Tests 4052 passed` + `Tests 4067 passed`.
- `pnpm --filter @objectstack/metadata-protocol typecheck` exit 0. `pnpm
--filter @objectstack/objectql typecheck` exit 0 (`check:test-typecheck:
OK`).
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 67 commands from a fresh tree. Every
exit was captured before any pipe. `--ran` printed: `67 derived
famil(ies) accounted for — 66 run, 1 NOT-MEASURED`.
- The one is `pnpm check:dual-build-cjs-loads`, which exited 3 with
`PREREQUISITE NOT MET` (it needs every package's `dist/`; a full `pnpm
build` is outside this card's local scope). NOT MEASURED locally; CI's
`Lint & Repo Gates` runs it.
- The other 66 exited 0.
## Acceptance notes
- **Residual, same family, not addressed here (class a, reported for the
seat to file).** A value naming a FIELD spelled like a prefixed package
object is still renamed by the free-text rewrite as if it named the
object. The field keeps its key and now its `name`. Measured with field
`dupsrc_customer` on `dupsrc_customer`:
- On this head, a list view with `columns: ['full_name',
'dupsrc_customer']` is **refused at the copy**:
`view/dupdst_customer.board failed author-time validation …
config.columns[1] [list-view-field-unknown]`. A form view with
`sections[].fields` naming it copies silently as `['full_name',
'dupdst_customer']`.
- On `main` `8226755ee9` the list view copied with column
`dupdst_customer`, which matches the renamed `name` but not the key the
data plane addresses (`engine.insert('dupdst_customer', {
dupdst_customer })` answers `Unknown field 'dupdst_customer'`).
- So this change moves the list-view case from a silent dangling column
to a loud refusal, just as #23536 moved the permission-set case the
other way.
- Closing it needs a semantics decision about a token that names both an
object and a field. One option is to rename such a token only at
object-reference sites, as a bare name already is. That decision is left
open, not guessed.
- **Not measured, read from code:** a view CONTAINER name (no dot, e.g.
`ticket_views`) is still renamed by the prefix swap alone, so a bare
object's container keeps the source's name. A container name is
ungrammared (no declared `OBJECT.` identity), so this is not a contract
breach.
- **Ruled negative control, kept:** a view item of an object outside the
package keeps its name, so the copy shares that name with the source's
row.
- **File surface:** besides the claimed region, the pin and the
changeset, a 6-line doc comment in the sibling pin
`protocol-duplicate-package-permission-field-keys.test.ts` is updated.
It described this defect as live ("renames such a field's `name` but not
its `fields` key … a separate defect"), which this change makes false.
It now points at the new pin file.
- The card's blocker #23537 landed as PR #23545 (`8226755ee9`). #23539
remains open and is untouched here (`referencedNamesAt`,
`collectReferenceHits`, `findReferencesToMeta` and `reference-sites.ts`
are not edited). PR #23469's source-row read line is not edited either.
Changeset: `.changeset/23538-duplicate-names-in-step.md`,
`@objectstack/metadata-protocol` `patch`.
Session `session_0121ooBYRmS8ziFXm9ai5YHm`, os-dev for `domain:spec`
seat 4.
---
_Generated by [Claude
Code](https://claude.ai/code/session_0121ooBYRmS8ziFXm9ai5YHm)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 252775d commit cdc42eb
4 files changed
Lines changed: 435 additions & 10 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- objectql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26374 | 26374 | | |
26375 | 26375 | | |
26376 | 26376 | | |
| 26377 | + | |
| 26378 | + | |
| 26379 | + | |
| 26380 | + | |
| 26381 | + | |
| 26382 | + | |
| 26383 | + | |
| 26384 | + | |
26377 | 26385 | | |
26378 | 26386 | | |
26379 | 26387 | | |
| |||
26503 | 26511 | | |
26504 | 26512 | | |
26505 | 26513 | | |
26506 | | - | |
26507 | | - | |
| 26514 | + | |
| 26515 | + | |
| 26516 | + | |
26508 | 26517 | | |
26509 | 26518 | | |
26510 | 26519 | | |
| |||
26579 | 26588 | | |
26580 | 26589 | | |
26581 | 26590 | | |
| 26591 | + | |
| 26592 | + | |
| 26593 | + | |
| 26594 | + | |
| 26595 | + | |
| 26596 | + | |
| 26597 | + | |
| 26598 | + | |
26582 | 26599 | | |
26583 | 26600 | | |
26584 | | - | |
| 26601 | + | |
| 26602 | + | |
| 26603 | + | |
| 26604 | + | |
| 26605 | + | |
| 26606 | + | |
| 26607 | + | |
| 26608 | + | |
| 26609 | + | |
| 26610 | + | |
| 26611 | + | |
| 26612 | + | |
| 26613 | + | |
| 26614 | + | |
| 26615 | + | |
| 26616 | + | |
| 26617 | + | |
| 26618 | + | |
| 26619 | + | |
| 26620 | + | |
| 26621 | + | |
| 26622 | + | |
| 26623 | + | |
| 26624 | + | |
| 26625 | + | |
| 26626 | + | |
| 26627 | + | |
| 26628 | + | |
| 26629 | + | |
| 26630 | + | |
| 26631 | + | |
| 26632 | + | |
| 26633 | + | |
| 26634 | + | |
26585 | 26635 | | |
26586 | 26636 | | |
26587 | 26637 | | |
| |||
26621 | 26671 | | |
26622 | 26672 | | |
26623 | 26673 | | |
| 26674 | + | |
| 26675 | + | |
26624 | 26676 | | |
26625 | 26677 | | |
26626 | 26678 | | |
| |||
26649 | 26701 | | |
26650 | 26702 | | |
26651 | 26703 | | |
26652 | | - | |
| 26704 | + | |
26653 | 26705 | | |
26654 | 26706 | | |
26655 | 26707 | | |
26656 | 26708 | | |
| 26709 | + | |
| 26710 | + | |
| 26711 | + | |
| 26712 | + | |
| 26713 | + | |
| 26714 | + | |
| 26715 | + | |
| 26716 | + | |
| 26717 | + | |
| 26718 | + | |
| 26719 | + | |
| 26720 | + | |
| 26721 | + | |
| 26722 | + | |
| 26723 | + | |
| 26724 | + | |
26657 | 26725 | | |
26658 | 26726 | | |
26659 | 26727 | | |
| |||
26755 | 26823 | | |
26756 | 26824 | | |
26757 | 26825 | | |
26758 | | - | |
| 26826 | + | |
| 26827 | + | |
| 26828 | + | |
| 26829 | + | |
| 26830 | + | |
26759 | 26831 | | |
26760 | 26832 | | |
26761 | 26833 | | |
| |||
0 commit comments