@superdoc/sdk 2.13.0-next.12 → 2.13.0-next.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -641,8 +641,8 @@ function evaluateChecks(pre, post, checks) {
641
641
  else if (check.kind === 'tracked-change-count-delta') {
642
642
  results.push({
643
643
  check,
644
- passed: post.trackedChanges.length - pre.trackedChanges.length === check.delta,
645
- detail: `pre=${pre.trackedChanges.length} post=${post.trackedChanges.length}`,
644
+ passed: post.counts.trackedChanges - pre.counts.trackedChanges === check.delta,
645
+ detail: `pre=${pre.counts.trackedChanges} post=${post.counts.trackedChanges}`,
646
646
  });
647
647
  }
648
648
  else if (check.kind === 'revision-changed') {
@@ -1544,11 +1544,25 @@ async function runDeleteBlocks(doc, args) {
1544
1544
  const changeMode = parseChangeMode(args.changeMode);
1545
1545
  const executedOperations = [];
1546
1546
  const deleted = [];
1547
+ const addressedTrackedChangeIds = new Set();
1548
+ let allTrackedEffectsAddressed = true;
1547
1549
  for (const target of targets) {
1548
1550
  try {
1549
1551
  const result = await deleteFn({ target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId } }, changeMode ? { changeMode } : undefined);
1550
1552
  executedOperations.push({ operationId: 'doc.blocks.delete', result: compactOpResult(result) });
1551
1553
  deleted.push({ nodeId: target.nodeId, nodeType: target.nodeType });
1554
+ if (changeMode === 'tracked') {
1555
+ // One block transaction can create multiple logical review entities
1556
+ // (for example, text plus its section break). Exact receipt refs let
1557
+ // us fail closed on missing entities. Older handles omit refs, and
1558
+ // adjacent deletions can coalesce into one review entity, so their
1559
+ // truthful fallback is the positive observed inventory delta rather
1560
+ // than one assumed entity per operation.
1561
+ const refIds = trackedChangeRefIds(result);
1562
+ allTrackedEffectsAddressed &&= refIds.size > 0;
1563
+ for (const entityId of refIds)
1564
+ addressedTrackedChangeIds.add(entityId);
1565
+ }
1552
1566
  }
1553
1567
  catch (err) {
1554
1568
  errors$1.push({
@@ -1562,14 +1576,37 @@ async function runDeleteBlocks(doc, args) {
1562
1576
  // Tracked deletions leave the block in place until the revision is decided,
1563
1577
  // so the block count cannot move — count the structural revisions instead.
1564
1578
  // Direct deletions must show one fewer block of each deleted node type.
1579
+ const observedTrackedChangeDelta = post.counts.trackedChanges - pre.counts.trackedChanges;
1580
+ // A later deletion can coalesce the batch and replace entity identities
1581
+ // returned by earlier operation receipts. For a multi-operation batch the
1582
+ // final inventory is therefore the authoritative count. A single operation
1583
+ // keeps using its addressed refs so fan-out remains verifiable even when
1584
+ // the inventory listing is capped.
1585
+ const expectedTrackedChangeDelta = deleted.length > 1
1586
+ ? observedTrackedChangeDelta
1587
+ : allTrackedEffectsAddressed
1588
+ ? addressedTrackedChangeIds.size
1589
+ : Math.max(observedTrackedChangeDelta, addressedTrackedChangeIds.size);
1565
1590
  const checks = changeMode === 'tracked'
1566
- ? [{ kind: 'tracked-change-count-delta', delta: deleted.length }]
1591
+ ? [
1592
+ {
1593
+ kind: 'tracked-change-count-delta',
1594
+ delta: expectedTrackedChangeDelta,
1595
+ },
1596
+ ]
1567
1597
  : [...new Set(deleted.map((entry) => entry.nodeType))].map((nodeType) => ({
1568
1598
  kind: 'block-count-delta',
1569
1599
  nodeType,
1570
1600
  delta: -deleted.filter((entry) => entry.nodeType === nodeType).length,
1571
1601
  }));
1572
1602
  const verification = evaluateChecks(pre, post, checks);
1603
+ if (changeMode === 'tracked' && observedTrackedChangeDelta <= 0) {
1604
+ verification[0] = {
1605
+ ...verification[0],
1606
+ passed: false,
1607
+ detail: `pre=${pre.counts.trackedChanges} post=${post.counts.trackedChanges}; no tracked review entity was created`,
1608
+ };
1609
+ }
1573
1610
  // Compare against the DEDUPED target list: two selectors naming the same
1574
1611
  // block are one deletion, and counting the raw selectors would report a
1575
1612
  // fully applied call as `partial` with no error to explain it. Selectors
@@ -4513,6 +4550,15 @@ function compactOpResult(result) {
4513
4550
  compact.stepCount = result.steps.length;
4514
4551
  return compact;
4515
4552
  }
4553
+ function trackedChangeRefIds(result) {
4554
+ const refs = asRecord(result)?.trackedChangeRefs;
4555
+ if (!Array.isArray(refs))
4556
+ return new Set();
4557
+ return new Set(refs.flatMap((entry) => {
4558
+ const entityId = asString(asRecord(entry)?.entityId);
4559
+ return entityId ? [entityId] : [];
4560
+ }));
4561
+ }
4516
4562
  function parseScopedReplaceArgs(args, changeMode) {
4517
4563
  if (args.action !== 'replace_text')
4518
4564
  return null;
@@ -638,8 +638,8 @@ function evaluateChecks(pre, post, checks) {
638
638
  else if (check.kind === 'tracked-change-count-delta') {
639
639
  results.push({
640
640
  check,
641
- passed: post.trackedChanges.length - pre.trackedChanges.length === check.delta,
642
- detail: `pre=${pre.trackedChanges.length} post=${post.trackedChanges.length}`,
641
+ passed: post.counts.trackedChanges - pre.counts.trackedChanges === check.delta,
642
+ detail: `pre=${pre.counts.trackedChanges} post=${post.counts.trackedChanges}`,
643
643
  });
644
644
  }
645
645
  else if (check.kind === 'revision-changed') {
@@ -1541,11 +1541,25 @@ async function runDeleteBlocks(doc, args) {
1541
1541
  const changeMode = parseChangeMode(args.changeMode);
1542
1542
  const executedOperations = [];
1543
1543
  const deleted = [];
1544
+ const addressedTrackedChangeIds = new Set();
1545
+ let allTrackedEffectsAddressed = true;
1544
1546
  for (const target of targets) {
1545
1547
  try {
1546
1548
  const result = await deleteFn({ target: { kind: 'block', nodeType: target.nodeType, nodeId: target.nodeId } }, changeMode ? { changeMode } : undefined);
1547
1549
  executedOperations.push({ operationId: 'doc.blocks.delete', result: compactOpResult(result) });
1548
1550
  deleted.push({ nodeId: target.nodeId, nodeType: target.nodeType });
1551
+ if (changeMode === 'tracked') {
1552
+ // One block transaction can create multiple logical review entities
1553
+ // (for example, text plus its section break). Exact receipt refs let
1554
+ // us fail closed on missing entities. Older handles omit refs, and
1555
+ // adjacent deletions can coalesce into one review entity, so their
1556
+ // truthful fallback is the positive observed inventory delta rather
1557
+ // than one assumed entity per operation.
1558
+ const refIds = trackedChangeRefIds(result);
1559
+ allTrackedEffectsAddressed &&= refIds.size > 0;
1560
+ for (const entityId of refIds)
1561
+ addressedTrackedChangeIds.add(entityId);
1562
+ }
1549
1563
  }
1550
1564
  catch (err) {
1551
1565
  errors.push({
@@ -1559,14 +1573,37 @@ async function runDeleteBlocks(doc, args) {
1559
1573
  // Tracked deletions leave the block in place until the revision is decided,
1560
1574
  // so the block count cannot move — count the structural revisions instead.
1561
1575
  // Direct deletions must show one fewer block of each deleted node type.
1576
+ const observedTrackedChangeDelta = post.counts.trackedChanges - pre.counts.trackedChanges;
1577
+ // A later deletion can coalesce the batch and replace entity identities
1578
+ // returned by earlier operation receipts. For a multi-operation batch the
1579
+ // final inventory is therefore the authoritative count. A single operation
1580
+ // keeps using its addressed refs so fan-out remains verifiable even when
1581
+ // the inventory listing is capped.
1582
+ const expectedTrackedChangeDelta = deleted.length > 1
1583
+ ? observedTrackedChangeDelta
1584
+ : allTrackedEffectsAddressed
1585
+ ? addressedTrackedChangeIds.size
1586
+ : Math.max(observedTrackedChangeDelta, addressedTrackedChangeIds.size);
1562
1587
  const checks = changeMode === 'tracked'
1563
- ? [{ kind: 'tracked-change-count-delta', delta: deleted.length }]
1588
+ ? [
1589
+ {
1590
+ kind: 'tracked-change-count-delta',
1591
+ delta: expectedTrackedChangeDelta,
1592
+ },
1593
+ ]
1564
1594
  : [...new Set(deleted.map((entry) => entry.nodeType))].map((nodeType) => ({
1565
1595
  kind: 'block-count-delta',
1566
1596
  nodeType,
1567
1597
  delta: -deleted.filter((entry) => entry.nodeType === nodeType).length,
1568
1598
  }));
1569
1599
  const verification = evaluateChecks(pre, post, checks);
1600
+ if (changeMode === 'tracked' && observedTrackedChangeDelta <= 0) {
1601
+ verification[0] = {
1602
+ ...verification[0],
1603
+ passed: false,
1604
+ detail: `pre=${pre.counts.trackedChanges} post=${post.counts.trackedChanges}; no tracked review entity was created`,
1605
+ };
1606
+ }
1570
1607
  // Compare against the DEDUPED target list: two selectors naming the same
1571
1608
  // block are one deletion, and counting the raw selectors would report a
1572
1609
  // fully applied call as `partial` with no error to explain it. Selectors
@@ -4510,6 +4547,15 @@ function compactOpResult(result) {
4510
4547
  compact.stepCount = result.steps.length;
4511
4548
  return compact;
4512
4549
  }
4550
+ function trackedChangeRefIds(result) {
4551
+ const refs = asRecord(result)?.trackedChangeRefs;
4552
+ if (!Array.isArray(refs))
4553
+ return new Set();
4554
+ return new Set(refs.flatMap((entry) => {
4555
+ const entityId = asString(asRecord(entry)?.entityId);
4556
+ return entityId ? [entityId] : [];
4557
+ }));
4558
+ }
4513
4559
  function parseScopedReplaceArgs(args, changeMode) {
4514
4560
  if (args.action !== 'replace_text')
4515
4561
  return null;