things-api 0.19.0 → 0.19.1

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.
Files changed (131) hide show
  1. package/README.md +3 -1
  2. package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
  3. package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
  4. package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
  5. package/dist/audit/schema.d.ts +19 -2
  6. package/dist/audit/schema.js.map +1 -1
  7. package/dist/capability.d.ts +166 -4
  8. package/dist/capability.js +303 -13
  9. package/dist/capability.js.map +1 -1
  10. package/dist/cli/commands/doctor.d.ts +13 -1
  11. package/dist/cli/commands/doctor.js +24 -14
  12. package/dist/cli/commands/doctor.js.map +1 -1
  13. package/dist/cli/commands/helpers.js +14 -3
  14. package/dist/cli/commands/helpers.js.map +1 -1
  15. package/dist/cli/commands/mcp.js +8 -0
  16. package/dist/cli/commands/mcp.js.map +1 -1
  17. package/dist/cli/commands/repeat-flags.js +34 -2
  18. package/dist/cli/commands/repeat-flags.js.map +1 -1
  19. package/dist/cli/commands/setup.js +16 -3
  20. package/dist/cli/commands/setup.js.map +1 -1
  21. package/dist/cli/commands/writes.js +102 -67
  22. package/dist/cli/commands/writes.js.map +1 -1
  23. package/dist/cli/interrupt.d.ts +54 -6
  24. package/dist/cli/interrupt.js +78 -12
  25. package/dist/cli/interrupt.js.map +1 -1
  26. package/dist/cli/main.js +8 -5
  27. package/dist/cli/main.js.map +1 -1
  28. package/dist/client.d.ts +1 -1
  29. package/dist/contracts.d.ts +3 -3
  30. package/dist/contracts.js +1 -1
  31. package/dist/deputy/install.d.ts +15 -1
  32. package/dist/deputy/install.js +46 -6
  33. package/dist/deputy/install.js.map +1 -1
  34. package/dist/deputy/routing.d.ts +16 -0
  35. package/dist/deputy/routing.js +70 -0
  36. package/dist/deputy/routing.js.map +1 -1
  37. package/dist/deputy/wake.d.ts +61 -0
  38. package/dist/deputy/wake.js +149 -0
  39. package/dist/deputy/wake.js.map +1 -0
  40. package/dist/diagnose.d.ts +6 -4
  41. package/dist/diagnose.js +20 -4
  42. package/dist/diagnose.js.map +1 -1
  43. package/dist/direct-setup.d.ts +31 -5
  44. package/dist/direct-setup.js +200 -26
  45. package/dist/direct-setup.js.map +1 -1
  46. package/dist/host-access.d.ts +12 -0
  47. package/dist/host-access.js +12 -0
  48. package/dist/host-access.js.map +1 -1
  49. package/dist/index.d.ts +3 -3
  50. package/dist/index.js +2 -2
  51. package/dist/index.js.map +1 -1
  52. package/dist/mcp/server.js +23 -9
  53. package/dist/mcp/server.js.map +1 -1
  54. package/dist/op-result.js +8 -1
  55. package/dist/op-result.js.map +1 -1
  56. package/dist/session-grant.js +16 -1
  57. package/dist/session-grant.js.map +1 -1
  58. package/dist/surface-copy.d.ts +11 -3
  59. package/dist/surface-copy.js +11 -3
  60. package/dist/surface-copy.js.map +1 -1
  61. package/dist/wizard.js +8 -1
  62. package/dist/wizard.js.map +1 -1
  63. package/dist/write/availability.d.ts +0 -15
  64. package/dist/write/availability.js +7 -62
  65. package/dist/write/availability.js.map +1 -1
  66. package/dist/write/batch.js +17 -6
  67. package/dist/write/batch.js.map +1 -1
  68. package/dist/write/commands.d.ts +10 -6
  69. package/dist/write/commands.js +120 -31
  70. package/dist/write/commands.js.map +1 -1
  71. package/dist/write/failure-hints.d.ts +29 -6
  72. package/dist/write/failure-hints.js +9 -14
  73. package/dist/write/failure-hints.js.map +1 -1
  74. package/dist/write/guards.js +3 -2
  75. package/dist/write/guards.js.map +1 -1
  76. package/dist/write/move.js +9 -12
  77. package/dist/write/move.js.map +1 -1
  78. package/dist/write/operations.d.ts +15 -8
  79. package/dist/write/operations.js +9 -0
  80. package/dist/write/operations.js.map +1 -1
  81. package/dist/write/opid.d.ts +67 -23
  82. package/dist/write/opid.js +141 -19
  83. package/dist/write/opid.js.map +1 -1
  84. package/dist/write/param-schema.d.ts +0 -44
  85. package/dist/write/param-schema.js +10 -3
  86. package/dist/write/param-schema.js.map +1 -1
  87. package/dist/write/pipeline.d.ts +38 -7
  88. package/dist/write/pipeline.js +104 -18
  89. package/dist/write/pipeline.js.map +1 -1
  90. package/dist/write/pre-state.d.ts +30 -16
  91. package/dist/write/pre-state.js +88 -20
  92. package/dist/write/pre-state.js.map +1 -1
  93. package/dist/write/promote-clone.js +132 -18
  94. package/dist/write/promote-clone.js.map +1 -1
  95. package/dist/write/repeat-collateral.d.ts +31 -0
  96. package/dist/write/repeat-collateral.js +153 -0
  97. package/dist/write/repeat-collateral.js.map +1 -0
  98. package/dist/write/resolution-timestamps.js +25 -2
  99. package/dist/write/resolution-timestamps.js.map +1 -1
  100. package/dist/write/template-mutation.js +163 -16
  101. package/dist/write/template-mutation.js.map +1 -1
  102. package/dist/write/vectors/applescript.js +3 -10
  103. package/dist/write/vectors/applescript.js.map +1 -1
  104. package/dist/write/vectors/simulator.js +8 -2
  105. package/dist/write/vectors/simulator.js.map +1 -1
  106. package/dist/write/vectors/types.d.ts +211 -1
  107. package/dist/write/vectors/ui-certification.js +63 -13
  108. package/dist/write/vectors/ui-certification.js.map +1 -1
  109. package/dist/write/vectors/ui-chord.d.ts +144 -0
  110. package/dist/write/vectors/ui-chord.js +428 -0
  111. package/dist/write/vectors/ui-chord.js.map +1 -0
  112. package/dist/write/vectors/ui-drag.d.ts +8 -4
  113. package/dist/write/vectors/ui-drag.js +2 -0
  114. package/dist/write/vectors/ui-drag.js.map +1 -1
  115. package/dist/write/vectors/ui-recipes.d.ts +51 -3
  116. package/dist/write/vectors/ui-recipes.js +452 -44
  117. package/dist/write/vectors/ui-recipes.js.map +1 -1
  118. package/dist/write/vectors/ui.d.ts +267 -10
  119. package/dist/write/vectors/ui.js +1006 -64
  120. package/dist/write/vectors/ui.js.map +1 -1
  121. package/dist/write/vectors/url-scheme.js +3 -0
  122. package/dist/write/vectors/url-scheme.js.map +1 -1
  123. package/dist/write/verify/delta.d.ts +41 -0
  124. package/dist/write/verify/delta.js +36 -2
  125. package/dist/write/verify/delta.js.map +1 -1
  126. package/dist/write/verify/poller.d.ts +4 -1
  127. package/dist/write/verify/poller.js +14 -0
  128. package/dist/write/verify/poller.js.map +1 -1
  129. package/package.json +1 -1
  130. package/schema/envelope.schema.json +1 -1
  131. package/skills/things-cli/SKILL.md +1 -1
