things-api 0.16.0 → 0.18.0
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.
- package/README.md +1 -1
- package/deputy/VERSION +1 -0
- package/deputy/helpers-Info.plist +23 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Info.plist +20 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/_CodeSignature/CodeResources +115 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +23 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/_CodeSignature/CodeResources +125 -0
- package/deputy/reader/Info.plist +20 -0
- package/deputy/reader/entitlements.plist +15 -0
- package/deputy/reader/main.swift +491 -0
- package/deputy/src/main.swift +133 -0
- package/deputy/src/osascript.swift +102 -0
- package/deputy/src/server.swift +350 -0
- package/deputy/src/sqlite.swift +121 -0
- package/dist/cli/commands/doctor.js +55 -2
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.d.ts +17 -0
- package/dist/cli/commands/helpers.js +148 -0
- package/dist/cli/commands/helpers.js.map +1 -0
- package/dist/cli/commands/op-result.d.ts +15 -0
- package/dist/cli/commands/op-result.js +39 -0
- package/dist/cli/commands/op-result.js.map +1 -0
- package/dist/cli/commands/repeat-flags.d.ts +3 -3
- package/dist/cli/commands/repeat-flags.js +90 -33
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/writes.js +79 -6
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/help.js +13 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/helpers-check.d.ts +10 -0
- package/dist/cli/helpers-check.js +59 -0
- package/dist/cli/helpers-check.js.map +1 -0
- package/dist/cli/interrupt.d.ts +42 -0
- package/dist/cli/interrupt.js +105 -0
- package/dist/cli/interrupt.js.map +1 -0
- package/dist/cli/main.js +27 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/client.js +19 -2
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +70 -0
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +9 -2
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/db/baselines/db-v26.js +8 -4
- package/dist/db/baselines/db-v26.js.map +1 -1
- package/dist/db/baselines/db-v27.d.ts +42 -0
- package/dist/db/baselines/db-v27.js +47 -0
- package/dist/db/baselines/db-v27.js.map +1 -0
- package/dist/db/baselines/index.js +2 -1
- package/dist/db/baselines/index.js.map +1 -1
- package/dist/db/locate.d.ts +4 -1
- package/dist/db/locate.js +3 -1
- package/dist/db/locate.js.map +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/db/schema.js +9 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/deputy/bridge-worker.d.ts +1 -0
- package/dist/deputy/bridge-worker.js +79 -0
- package/dist/deputy/bridge-worker.js.map +1 -0
- package/dist/deputy/bridge.d.ts +14 -0
- package/dist/deputy/bridge.js +70 -0
- package/dist/deputy/bridge.js.map +1 -0
- package/dist/deputy/client.d.ts +19 -0
- package/dist/deputy/client.js +104 -0
- package/dist/deputy/client.js.map +1 -0
- package/dist/deputy/db-facade.d.ts +18 -0
- package/dist/deputy/db-facade.js +71 -0
- package/dist/deputy/db-facade.js.map +1 -0
- package/dist/deputy/files.d.ts +1 -0
- package/dist/deputy/files.js +16 -0
- package/dist/deputy/files.js.map +1 -0
- package/dist/deputy/install.d.ts +105 -0
- package/dist/deputy/install.js +467 -0
- package/dist/deputy/install.js.map +1 -0
- package/dist/deputy/notice.d.ts +6 -0
- package/dist/deputy/notice.js +28 -0
- package/dist/deputy/notice.js.map +1 -0
- package/dist/deputy/notices.d.ts +31 -0
- package/dist/deputy/notices.js +94 -0
- package/dist/deputy/notices.js.map +1 -0
- package/dist/deputy/osa.d.ts +21 -0
- package/dist/deputy/osa.js +72 -0
- package/dist/deputy/osa.js.map +1 -0
- package/dist/deputy/protocol.d.ts +86 -0
- package/dist/deputy/protocol.js +106 -0
- package/dist/deputy/protocol.js.map +1 -0
- package/dist/deputy/routing.d.ts +63 -0
- package/dist/deputy/routing.js +338 -0
- package/dist/deputy/routing.js.map +1 -0
- package/dist/deputy/shortcuts-exec.d.ts +10 -0
- package/dist/deputy/shortcuts-exec.js +50 -0
- package/dist/deputy/shortcuts-exec.js.map +1 -0
- package/dist/diagnose.d.ts +43 -1
- package/dist/diagnose.js +64 -2
- package/dist/diagnose.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +25 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/mappers.d.ts +12 -3
- package/dist/model/mappers.js +6 -1
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/occurrences.d.ts +14 -1
- package/dist/model/occurrences.js +20 -4
- package/dist/model/occurrences.js.map +1 -1
- package/dist/model/recurrence.d.ts +64 -9
- package/dist/model/recurrence.js +94 -19
- package/dist/model/recurrence.js.map +1 -1
- package/dist/model/template-projection.d.ts +26 -0
- package/dist/model/template-projection.js +113 -0
- package/dist/model/template-projection.js.map +1 -0
- package/dist/op-result.d.ts +46 -0
- package/dist/op-result.js +156 -0
- package/dist/op-result.js.map +1 -0
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +10 -0
- package/dist/paths.js.map +1 -1
- package/dist/read/detail.js +7 -1
- package/dist/read/detail.js.map +1 -1
- package/dist/read/queries.js +8 -1
- package/dist/read/queries.js.map +1 -1
- package/dist/read/views.d.ts +6 -4
- package/dist/read/views.js +60 -18
- package/dist/read/views.js.map +1 -1
- package/dist/sync-health.js +8 -3
- package/dist/sync-health.js.map +1 -1
- package/dist/trace/tracer.d.ts +87 -0
- package/dist/trace/tracer.js +167 -0
- package/dist/trace/tracer.js.map +1 -0
- package/dist/write/accessibility-probe.js +4 -2
- package/dist/write/accessibility-probe.js.map +1 -1
- package/dist/write/automation-probe.js +5 -1
- package/dist/write/automation-probe.js.map +1 -1
- package/dist/write/availability.js +7 -3
- package/dist/write/availability.js.map +1 -1
- package/dist/write/clear-reminder.js +24 -21
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/commands.js +95 -45
- package/dist/write/commands.js.map +1 -1
- package/dist/write/completion-context.d.ts +57 -0
- package/dist/write/completion-context.js +49 -0
- package/dist/write/completion-context.js.map +1 -0
- package/dist/write/experimental.d.ts +19 -0
- package/dist/write/experimental.js +50 -0
- package/dist/write/experimental.js.map +1 -1
- package/dist/write/failure-hints.d.ts +10 -1
- package/dist/write/failure-hints.js +8 -0
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.d.ts +1 -1
- package/dist/write/guards.js +10 -0
- package/dist/write/guards.js.map +1 -1
- package/dist/write/move.js +14 -6
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +40 -7
- package/dist/write/pipeline.d.ts +41 -0
- package/dist/write/pipeline.js +289 -19
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +1 -1
- package/dist/write/pre-state.js +35 -17
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +434 -19
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +76 -27
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-anchor.d.ts +140 -0
- package/dist/write/repeat-anchor.js +403 -0
- package/dist/write/repeat-anchor.js.map +1 -0
- package/dist/write/repeat-asserts.d.ts +28 -0
- package/dist/write/repeat-asserts.js +169 -0
- package/dist/write/repeat-asserts.js.map +1 -0
- package/dist/write/repeat-rule.js +37 -12
- package/dist/write/repeat-rule.js.map +1 -1
- package/dist/write/vectors/applescript.js +7 -16
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/session-reachability.d.ts +133 -0
- package/dist/write/vectors/session-reachability.js +151 -0
- package/dist/write/vectors/session-reachability.js.map +1 -0
- package/dist/write/vectors/shortcuts.js +5 -14
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.d.ts +16 -1
- package/dist/write/vectors/simulator.js +122 -32
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +183 -10
- package/dist/write/vectors/ui-recipes.d.ts +8 -0
- package/dist/write/vectors/ui-recipes.js +246 -58
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +156 -14
- package/dist/write/vectors/ui.js +846 -84
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/verify/delta.d.ts +12 -14
- package/dist/write/verify/delta.js +35 -34
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +13 -0
- package/dist/write/verify/poller.js +38 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +8 -1
- package/schema/envelope.schema.json +22 -0
- package/scripts/build-helpers.sh +104 -0
- package/scripts/deputy-cert-setup.sh +60 -0
- package/skills/things-cli/SKILL.md +11 -3
- package/skills/things-cli/references/errors.md +4 -0
- package/skills/things-cli/references/repeating.md +69 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FIXED-recurrence DEFAULT-anchor law + the weekly-weekday derivation the
|
|
3
|
+
* promote verbs use (issue #476; evidence: docs/lab/anch2-next-field.md +
|
|
4
|
+
* anch1-repeat-anchor.md, golden-v2 / Things 3.22.12).
|
|
5
|
+
*
|
|
6
|
+
* DEFAULT LAW (ANCH1, still valid as the DEFAULT). Left untouched, the Repeat
|
|
7
|
+
* dialog's "Next:" field defaults the first occurrence to the next calendar match
|
|
8
|
+
* ON OR AFTER TODAY (interval-independent), and a weekday-less weekly rule
|
|
9
|
+
* defaults to SUNDAY. ANCH2 then showed the "Next:" field is EDITABLE and honored
|
|
10
|
+
* (docs/lab/anch2-next-field.md) — so the promote verbs DRIVE it with the item's
|
|
11
|
+
* scheduled date rather than refuse a wrong-phase series. This module no longer
|
|
12
|
+
* carries a phase-refusal (the ANCH1 `H-REPEAT-ANCHOR` premise — "no drivable
|
|
13
|
+
* first-occurrence control" — was false).
|
|
14
|
+
*
|
|
15
|
+
* What it still provides:
|
|
16
|
+
* 1. {@link deriveWeeklyWeekdays} — when a weekly rule omits `weekdays` but a
|
|
17
|
+
* concrete anchor date is known, derive the recurring weekday FROM that date
|
|
18
|
+
* so the series fires on the intended weekday (not the app's Sunday default).
|
|
19
|
+
* Driving "Next:" fixes the FIRST occurrence; this fixes the recurring day.
|
|
20
|
+
* 2. {@link fixedSpawnPlan} — the DEFAULT spawn shape (used by the simulator
|
|
21
|
+
* when no explicit first occurrence is requested): first occurrence = next
|
|
22
|
+
* match ≥ today, an instance spawns only when today is itself an occurrence.
|
|
23
|
+
*
|
|
24
|
+
* Daily/weekly follow the probed default law; monthly/yearly keep the caller's
|
|
25
|
+
* today+interval model for the default (their default-anchor law is unprobed).
|
|
26
|
+
*/
|
|
27
|
+
import { addDaysIso } from "../model/dates.js";
|
|
28
|
+
import { WD_TO_WEEKDAY, WEEKDAY_TO_WD } from "./repeat-rule.js";
|
|
29
|
+
/** True iff `v` is a concrete `YYYY-MM-DD` date (not a list keyword / undefined). */
|
|
30
|
+
export function isIsoDate(v) {
|
|
31
|
+
return typeof v === "string" && /^\d{4}-\d{2}-\d{2}$/.test(v);
|
|
32
|
+
}
|
|
33
|
+
/** Weekday of an ISO date, 0 = Sunday … 6 = Saturday (evaluated at UTC noon, tz-invariant). */
|
|
34
|
+
export function weekdayOfIso(iso) {
|
|
35
|
+
const [y, m, d] = iso.split("-").map(Number);
|
|
36
|
+
return new Date(Date.UTC(y, m - 1, d, 12)).getUTCDay();
|
|
37
|
+
}
|
|
38
|
+
/** Whole days from `a` to `b` (b − a); negative when b precedes a. */
|
|
39
|
+
export function daysBetweenIso(a, b) {
|
|
40
|
+
const [ay, am, ad] = a.split("-").map(Number);
|
|
41
|
+
const [by, bm, bd] = b.split("-").map(Number);
|
|
42
|
+
return Math.round((Date.UTC(by, bm - 1, bd) - Date.UTC(ay, am - 1, ad)) / 86_400_000);
|
|
43
|
+
}
|
|
44
|
+
/** The smallest date on or after `todayIso` whose weekday is `wd` (0 = Sunday). */
|
|
45
|
+
function nextWeekdayOnOrAfter(todayIso, wd) {
|
|
46
|
+
const delta = (((wd - weekdayOfIso(todayIso)) % 7) + 7) % 7;
|
|
47
|
+
return addDaysIso(todayIso, delta);
|
|
48
|
+
}
|
|
49
|
+
/** The Sunday that starts the week containing `iso` (the app's weeks start Sunday). */
|
|
50
|
+
function weekAnchorSunday(iso) {
|
|
51
|
+
return addDaysIso(iso, -weekdayOfIso(iso));
|
|
52
|
+
}
|
|
53
|
+
/** A true modulo (result always in [0, n)). */
|
|
54
|
+
function mod(x, n) {
|
|
55
|
+
return ((x % n) + n) % n;
|
|
56
|
+
}
|
|
57
|
+
/** The rule's effective weekday numbers (explicit set, else the app's Sunday default). */
|
|
58
|
+
function effectiveWds(rule) {
|
|
59
|
+
return rule.weekdays !== undefined && rule.weekdays.length > 0
|
|
60
|
+
? rule.weekdays.map((w) => WEEKDAY_TO_WD[w])
|
|
61
|
+
: [0];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Whether `d` is on the active-week grid of a weekly rule anchored at `firstOcc`
|
|
65
|
+
* (weeks fire every `interval` weeks from the anchor's Sunday-week). Weekday match
|
|
66
|
+
* is checked separately.
|
|
67
|
+
*/
|
|
68
|
+
function weeklyActiveWeek(firstOcc, d, interval) {
|
|
69
|
+
const weeks = daysBetweenIso(weekAnchorSunday(firstOcc), weekAnchorSunday(d)) / 7;
|
|
70
|
+
return mod(weeks, interval) === 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* When a WEEKLY rule omits its weekday set but a concrete anchor date is known,
|
|
74
|
+
* the weekday to drive explicitly (so the series fires on the anchor date's
|
|
75
|
+
* weekday, not the app's Sunday default). Returns `undefined` when nothing should
|
|
76
|
+
* change (non-weekly, weekdays already given, or no concrete anchor date).
|
|
77
|
+
*/
|
|
78
|
+
export function deriveWeeklyWeekdays(rule, anchorIso) {
|
|
79
|
+
if (rule.afterCompletion === true)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (rule.frequency !== "weekly")
|
|
82
|
+
return undefined;
|
|
83
|
+
if (rule.weekdays !== undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (!isIsoDate(anchorIso))
|
|
86
|
+
return undefined;
|
|
87
|
+
return [WD_TO_WEEKDAY[weekdayOfIso(anchorIso)]];
|
|
88
|
+
}
|
|
89
|
+
/** The 1-based day-of-month of an ISO date. */
|
|
90
|
+
function dayOfMonthIso(iso) {
|
|
91
|
+
return Number(iso.split("-")[2]);
|
|
92
|
+
}
|
|
93
|
+
/** The 1-based month of an ISO date. */
|
|
94
|
+
function monthOfIso(iso) {
|
|
95
|
+
return Number(iso.split("-")[1]);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* When a MONTHLY rule omits its day anchor but a concrete first-occurrence date
|
|
99
|
+
* is known, the day-of-month anchor to drive EXPLICITLY (so the series recurs on
|
|
100
|
+
* that day, not the Repeat dialog's untouched default). Returns `undefined` when
|
|
101
|
+
* nothing should change (non-monthly, an explicit anchor already given, or no
|
|
102
|
+
* concrete date). Mirrors {@link deriveWeeklyWeekdays} for the monthly unit —
|
|
103
|
+
* the same day-of-month the read-path decoder / composeOffsets derives from the
|
|
104
|
+
* occurrence date, so the derived anchor is byte-consistent with the rule blob.
|
|
105
|
+
*/
|
|
106
|
+
export function deriveMonthlyAnchor(rule, anchorIso) {
|
|
107
|
+
if (rule.afterCompletion === true)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (rule.frequency !== "monthly")
|
|
110
|
+
return undefined;
|
|
111
|
+
if (rule.monthly !== undefined)
|
|
112
|
+
return undefined;
|
|
113
|
+
if (!isIsoDate(anchorIso))
|
|
114
|
+
return undefined;
|
|
115
|
+
return { day: dayOfMonthIso(anchorIso) };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* When a YEARLY rule omits its month+day anchor but a concrete first-occurrence
|
|
119
|
+
* date is known, the {month, day} anchor to drive EXPLICITLY (so the series recurs
|
|
120
|
+
* on that month/day, not the dialog's untouched January-1 default — the ANCH2-c /
|
|
121
|
+
* issue #493 anchor-drop). Returns `undefined` when nothing should change
|
|
122
|
+
* (non-yearly, an explicit anchor already given, or no concrete date).
|
|
123
|
+
*/
|
|
124
|
+
export function deriveYearlyAnchor(rule, anchorIso) {
|
|
125
|
+
if (rule.afterCompletion === true)
|
|
126
|
+
return undefined;
|
|
127
|
+
if (rule.frequency !== "yearly")
|
|
128
|
+
return undefined;
|
|
129
|
+
if (rule.yearly !== undefined)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (!isIsoDate(anchorIso))
|
|
132
|
+
return undefined;
|
|
133
|
+
return { month: monthOfIso(anchorIso), day: dayOfMonthIso(anchorIso) };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The date to drive into the Repeat dialog's "Next:" field for a rule whose
|
|
137
|
+
* `next` is the scheduled START (`--when`). A DEADLINED rule anchors on the
|
|
138
|
+
* DEADLINE — the dialog's "Next:" is the next deadline and the instance START =
|
|
139
|
+
* deadline − startDaysEarlier — so it is driven with `next + startDaysEarlier` and
|
|
140
|
+
* the app back-shifts the start to `next` (YANCH1 #493, golden-v3 probe: Next
|
|
141
|
+
* driven to a date with start-N lands the instance start N days earlier). A
|
|
142
|
+
* non-deadlined rule (or one with no concrete `next`) drives `next` verbatim.
|
|
143
|
+
* Returns `undefined` when `next` is absent/non-concrete.
|
|
144
|
+
*/
|
|
145
|
+
export function deadlineDriveNext(p) {
|
|
146
|
+
if (!isIsoDate(p.next))
|
|
147
|
+
return undefined;
|
|
148
|
+
const shift = p.deadline === true || (p.startDaysEarlier ?? 0) > 0 ? (p.startDaysEarlier ?? 0) : 0;
|
|
149
|
+
return addDaysIso(p.next, shift);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The calendar-anchor fields to drive EXPLICITLY for a fixed weekly/monthly/yearly
|
|
153
|
+
* rule when the caller supplied a concrete first-occurrence date but NO explicit
|
|
154
|
+
* anchor — each derived from that date by the SAME refIso law composeOffsets uses
|
|
155
|
+
* (weekly → weekday, monthly → day-of-month, yearly → month+day). Driving the
|
|
156
|
+
* anchor pop-ups makes the series RECUR on the intended calendar placement;
|
|
157
|
+
* leaving the anchor to the "Next:" first-occurrence field alone fixes only the
|
|
158
|
+
* FIRST occurrence and leaves the dialog's untouched default as the recurring rule
|
|
159
|
+
* (weekly → Sunday, monthly → 1st, yearly → January 1 — ANCH2 cell c / issue
|
|
160
|
+
* #493). Returns only the fields that need driving; an explicit anchor, an
|
|
161
|
+
* after-completion rule, daily, or no concrete date yields an empty patch. This
|
|
162
|
+
* completes the derive-and-drive family the weekly weekday-derivation began
|
|
163
|
+
* post-ANCH1 (decisions.md).
|
|
164
|
+
*/
|
|
165
|
+
export function deriveFixedAnchor(rule, anchorIso) {
|
|
166
|
+
const weekdays = deriveWeeklyWeekdays(rule, anchorIso);
|
|
167
|
+
const monthly = deriveMonthlyAnchor(rule, anchorIso);
|
|
168
|
+
const yearly = deriveYearlyAnchor(rule, anchorIso);
|
|
169
|
+
return {
|
|
170
|
+
...(weekdays !== undefined && { weekdays }),
|
|
171
|
+
...(monthly !== undefined && { monthly }),
|
|
172
|
+
...(yearly !== undefined && { yearly }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ------------------------------------------------ off-rule first occurrence (DACON1)
|
|
176
|
+
//
|
|
177
|
+
// A concrete `--when` together with an EXPLICIT calendar anchor
|
|
178
|
+
// (weekdays/monthly/yearly) can DISAGREE — the first occurrence lands off the
|
|
179
|
+
// rule's grid. This is NOT an error by default: the Repeat dialog's "Next:" field
|
|
180
|
+
// accepts an off-schedule first occurrence, so the series appears on `--when` the
|
|
181
|
+
// first time and follows the anchor thereafter (e.g. `--weekdays wednesday --when
|
|
182
|
+
// <a thursday>` = Thursday first, Wednesdays after). In DEADLINE mode `--when` is
|
|
183
|
+
// the START and each occurrence is DUE `startDaysEarlier` days later; the anchor
|
|
184
|
+
// names the DUE date, so the off-rule test compares the anchor against
|
|
185
|
+
// `deadlineDriveNext` (= when + N, the deadline). `deriveFixedAnchor` covers the
|
|
186
|
+
// NO-explicit-anchor case (anchor derived from `--when`, never off-rule).
|
|
187
|
+
//
|
|
188
|
+
// EMPIRICAL boundary (docs/lab/dacon1-deadline-contradiction.md, golden-v3 /
|
|
189
|
+
// Things 3.22.14). The app HONORS an off-rule first for WEEKLY and YEARLY (cells
|
|
190
|
+
// DC1/DC5, DC3/DC4: the typed `--when` lands verbatim as the first instance start,
|
|
191
|
+
// the anchor drives the recurring grid). It does NOT for MONTHLY: the month row's
|
|
192
|
+
// "Next:" field SNAPS to the anchor day (cell DC2: Next 08-10 with a day-20 anchor
|
|
193
|
+
// committed 08-20, then the CLI's read-back rejected it, -2700). So an off-rule
|
|
194
|
+
// first is EXPRESSIBLE for weekly/yearly (allowed + disclosed) and INEXPRESSIBLE
|
|
195
|
+
// for monthly (fail-closed at validation rather than mid-drive).
|
|
196
|
+
/** Days in the calendar month containing `iso`. */
|
|
197
|
+
function daysInMonthOf(iso) {
|
|
198
|
+
const [y, m] = iso.split("-").map(Number);
|
|
199
|
+
return new Date(Date.UTC(y, m, 0)).getUTCDate(); // day 0 of month m+1 = last day of m
|
|
200
|
+
}
|
|
201
|
+
/** True iff `iso` is the last calendar day of its month. */
|
|
202
|
+
function isLastDayOfMonth(iso) {
|
|
203
|
+
return dayOfMonthIso(iso) === daysInMonthOf(iso);
|
|
204
|
+
}
|
|
205
|
+
/** Which occurrence (1-based) of its own weekday `iso` is within its month. */
|
|
206
|
+
function weekdayOrdinalInMonth(iso) {
|
|
207
|
+
return Math.floor((dayOfMonthIso(iso) - 1) / 7) + 1;
|
|
208
|
+
}
|
|
209
|
+
/** True iff `iso` is the LAST occurrence of its weekday in its month. */
|
|
210
|
+
function isLastWeekdayInMonth(iso) {
|
|
211
|
+
return dayOfMonthIso(iso) + 7 > daysInMonthOf(iso);
|
|
212
|
+
}
|
|
213
|
+
/** Whether date `iso` lands on the monthly/yearly day-or-nth-weekday `anchor`. */
|
|
214
|
+
function dateSatisfiesDayAnchor(iso, anchor) {
|
|
215
|
+
if ("day" in anchor) {
|
|
216
|
+
return anchor.day === "last" ? isLastDayOfMonth(iso) : dayOfMonthIso(iso) === anchor.day;
|
|
217
|
+
}
|
|
218
|
+
if (WEEKDAY_TO_WD[anchor.weekday] !== weekdayOfIso(iso))
|
|
219
|
+
return false;
|
|
220
|
+
return anchor.ordinal === "last"
|
|
221
|
+
? isLastWeekdayInMonth(iso)
|
|
222
|
+
: weekdayOrdinalInMonth(iso) === anchor.ordinal;
|
|
223
|
+
}
|
|
224
|
+
/** "1st" | "2nd" | … | "5th" | "last" for an nth-weekday ordinal. */
|
|
225
|
+
function ordinalWord(ordinal) {
|
|
226
|
+
if (ordinal === "last")
|
|
227
|
+
return "last";
|
|
228
|
+
return `${ordinal}${["th", "st", "nd", "rd", "th"][ordinal] ?? "th"}`;
|
|
229
|
+
}
|
|
230
|
+
/** Human phrase for a monthly/yearly day-or-nth-weekday anchor (surface copy). */
|
|
231
|
+
function describeDayAnchor(anchor) {
|
|
232
|
+
if ("day" in anchor)
|
|
233
|
+
return anchor.day === "last" ? "the last day of the month" : `day ${anchor.day}`;
|
|
234
|
+
return `the ${ordinalWord(anchor.ordinal)} ${anchor.weekday}`;
|
|
235
|
+
}
|
|
236
|
+
/** The effective deadline back-shift (days) for the given params, mirroring {@link deadlineDriveNext}. */
|
|
237
|
+
function anchorShiftDays(p) {
|
|
238
|
+
return p.deadline === true || (p.startDaysEarlier ?? 0) > 0 ? (p.startDaysEarlier ?? 0) : 0;
|
|
239
|
+
}
|
|
240
|
+
/** Whether `driveIso` lands on the explicit anchor of these params (deadline-shift date already applied). */
|
|
241
|
+
function driveDateOnAnchor(p, driveIso) {
|
|
242
|
+
if (p.frequency === "weekly" && p.weekdays !== undefined && p.weekdays.length > 0) {
|
|
243
|
+
return p.weekdays.includes(WD_TO_WEEKDAY[weekdayOfIso(driveIso)]);
|
|
244
|
+
}
|
|
245
|
+
if (p.frequency === "monthly" && p.monthly !== undefined) {
|
|
246
|
+
return dateSatisfiesDayAnchor(driveIso, p.monthly);
|
|
247
|
+
}
|
|
248
|
+
if (p.frequency === "yearly" && p.yearly !== undefined) {
|
|
249
|
+
return monthOfIso(driveIso) === p.yearly.month && dateSatisfiesDayAnchor(driveIso, p.yearly);
|
|
250
|
+
}
|
|
251
|
+
return true; // no explicit anchor for this frequency ⇒ never off-rule (derived from --when)
|
|
252
|
+
}
|
|
253
|
+
/** The recurring-pattern phrase for the explicit anchor (the "thereafter" half of the disclosure). */
|
|
254
|
+
function describeRulePattern(p) {
|
|
255
|
+
if (p.frequency === "weekly" && p.weekdays !== undefined) {
|
|
256
|
+
return `every ${p.weekdays.join(", ")}`;
|
|
257
|
+
}
|
|
258
|
+
if (p.frequency === "monthly" && p.monthly !== undefined) {
|
|
259
|
+
return `monthly on ${describeDayAnchor(p.monthly)}`;
|
|
260
|
+
}
|
|
261
|
+
if (p.frequency === "yearly" && p.yearly !== undefined) {
|
|
262
|
+
return `yearly in month ${p.yearly.month} on ${describeDayAnchor(p.yearly)}`;
|
|
263
|
+
}
|
|
264
|
+
return "the rule";
|
|
265
|
+
}
|
|
266
|
+
export function assessOffRuleFirst(p, preservedDeadlineOffset) {
|
|
267
|
+
if (p.afterCompletion === true)
|
|
268
|
+
return null;
|
|
269
|
+
if (!isIsoDate(p.next))
|
|
270
|
+
return null;
|
|
271
|
+
const driveIso = deadlineDriveNext(p);
|
|
272
|
+
if (driveIso === undefined)
|
|
273
|
+
return null;
|
|
274
|
+
if (driveDateOnAnchor(p, driveIso))
|
|
275
|
+
return null; // on-rule — the common case
|
|
276
|
+
const shift = anchorShiftDays(p);
|
|
277
|
+
// RSPA1-D: a reschedule that PRESERVES the template's existing deadline (the
|
|
278
|
+
// request carries no --deadline / --start-days-earlier) drives "Next:" verbatim
|
|
279
|
+
// (shift 0), but the app reads that driven date as the DUE date of the still-
|
|
280
|
+
// deadlined rule and back-shifts the START by the preserved offset. So the first
|
|
281
|
+
// occurrence APPEARS `preserved` days BEFORE --when and is DUE on --when — the
|
|
282
|
+
// inverse of the explicit-deadline case (where --when is the start and the due is
|
|
283
|
+
// `shift` days later). `assessOffRuleFirst` is otherwise deadline-shift-aware only
|
|
284
|
+
// via the params' own flags; the caller threads the pre-state template's offset so
|
|
285
|
+
// the disclosure states the true appear/due dates the app actually lands.
|
|
286
|
+
const preservedShift = shift === 0 && preservedDeadlineOffset != null ? Math.abs(preservedDeadlineOffset) : 0;
|
|
287
|
+
const pattern = describeRulePattern(p);
|
|
288
|
+
// MONTHLY off-rule first is INEXPRESSIBLE — the month row's "Next:" field snaps
|
|
289
|
+
// to the anchor day (dacon1-deadline-contradiction.md cell DC2). Fail closed at
|
|
290
|
+
// validation with the two nearest expressible alternatives. (When the deadline is
|
|
291
|
+
// preserved, --when IS the due date the anchor names, so the shift-0 hint — "set
|
|
292
|
+
// --when to a date on <anchor>" — is the correct guidance, exactly as here.)
|
|
293
|
+
if (p.frequency === "monthly") {
|
|
294
|
+
const onRuleHint = shift > 0
|
|
295
|
+
? `set --when so that --when + ${shift} lands on ${describeDayAnchor(p.monthly)}`
|
|
296
|
+
: `set --when to a date on ${describeDayAnchor(p.monthly)}`;
|
|
297
|
+
return {
|
|
298
|
+
kind: "dishonored",
|
|
299
|
+
refusal: `a monthly rule cannot start off its anchor: the Repeat dialog snaps the first ` +
|
|
300
|
+
`occurrence to ${describeDayAnchor(p.monthly)}, so a first occurrence on ` +
|
|
301
|
+
`${p.next} would not hold. Either ${onRuleHint}, or omit the monthly anchor to take it ` +
|
|
302
|
+
`from --when.`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
// WEEKLY / YEARLY off-rule first is HONORED — allow + disclose both halves. The
|
|
306
|
+
// appear/due assignment depends on which deadline mode applies: an explicit
|
|
307
|
+
// deadline makes --when the START (due `shift` days later); a preserved deadline
|
|
308
|
+
// makes --when the DUE date (start `preservedShift` days earlier).
|
|
309
|
+
let appearsIso;
|
|
310
|
+
let dueIso;
|
|
311
|
+
let earlierDays;
|
|
312
|
+
if (shift > 0) {
|
|
313
|
+
appearsIso = p.next;
|
|
314
|
+
dueIso = addDaysIso(p.next, shift);
|
|
315
|
+
earlierDays = shift;
|
|
316
|
+
}
|
|
317
|
+
else if (preservedShift > 0) {
|
|
318
|
+
dueIso = p.next;
|
|
319
|
+
appearsIso = addDaysIso(p.next, -preservedShift);
|
|
320
|
+
earlierDays = preservedShift;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
appearsIso = p.next;
|
|
324
|
+
dueIso = null;
|
|
325
|
+
earlierDays = 0;
|
|
326
|
+
}
|
|
327
|
+
const dueClause = dueIso !== null ? `, due ${dueIso}` : "";
|
|
328
|
+
const ongoing = earlierDays > 0
|
|
329
|
+
? `thereafter: ${pattern}, appearing ${earlierDays} day${earlierDays === 1 ? "" : "s"} earlier`
|
|
330
|
+
: `thereafter: ${pattern}`;
|
|
331
|
+
return {
|
|
332
|
+
kind: "honored",
|
|
333
|
+
disclosure: {
|
|
334
|
+
appearsIso,
|
|
335
|
+
dueIso,
|
|
336
|
+
message: `off-rule first occurrence — appears ${appearsIso}${dueClause}; ${ongoing}`,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The app's first occurrence of a FIXED daily/weekly rule = the next calendar
|
|
342
|
+
* match on or after today. `null` for after-completion (no calendar) or an
|
|
343
|
+
* unguarded frequency (monthly/yearly). Weekly uses `weekdays` (caller should
|
|
344
|
+
* pass the DERIVED set); with no weekdays it falls back to the app's Sunday default.
|
|
345
|
+
*/
|
|
346
|
+
export function fixedFirstOccurrence(rule, todayIso) {
|
|
347
|
+
if (rule.afterCompletion === true)
|
|
348
|
+
return null;
|
|
349
|
+
if (rule.frequency === "daily")
|
|
350
|
+
return todayIso; // every day is an occurrence
|
|
351
|
+
if (rule.frequency === "weekly") {
|
|
352
|
+
let best = null;
|
|
353
|
+
for (const wd of effectiveWds(rule)) {
|
|
354
|
+
const cand = nextWeekdayOnOrAfter(todayIso, wd);
|
|
355
|
+
if (best === null || daysBetweenIso(cand, best) > 0)
|
|
356
|
+
best = cand;
|
|
357
|
+
}
|
|
358
|
+
return best;
|
|
359
|
+
}
|
|
360
|
+
return null; // monthly/yearly: default anchor law unprobed — not guarded
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The smallest occurrence STRICTLY AFTER today (the template's cursor,
|
|
364
|
+
* rt1_nextInstanceStartDate). Daily/weekly only. Returns `null` for other
|
|
365
|
+
* frequencies / after-completion.
|
|
366
|
+
*/
|
|
367
|
+
export function nextFixedOccurrenceAfter(rule, todayIso) {
|
|
368
|
+
if (rule.afterCompletion === true)
|
|
369
|
+
return null;
|
|
370
|
+
if (rule.frequency === "daily")
|
|
371
|
+
return addDaysIso(todayIso, Math.max(1, rule.interval));
|
|
372
|
+
if (rule.frequency === "weekly") {
|
|
373
|
+
const first = fixedFirstOccurrence(rule, todayIso);
|
|
374
|
+
if (first === null)
|
|
375
|
+
return null;
|
|
376
|
+
const wds = effectiveWds(rule);
|
|
377
|
+
const interval = Math.max(1, rule.interval);
|
|
378
|
+
let d = addDaysIso(todayIso, 1);
|
|
379
|
+
for (let i = 0; i < (interval + 2) * 7; i++) {
|
|
380
|
+
if (wds.includes(weekdayOfIso(d)) && weeklyActiveWeek(first, d, interval))
|
|
381
|
+
return d;
|
|
382
|
+
d = addDaysIso(d, 1);
|
|
383
|
+
}
|
|
384
|
+
return first; // unreachable in practice
|
|
385
|
+
}
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
export function fixedSpawnPlan(rule, todayIso) {
|
|
389
|
+
const first = fixedFirstOccurrence(rule, todayIso);
|
|
390
|
+
if (first === null) {
|
|
391
|
+
// Not a guarded frequency — degenerate plan (caller handles monthly/yearly).
|
|
392
|
+
return { refIso: todayIso, instanceStartIso: todayIso, cursorIso: todayIso, instanceCount: 1 };
|
|
393
|
+
}
|
|
394
|
+
const isToday = first === todayIso;
|
|
395
|
+
const cursor = isToday ? (nextFixedOccurrenceAfter(rule, todayIso) ?? first) : first;
|
|
396
|
+
return {
|
|
397
|
+
refIso: first,
|
|
398
|
+
instanceStartIso: isToday ? todayIso : null,
|
|
399
|
+
cursorIso: cursor,
|
|
400
|
+
instanceCount: isToday ? 1 : 0,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=repeat-anchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repeat-anchor.js","sourceRoot":"","sources":["../../src/write/repeat-anchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RepeatRuleParams } from "./operations.ts";
|
|
2
|
+
import type { FieldAssertion } from "./verify/delta.ts";
|
|
3
|
+
/** The full requested-rule vocabulary minus the target uuid. */
|
|
4
|
+
export type RuleFields = Omit<RepeatRuleParams, "uuid">;
|
|
5
|
+
export interface RuleAssertOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Include the ANCH2 first-occurrence cursor assertion (`repeating.nextOccurrence`
|
|
8
|
+
* = rt1_nextInstanceStartDate) when `next` is requested. TRUE for
|
|
9
|
+
* reschedule-repeat — the cursor is driven into the EXISTING template in place
|
|
10
|
+
* with an on-rule `--when`, so a wrong-anchor drive is caught. FALSE for
|
|
11
|
+
* make-repeating / add-repeating: the cursor is the first RULE-ALIGNED occurrence
|
|
12
|
+
* ≥ today (ANCH1 spawn law), which coincides with the requested `--when` only
|
|
13
|
+
* when that date is on-rule AND future — an overdue seed or an off-rule `--when`
|
|
14
|
+
* makes the cursor legitimately differ. Those verbs instead verify (a) the rule
|
|
15
|
+
* BLOB anchor at full fidelity — the calendar anchor is asserted whether it was
|
|
16
|
+
* given explicitly or DERIVED from `--when` (YANCH1 #493: this is what catches a
|
|
17
|
+
* dropped January-1 / Sunday / 1st-of-month anchor), and (b) the driven FIRST
|
|
18
|
+
* occurrence (rt1_instanceCreationStartDate, verbatim even off-rule) through the
|
|
19
|
+
* dedicated post-drive check in promote-clone.ts — not the cursor.
|
|
20
|
+
*/
|
|
21
|
+
includeCursor: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the full-fidelity expected-rule assertion set for a validated rule-params
|
|
25
|
+
* bag. The base three (type/unit/interval) are always emitted; every other field
|
|
26
|
+
* contributes only when requested (requested-fields-only law).
|
|
27
|
+
*/
|
|
28
|
+
export declare function expectedRuleAssertions(params: RuleFields, opts: RuleAssertOptions): FieldAssertion[];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FULL-FIDELITY expected-rule assertions for the rule-writing verbs
|
|
3
|
+
* (reschedule-repeat, make-repeating, add-repeating; to-do AND project).
|
|
4
|
+
*
|
|
5
|
+
* The idempotency precheck (pipeline.ts step 5a½) and the post-drive verify both
|
|
6
|
+
* ride the SAME {@link FieldAssertion} set an op's `expectedDelta` produces. When
|
|
7
|
+
* that set asserted only unit+interval (the RSIM5-era shallow subset), a
|
|
8
|
+
* reschedule that changed ONLY the monthly anchor / deadline offset / ends bound
|
|
9
|
+
* read back "already satisfied" and was skipped with a false idempotent-no-op —
|
|
10
|
+
* issue #491. This builder closes that class by construction: it maps the COMPLETE
|
|
11
|
+
* requested vocabulary onto assertions over the DECODED rule (decodeRecurrenceRule
|
|
12
|
+
* shapes) + the template's deadline column, never a raw blob/string compare.
|
|
13
|
+
*
|
|
14
|
+
* Two laws:
|
|
15
|
+
* 1. REQUESTED-FIELDS-ONLY. Only fields the caller actually set contribute an
|
|
16
|
+
* assertion; the three base fields (type/unit/interval) are always asserted
|
|
17
|
+
* (frequency+interval are required; the rule TYPE is meaningful even when
|
|
18
|
+
* `afterCompletion` is defaulted). A bare `{frequency, interval}` reschedule
|
|
19
|
+
* therefore never fails on an untouched anchor/ends/deadline — those keep
|
|
20
|
+
* their existing don't-care semantics.
|
|
21
|
+
* 2. EXHAUSTIVE MAP. {@link RULE_ASSERT_MAP} is a `Record` over every key of the
|
|
22
|
+
* rule vocabulary; each key is either an assert-producer or an explicit skip
|
|
23
|
+
* carrying a written reason. Adding a field to {@link RepeatRuleParams} breaks
|
|
24
|
+
* compilation here until it is consciously handled — a multi-consumer
|
|
25
|
+
* vocabulary must never be hand-enumerated per consumer (decisions.md).
|
|
26
|
+
*/
|
|
27
|
+
import { anchorKeyOfOffsets, decodeOffsetEntry } from "../model/recurrence.js";
|
|
28
|
+
import { composeRepeatRuleSpec } from "./recurrence-rule-blob.js";
|
|
29
|
+
/** A never-used anchor reference: every anchor path that reaches the key builder
|
|
30
|
+
* carries an EXPLICIT anchor (weekdays/monthly/yearly present), so composeOffsets
|
|
31
|
+
* never falls back to deriving one from this date. */
|
|
32
|
+
const ANCHOR_DUMMY_REF = "2000-01-01";
|
|
33
|
+
/**
|
|
34
|
+
* The canonical, order-insensitive anchor key the DECODED rule would carry for
|
|
35
|
+
* these requested params — computed by composing the numeric offsets exactly as a
|
|
36
|
+
* write would and round-tripping them through the SAME per-offset decode the read
|
|
37
|
+
* path uses, so the expected key is byte-consistent with the observed one.
|
|
38
|
+
*/
|
|
39
|
+
function expectedAnchorKey(p) {
|
|
40
|
+
const spec = composeRepeatRuleSpec({ uuid: "", ...p }, ANCHOR_DUMMY_REF, 0);
|
|
41
|
+
return anchorKeyOfOffsets((spec.of ?? []).map((o) => decodeOffsetEntry(o)));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The Ends bound, per the RRX1 laws: ends-after sets the `rc` occurrence-count
|
|
45
|
+
* total and OMITS the end date; ends-on-date sets `ed` and no count; never = both
|
|
46
|
+
* null. Every arm asserts BOTH columns so a reschedule from one bound kind to
|
|
47
|
+
* another is fully discriminated (e.g. after→on-date must clear the count).
|
|
48
|
+
*/
|
|
49
|
+
function endsAsserts(ends) {
|
|
50
|
+
switch (ends.kind) {
|
|
51
|
+
case "never":
|
|
52
|
+
return [
|
|
53
|
+
{ field: "repeating.rule.endDate", equals: null },
|
|
54
|
+
{ field: "repeating.rule.occurrenceCount", equals: null },
|
|
55
|
+
];
|
|
56
|
+
case "on-date":
|
|
57
|
+
return [
|
|
58
|
+
{ field: "repeating.rule.endDate", equals: ends.date },
|
|
59
|
+
{ field: "repeating.rule.occurrenceCount", equals: null },
|
|
60
|
+
];
|
|
61
|
+
case "after":
|
|
62
|
+
return [
|
|
63
|
+
{ field: "repeating.rule.occurrenceCount", equals: ends.count },
|
|
64
|
+
{ field: "repeating.rule.endDate", equals: null },
|
|
65
|
+
];
|
|
66
|
+
default: {
|
|
67
|
+
const exhaustive = ends;
|
|
68
|
+
throw new Error(`unknown ends bound ${JSON.stringify(exhaustive)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* EXHAUSTIVE over every key of the rule vocabulary. The mapped-type key set (`-?`
|
|
74
|
+
* makes each required) is what makes a newly added {@link RepeatRuleParams} field
|
|
75
|
+
* a COMPILE error until it earns an entry here.
|
|
76
|
+
*/
|
|
77
|
+
const RULE_ASSERT_MAP = {
|
|
78
|
+
frequency: {
|
|
79
|
+
kind: "assert",
|
|
80
|
+
always: true,
|
|
81
|
+
build: (p) => [{ field: "repeating.rule.unit", equals: p.frequency }],
|
|
82
|
+
},
|
|
83
|
+
interval: {
|
|
84
|
+
kind: "assert",
|
|
85
|
+
always: true,
|
|
86
|
+
build: (p) => [{ field: "repeating.rule.interval", equals: p.interval }],
|
|
87
|
+
},
|
|
88
|
+
afterCompletion: {
|
|
89
|
+
kind: "assert",
|
|
90
|
+
// The rule TYPE is always meaningful (an absent flag ⇒ a fixed schedule); a
|
|
91
|
+
// fixed→after-completion conversion leaves unit+interval unchanged, so without
|
|
92
|
+
// this the precheck could not tell the target had not yet been reached.
|
|
93
|
+
always: true,
|
|
94
|
+
build: (p) => [
|
|
95
|
+
{
|
|
96
|
+
field: "repeating.rule.type",
|
|
97
|
+
equals: p.afterCompletion === true ? "after-completion" : "fixed",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
weekdays: {
|
|
102
|
+
kind: "assert",
|
|
103
|
+
build: (p) => [{ field: "repeating.rule.anchorKey", equals: expectedAnchorKey(p) }],
|
|
104
|
+
},
|
|
105
|
+
monthly: {
|
|
106
|
+
kind: "assert",
|
|
107
|
+
build: (p) => [{ field: "repeating.rule.anchorKey", equals: expectedAnchorKey(p) }],
|
|
108
|
+
},
|
|
109
|
+
yearly: {
|
|
110
|
+
kind: "assert",
|
|
111
|
+
build: (p) => [{ field: "repeating.rule.anchorKey", equals: expectedAnchorKey(p) }],
|
|
112
|
+
},
|
|
113
|
+
ends: {
|
|
114
|
+
kind: "assert",
|
|
115
|
+
build: (p) => (p.ends === undefined ? [] : endsAsserts(p.ends)),
|
|
116
|
+
},
|
|
117
|
+
deadline: {
|
|
118
|
+
kind: "assert",
|
|
119
|
+
// A start-offset request also asserts a deadline: startDaysEarlier implies one
|
|
120
|
+
// (assertRepeatRule refuses startDaysEarlier with deadline:false).
|
|
121
|
+
when: (p) => p.deadline !== undefined || (p.startDaysEarlier ?? 0) > 0,
|
|
122
|
+
build: (p) => [
|
|
123
|
+
{
|
|
124
|
+
field: "repeating.deadlined",
|
|
125
|
+
equals: p.deadline === true || (p.startDaysEarlier ?? 0) > 0,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
startDaysEarlier: {
|
|
130
|
+
kind: "assert",
|
|
131
|
+
// ts = −startDaysEarlier on the decoded rule (a deadline-relative start offset).
|
|
132
|
+
build: (p) => [{ field: "repeating.rule.startOffsetDays", equals: -(p.startDaysEarlier ?? 0) }],
|
|
133
|
+
},
|
|
134
|
+
next: {
|
|
135
|
+
kind: "assert",
|
|
136
|
+
// The ANCH2 first-occurrence CURSOR (rt1_nextInstanceStartDate → nextOccurrence)
|
|
137
|
+
// — the first RULE-ALIGNED occurrence, which equals the requested `--when` only
|
|
138
|
+
// when that date is itself on-rule AND future. Gated by
|
|
139
|
+
// RuleAssertOptions.includeCursor (reschedule only) — see expectedRuleAssertions.
|
|
140
|
+
build: (p) => p.next === undefined ? [] : [{ field: "repeating.nextOccurrence", equals: p.next }],
|
|
141
|
+
},
|
|
142
|
+
reminder: {
|
|
143
|
+
kind: "skip",
|
|
144
|
+
reason: "not stored in the recurrence-rule blob (rt1_recurrenceRule) — 'Add reminders' sets a " +
|
|
145
|
+
"time-of-day on the SPAWNED INSTANCES, not on the rule (RRX1/ANCH2). It has no decoded-rule " +
|
|
146
|
+
"field to compare against, so it is verified through the instance reminder-byte path, not here.",
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Build the full-fidelity expected-rule assertion set for a validated rule-params
|
|
151
|
+
* bag. The base three (type/unit/interval) are always emitted; every other field
|
|
152
|
+
* contributes only when requested (requested-fields-only law).
|
|
153
|
+
*/
|
|
154
|
+
export function expectedRuleAssertions(params, opts) {
|
|
155
|
+
const out = [];
|
|
156
|
+
for (const key of Object.keys(RULE_ASSERT_MAP)) {
|
|
157
|
+
// The cursor is a reschedule-only expectation (see RuleAssertOptions).
|
|
158
|
+
if (key === "next" && !opts.includeCursor)
|
|
159
|
+
continue;
|
|
160
|
+
const spec = RULE_ASSERT_MAP[key];
|
|
161
|
+
if (spec.kind === "skip")
|
|
162
|
+
continue;
|
|
163
|
+
const present = spec.when !== undefined ? spec.when(params) : params[key] !== undefined;
|
|
164
|
+
if (present || spec.always === true)
|
|
165
|
+
out.push(...spec.build(params));
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=repeat-asserts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repeat-asserts.js","sourceRoot":"","sources":["../../src/write/repeat-asserts.ts"],"names":[],"mappings":"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"}
|