feat(sequence): record propertyKeys on interval Add/Change local metadata at submit#27411
feat(sequence): record propertyKeys on interval Add/Change local metadata at submit#27411anthony-murphy wants to merge 2 commits into
Conversation
|
Hi! Thank you for opening this PR. Want me to review it? Based on the diff (194 lines, 3 files), I've queued these reviewers:
How this works
|
| type: "add", | ||
| localSeq, | ||
| interval, | ||
| propertyKeys: props === undefined ? undefined : new Set(Object.keys(props)), |
There was a problem hiding this comment.
Deep Review: Contract mismatch between the new JSDoc and this populate predicate.
The JSDoc added to IntervalAddLocalMetadata.propertyKeys (and the matching one on IntervalChangeLocalMetadata at the line 1357 site) states the field carries "user-defined property keys". But props here is the raw caller-supplied PropertySet, and new Set(Object.keys(props)) snapshots every key including reserved ones — reservedIntervalIdKey and reservedRangeLabelsKey.
The rest of the module is careful to segregate those reserved keys from user properties: getSerializedProperties (intervals/sequenceInterval.ts:85-102) destructures both reserved keys out before exposing properties, and createSequenceInterval strips them from stored interval properties (intervals/sequenceInterval.ts:995-997). On the change path at line 1357, reservedRangeLabelsKey is rejected upstream (intervalCollection.ts:1322-1328), but reservedIntervalIdKey can still flow through here on both paths, and label keys can still slip through the add path.
The PR intentionally lands propertyKeys as a stable data-layout contract for a future squash consumer. Any consumer that reads the JSDoc at face value will mis-handle ops that touched reserved metadata — label changes that look like user-property deltas, or vice versa. The fix is two lines while the field has zero readers; deferring locks the wrong semantic into a permanent contract.
Two valid resolutions:
- Filter reserved keys at both submit sites — either inline (
Object.keys(props).filter(k => k !== reservedIntervalIdKey && k !== reservedRangeLabelsKey)) or by sourcing from the already-computed serialized bag:Object.keys(getSerializedProperties(serializedInterval).properties), which performs the same stripping the rest of the module relies on. - Keep raw caller keys and reword the JSDoc on both interfaces to say "property keys present on this op's
propsbag at submit, including any reserved keys" — so the consumer knows it must filter.
Either resolution closes the finding, but the choice is a contract decision worth a sign-off (Abe27342 or pleath).
There was a problem hiding this comment.
Deep Review: Resolved in eae55e7. Both populate sites now source from new Set(Object.keys(getSerializedProperties(serializedInterval).properties)), which strips reservedIntervalIdKey and reservedRangeLabelsKey centrally — matches the "user-defined property keys" JSDoc. New tests in sharedString.spec.ts:1084-1108 and :1140-1163 assert reserved-key exclusion on both add and change paths.
| type: "change", | ||
| localSeq, | ||
| interval: newInterval ?? interval, | ||
| propertyKeys: props === undefined ? undefined : new Set(Object.keys(props)), |
There was a problem hiding this comment.
Deep Review: Same contract mismatch as the inline at line 1228 — new Set(Object.keys(props)) snapshots reserved keys despite the "user-defined" JSDoc on IntervalChangeLocalMetadata.propertyKeys.
On this change path, reservedRangeLabelsKey is rejected upstream at intervalCollection.ts:1322-1328, but reservedIntervalIdKey is not, so the leak path is real here too. Fix at both sites together (filter the predicate, or reword the JSDoc on both interfaces) — see the line 1228 thread for the two resolution options and the rationale.
There was a problem hiding this comment.
Deep Review: Resolved in eae55e7 alongside the line 1228 site — same getSerializedProperties(...).properties sourcing closes the reservedIntervalIdKey leak on the change path too.
Deep ReviewReviewed commit Readiness: 8/10 — ALMOST READY The prior contract mismatch is resolved: both populate sites now source from Path to Ready
Context for Reviewers
For human reviewer
Suggested description## Description
Adds an optional `propertyKeys: ReadonlySet<string>` field to both `IntervalAddLocalMetadata` and `IntervalChangeLocalMetadata` in `intervalCollectionMapInterfaces.ts`, and populates it at the two inline submit sites in `IntervalCollection.add` and `IntervalCollection.change`:
```ts
propertyKeys:
props === undefined
? undefined
: new Set(Object.keys(getSerializedProperties(serializedInterval).properties)),Sourcing from
WhyRecords which user-defined property keys an interval add/change op carried at submit time. The metadata is small, isolated, and read-only at the consumer; landing it independently keeps the data-layout change separate from the squash semantics that will eventually consume it. This reintroduces a small property-derived slice onto interfaces that #24867 had deliberately slimmed — a knowingly additive step. NotesPrep for upcoming staging-mode squash work for Follow-up: link the squash consumer issue/PR here before un-drafting. |
| @@ -989,3 +989,177 @@ describe("Shared String Obliterate", () => { | |||
| assert.equal(sharedString2.getText(), "01234BBB56789", "obliterate failed"); | |||
| }); | |||
There was a problem hiding this comment.
Deep Review: This new 174-line describe("IntervalCollection local op metadata propertyKeys", …) block exercises getIntervalCollection().add/change exclusively and asserts nothing SharedString-specific — the SharedString is only used to instantiate the harness. The established home for IntervalCollection behavior tests is the sibling packages/dds/sequence/src/test/intervalCollection.spec.ts (alongside intervalCollection.rollback.spec.ts, intervalCollection.events.spec.ts, intervalCollection.detached.spec.ts), and intervalCollection.spec.ts:87-98 already instantiates SharedStringClass for interval-collection tests, so the harness is portable.
sharedString.spec.ts's historical scope (PRs #22787, #23011) is SharedString text/obliterate regressions, not interval-collection metadata. Move the new describe block into packages/dds/sequence/src/test/intervalCollection.spec.ts, or create intervalCollection.localMetadata.spec.ts following the intervalCollection.rollback.spec.ts pattern. The relocation is mechanical — the harness only depends on SharedStringClass and MockContainerRuntimeFactory, both already in scope of the other interval specs.
If the area owners prefer the current location (harness closer to the SharedString integration boundary), that overrides the convention argument — worth a sign-off either way.
Description
Adds an optional
propertyKeys: ReadonlySet<string>field to bothIntervalAddLocalMetadataandIntervalChangeLocalMetadatainintervalCollectionMapInterfaces.ts, and populates it at the two inline submit sites inIntervalCollection.addandIntervalCollection.change(props === undefined ? undefined : new Set(Object.keys(props))).Why
Records which user-defined property keys an interval add/change op carried at submit time. The metadata is small, isolated, and read-only at the consumer; landing it independently keeps the data-layout change separate from the squash semantics that will eventually consume it.
Notes
Prep for upcoming staging-mode squash work for
SharedStringintervals. This PR does not add the squash filter helpers, the squash branch inresubmitMessage, or any new walker logic.