@@ -2,6 +2,7 @@ import { localToday, zonedWallInstant } from "../model/dates.js";
2
2
  import { byUuid } from "../read/detail.js";
3
3
  import { manualLogDateEpoch, pendingLogCount } from "../read/log-boundary.js";
4
4
  import { isLooseRef } from "../read/pseudo-area.js";
5
+ import { RESOLUTION_TIMESTAMP_EXPECTED } from "../surface-copy.js";
5
6
  import { areaMemberCounts, childTagTitles, classifyHeadingConvert, classifyHeadingDissolve, classifyHeadingMoveToProject, classifyProjectRepeat, computeHeadingMovePre, computeReorderPre, emptyPreState, loadTarget, projectChildren, projectStatus, projectSubtreeUuids, resolveArea, resolveHeading, resolveProject, resolveTag, sameTitleTaskUuids, seriesRowUuids, trashedCount, } from "./pre-state.js";
6
7
  import { assertNotesModesExclusive, normalizeReminder, updateAssertions, updateWireParams, whenAssertions, whenWithReminder, } from "./update-fields.js";
7
8
  import { resolveTagRefs } from "./tag-refs.js";
@@ -10,7 +11,7 @@ import { assertRepeatRule } from "./repeat-rule.js";
10
11
  import { assessOffRuleFirst, deadlineDriveNext, deriveFixedAnchor } from "./repeat-anchor.js";
11
12
  import { expectedRuleAssertions } from "./repeat-asserts.js";
12
13
  import { escapeAppleScript } from "./vectors/applescript.js";
13
- import { areaReorderSidebarRecipe, convertToProjectRecipe, dissolveHeadingRecipe, headingConvertToProjectRecipe, moveHeadingToProjectRecipe, makeRepeatingRecipe, createNextCopyRecipe, pauseRepeatRecipe, projectMakeRepeatingRecipe, projectPauseRepeatRecipe, projectRescheduleRepeatRecipe, projectResumeRepeatRecipe, rescheduleRepeatRecipe, resumeRepeatRecipe, } from "./vectors/ui-recipes.js";
14
+ import { areaReorderSidebarRecipe, convertToProjectRecipe, dissolveHeadingRecipe, headingConvertToProjectRecipe, moveHeadingChordRecipe, moveHeadingToProjectRecipe, makeRepeatingRecipe, createNextCopyRecipe, pauseRepeatRecipe, projectMakeRepeatingRecipe, projectPauseRepeatRecipe, projectRescheduleRepeatRecipe, projectResumeRepeatRecipe, rescheduleRepeatRecipe, resumeRepeatRecipe, } from "./vectors/ui-recipes.js";
14
15
  import { buildRepeatingFingerprint } from "./verify/delta.js";
15
16
  // ------------------------------------------------------------------ helpers
16
17
  function thingsUrl(command, params, token) {
@@ -1416,22 +1417,26 @@ const reorder = {
1416
1417
  };
1417
1418
  // ---- resolution-timestamp normalization (§5 of the plan) ------------------
1418
1419
  /**
1419
- * Parse an ISO date (`2025-01-15`) OR datetime (`2025-01-15T09:30[:ss]`) and
1420
- * resolve it to a single UTC instant in the effective `zone`. A date-only value
1421
- * lands at NOON in that zone (B-DATEONLY: noon decodes to the intended calendar
1422
- * date in every zone; midnight can slip a day). A datetime is read as wall-clock
1423
- * time in the effective zone. `zone` undefined = the process-local (app host)
1424
- * zone, which for a local CLI run IS the app's own zone.
1420
+ * Parse a date (`2025-01-15`) OR datetime (`2025-01-15T09:30[:ss]`, or the same
1421
+ * value spelled with a space `2025-01-15 09:30`) and resolve it to a single
1422
+ * UTC instant in the effective `zone`. Both separators are first-class
1423
+ * spellings of the SAME instant: the space form is what a human (or a shell
1424
+ * heredoc) types, the `T` form is what a serializer emits, and neither is
1425
+ * rewritten into the other. A date-only value lands at NOON in that zone
1426
+ * (B-DATEONLY: noon decodes to the intended calendar date in every zone;
1427
+ * midnight can slip a day). A datetime is read as wall-clock time in the
1428
+ * effective zone. `zone` undefined = the process-local (app host) zone, which
1429
+ * for a local CLI run IS the app's own zone.
1425
1430
  */
1426
1431
  export function resolveResolutionInstant(input, zone) {
1427
1432
  const dateOnly = /^(\d{4}-\d{2}-\d{2})$/.exec(input);
1428
1433
  if (dateOnly !== null)
1429
1434
  return zonedWallInstant(dateOnly[1], 12, 0, 0, zone);
1430
- const dt = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(input);
1435
+ const dt = /^(\d{4}-\d{2}-\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?$/.exec(input);
1431
1436
  if (dt !== null) {
1432
1437
  return zonedWallInstant(dt[1], Number(dt[2]), Number(dt[3]), dt[4] === undefined ? 0 : Number(dt[4]), zone);
1433
1438
  }
1434
- throw new RangeError(`invalid timestamp "${input}" — expected an ISO date (YYYY-MM-DD) or datetime (YYYY-MM-DDTHH:mm)`);
1439
+ throw new RangeError(`invalid timestamp "${input}" — expected ${RESOLUTION_TIMESTAMP_EXPECTED}`);
1435
1440
  }
1436
1441
  const pad2 = (n) => String(n).padStart(2, "0");
1437
1442
  /** The host-local calendar date (`YYYY-MM-DD`) an instant renders as — what the verify layer observes for stopped/created. */
@@ -1662,9 +1667,18 @@ const headingAdd = {
1662
1667
  });
1663
1668
  },
1664
1669
  };
1670
+ /**
1671
+ * project.move-heading — within-project heading order, on the ARROW-CHORD ui
1672
+ * vector (CHORDMH1, on the HEADORD1 law). Things 3.23 left the private reorder
1673
+ * command declared and inert, which took heading order offline entirely; the
1674
+ * replacement is the app's own keyboard affordance (⌘↑/⌘↓/⌘⌥↑/⌘⌥↓ on a selected
1675
+ * heading row), driven as a verified closed loop by src/write/vectors/ui-chord.ts.
1676
+ * This is the ONE reorder that leaves the `privateReorderIsNoOp` range — every
1677
+ * other scope still routes through its own gate.
1678
+ */
1665
1679
  const projectMoveHeading = {
1666
1680
  op: "project.move-heading",
1667
- hazards: ["H-UNKNOWN-DESTINATION", "H-HEADING-ORDER"],
1681
+ hazards: ["H-UNKNOWN-DESTINATION", "H-HEADING-ORDER", "H-UI-DRIVE"],
1668
1682
  preRead(db, params) {
1669
1683
  const pre = emptyPreState();
1670
1684
  pre.destProject = resolveProject(db, params.project);
@@ -1672,24 +1686,41 @@ const projectMoveHeading = {
1672
1686
  return pre;
1673
1687
  },
1674
1688
  expectedDelta(pre) {
1675
- // The reorder wire re-ranks the FULL heading list; verify the whole target
1676
- // order (strictly ascending "index"). Children follow their heading (scf
1677
- // P1), so no per-child assertion is needed.
1678
- return { mode: "ordering", key: "index", sequence: pre.headingMove?.targetOrder ?? [] };
1689
+ const move = pre.headingMove;
1690
+ return {
1691
+ mode: "ordering",
1692
+ key: "index",
1693
+ // The full end state: every heading of the project, strictly ascending.
1694
+ sequence: move?.targetOrder ?? [],
1695
+ // RRF1: the headings the caller did NOT name must hold their EXACT prior
1696
+ // rank — a chord is a single-row write, and this is what proves the one
1697
+ // that landed was the row the plan addressed (a heading row exposes no
1698
+ // title to read the selection back through).
1699
+ unchanged: move?.untouched ?? [],
1700
+ // Children follow their heading through an intact FK, un-renumbered and
1701
+ // un-touched (no userModificationDate bump — that is what `frozen` adds).
1702
+ frozen: (move?.children ?? []).map((c) => ({
1703
+ uuid: c.uuid,
1704
+ assert: [{ field: "heading.uuid", equals: c.heading }],
1705
+ })),
1706
+ ...(move?.targetOrder[0] !== undefined && { subject: move.targetOrder[0] }),
1707
+ };
1679
1708
  },
1680
- compile(_params, vector, pre) {
1681
- // The same native private-reorder wire the old `reorder --scope headings`
1682
- // used (experimental — gated by allow-experimental + the sdef canary),
1683
- // fed the computed full order.
1684
- if (vector !== "applescript")
1709
+ compile(params, vector, pre) {
1710
+ if (vector !== "ui")
1685
1711
  unsupportedVector(this.op, vector);
1686
- // #V11: send the MINIMAL front-cluster wire, not the full order — an archived
1687
- // heading that need not move stays out of it (untouched, HEADSORT H-UNSWEPT);
1688
- // any archived heading the target FORCES in (pre.headingMove.reopened) is
1689
- // reopened and disclosed. NEVER put un-targeted rows into the wire.
1690
- const order = pre.headingMove?.wire ?? [];
1691
- return osa(`${PRIVATE_REORDER_COMMAND} project id ${q(pre.destProject?.resolved?.uuid ?? "")} ` +
1692
- `with ids ${q(order.join(","))}`);
1712
+ const move = pre.headingMove;
1713
+ if (move === null || move.problems.length > 0) {
1714
+ // The membership/anchor refusals are surfaced by H-HEADING-ORDER
1715
+ // before compile; reaching here with one means the guard was bypassed.
1716
+ throw new Error(`project.move-heading: ${move === null ? "no heading move computed (guard bypassed?)" : move.problems.join("; ")}`);
1717
+ }
1718
+ // The movee set is EXACTLY what the caller named — never derived from the
1719
+ // untouched set, which fences a different (smaller) thing: a bystander whose
1720
+ // POSITION shifts is still a bystander no chord may move.
1721
+ return uiDrive(moveHeadingChordRecipe(pre.destProject?.resolved?.uuid ?? "", move.targetOrder, [
1722
+ ...params.headings,
1723
+ ]));
1693
1724
  },
1694
1725
  };
1695
1726
  const todoClearDatedReminder = {
@@ -1738,11 +1769,41 @@ function uiDrive(recipe) {
1738
1769
  // the validator from commands.ts; the implementation + the full combination
1739
1770
  // matrix live in repeat-rule.ts.
1740
1771
  export { assertRepeatRule };
1772
+ /**
1773
+ * The recipe extras for a MAKE-REPEATING drive: {@link ruleExtras} with the
1774
+ * "Next:" drive date deadline-adjusted, exactly as {@link reschedRuleExtras}
1775
+ * does for a reschedule.
1776
+ *
1777
+ * ONE MEANING FOR `next`, ONE PLACE THAT SHIFTS IT (NEXTPOP1). `next` is the
1778
+ * requested first-occurrence START — what `--when` means to a caller — in every
1779
+ * params bag, at every layer. A DEADLINED rule anchors the dialog on the
1780
+ * DEADLINE (YANCH1 #493), so the date the "Next:" control must carry is
1781
+ * `next + startDaysEarlier`; that conversion belongs to the compile, where the
1782
+ * dialog is, and nowhere else.
1783
+ *
1784
+ * It used to happen upstream too: the promote orchestrators shifted `next`
1785
+ * before handing the bag to this op, so the SAME field meant a START coming from
1786
+ * a caller and a DUE date coming from a composite. Everything downstream that
1787
+ * shifts — `assessOffRuleFirst`, via `assertRepeatRule` in preRead — then shifted
1788
+ * a second time, and a deadlined MONTHLY promote was refused before it ran with
1789
+ * a message about a date the caller never asked for (`--deadline 2026-08-20`
1790
+ * + `--when 2026-08-06` → "a first occurrence on 2026-08-20 would not hold",
1791
+ * measured in-lab). The orchestrators now pass `--when` through unshifted.
1792
+ */
1793
+ function makeRuleExtras(params) {
1794
+ const base = ruleExtras(params);
1795
+ const drive = deadlineDriveNext(params);
1796
+ return drive !== undefined ? { ...base, next: drive } : base;
1797
+ }
1741
1798
  /**
1742
1799
  * The extended-vocabulary fields of a rule as a recipe `extras` bag, including
1743
1800
  * ONLY the keys that are present (exactOptionalPropertyTypes: never set an
1744
1801
  * optional field to undefined). A bare `{ uuid, frequency, interval }` yields
1745
1802
  * `{}`, so the recipe drives exactly the certified two-control path.
1803
+ *
1804
+ * `next` is copied through VERBATIM — a START. Every compile that drives the
1805
+ * dialog wraps this with its own deadline shift ({@link makeRuleExtras},
1806
+ * {@link reschedRuleExtras}); this function never shifts (NEXTPOP1).
1746
1807
  */
1747
1808
  function ruleExtras(params) {
1748
1809
  return {
@@ -1785,15 +1846,31 @@ function reschedEffParams(params) {
1785
1846
  * `--when` is the scheduled START, but a deadlined rule anchors on the DEADLINE,
1786
1847
  * so the dialog's "Next:" field is driven with `when + startDaysEarlier` and the
1787
1848
  * app back-shifts the instance start to `--when`. The expectedDelta keeps the RAW
1788
- * `params.next` (the cursor asserts the START). make/add-repeating apply this shift
1789
- * upstream (promote-clone.ts) and pass an already-adjusted rule, so only the
1790
- * reschedule compile needs it here.
1849
+ * `params.next` (the cursor asserts the START). {@link makeRuleExtras} is the
1850
+ * make/add-repeating twin of this the shift lands in the compile on every verb
1851
+ * that drives the dialog, and nowhere upstream of it (NEXTPOP1).
1791
1852
  */
1792
1853
  function reschedRuleExtras(params) {
1793
1854
  const base = ruleExtras(reschedEffParams(params));
1794
1855
  const drive = deadlineDriveNext(params);
1795
1856
  return drive !== undefined ? { ...base, next: drive } : base;
1796
1857
  }
1858
+ /**
1859
+ * The rule-vocabulary keys this reschedule REQUESTED — the input to the
1860
+ * unexplained-delta check (CGRD1 guard 3): every decoded-rule field that moves
1861
+ * must be attributable to one of these, or to a mapped co-mover of one.
1862
+ *
1863
+ * Built from the EFFECTIVE params, the same bag the recipe drives, so a
1864
+ * `--when`-only reschedule that DERIVES a calendar anchor counts that anchor as
1865
+ * requested rather than as an unexplained move (RSPA1 / YANCH1 #493) — the drive
1866
+ * really does address those pop-ups. `uuid` is the target, not a rule field.
1867
+ */
1868
+ function requestedRuleKeys(params) {
1869
+ const eff = reschedEffParams(params);
1870
+ return Object.entries(eff)
1871
+ .filter(([key, value]) => key !== "uuid" && value !== undefined)
1872
+ .map(([key]) => key);
1873
+ }
1797
1874
  /** ui ops all guard existence/type + the H-UI-DRIVE acknowledgement. */
1798
1875
  const UI_HAZARDS = ["H-UNKNOWN-DESTINATION", "H-UI-DRIVE"];
1799
1876
  const todoMakeRepeating = {
@@ -1841,7 +1918,7 @@ const todoMakeRepeating = {
1841
1918
  compile(params, vector) {
1842
1919
  if (vector !== "ui")
1843
1920
  unsupportedVector(this.op, vector);
1844
- return uiDrive(makeRepeatingRecipe(params.uuid, params.frequency, params.interval, ruleExtras(params)));
1921
+ return uiDrive(makeRepeatingRecipe(params.uuid, params.frequency, params.interval, makeRuleExtras(params)));
1845
1922
  },
1846
1923
  };
1847
1924
  const todoRescheduleRepeat = {
@@ -1879,6 +1956,12 @@ const todoRescheduleRepeat = {
1879
1956
  includeCursor: assessOffRuleFirst(eff)?.kind !== "honored",
1880
1957
  }),
1881
1958
  capture: [{ field: "repeating.rule" }, { field: "repeating.deadlined" }],
1959
+ // UNEXPLAINED-DELTA DETECTION (CGRD1 guard 3). The assertions above prove
1960
+ // the requested rule landed; this proves nothing ELSE did. A reschedule
1961
+ // rewrites an EXISTING rule, so there is a full pre-state to diff against —
1962
+ // which is why make-repeating / add-repeating do not carry it: they mint the
1963
+ // rule, so every field is new by construction and there is nothing to compare.
1964
+ collateral: { requested: requestedRuleKeys(params) },
1882
1965
  };
1883
1966
  },
1884
1967
  compile(params, vector) {
@@ -2018,6 +2101,12 @@ const projectRescheduleRepeat = {
2018
2101
  includeCursor: assessOffRuleFirst(eff)?.kind !== "honored",
2019
2102
  }),
2020
2103
  capture: [{ field: "repeating.rule" }, { field: "repeating.deadlined" }],
2104
+ // UNEXPLAINED-DELTA DETECTION (CGRD1 guard 3). The assertions above prove
2105
+ // the requested rule landed; this proves nothing ELSE did. A reschedule
2106
+ // rewrites an EXISTING rule, so there is a full pre-state to diff against —
2107
+ // which is why make-repeating / add-repeating do not carry it: they mint the
2108
+ // rule, so every field is new by construction and there is nothing to compare.
2109
+ collateral: { requested: requestedRuleKeys(params) },
2021
2110
  };
2022
2111
  },
2023
2112
  compile(params, vector) {
@@ -2127,7 +2216,7 @@ const projectMakeRepeating = {
2127
2216
  // Unreachable in practice: the guards block refuse/anytime before compile.
2128
2217
  throw new Error("project.make-repeating: no selectable-row taxonomy resolved (guard bypassed?)");
2129
2218
  }
2130
- return uiDrive(projectMakeRepeatingRecipe(tax.containerReveal, params.uuid, tax.title, params.frequency, params.interval, ruleExtras(params)));
2219
+ return uiDrive(projectMakeRepeatingRecipe(tax.containerReveal, params.uuid, tax.title, params.frequency, params.interval, makeRuleExtras(params)));
2131
2220
  },
2132
2221
  };
2133
2222
  // project.add-repeating is delivered by the runAddRepeatingProject