@ashlr/hub 3.5.1 → 3.8.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/CHANGELOG.md +165 -0
- package/README.md +3 -0
- package/dist/build-identity.json +2 -2
- package/dist/cli/activation.d.ts +41 -0
- package/dist/cli/activation.js +485 -0
- package/dist/cli/activation.js.map +1 -0
- package/dist/core/daemon/cutoff-checkpoint-scheduler.js +7 -0
- package/dist/core/daemon/cutoff-checkpoint-scheduler.js.map +1 -1
- package/dist/core/daemon/loop.d.ts +19 -0
- package/dist/core/daemon/loop.js +235 -5
- package/dist/core/daemon/loop.js.map +1 -1
- package/dist/core/daemon/overnight-status.d.ts +152 -0
- package/dist/core/daemon/overnight-status.js +275 -0
- package/dist/core/daemon/overnight-status.js.map +1 -0
- package/dist/core/daemon/post-merge-halt.d.ts +212 -0
- package/dist/core/daemon/post-merge-halt.js +503 -0
- package/dist/core/daemon/post-merge-halt.js.map +1 -0
- package/dist/core/daemon/run-window.d.ts +218 -0
- package/dist/core/daemon/run-window.js +420 -0
- package/dist/core/daemon/run-window.js.map +1 -0
- package/dist/core/fleet/generated-repair-resolution.d.ts +36 -0
- package/dist/core/fleet/generated-repair-resolution.js +245 -0
- package/dist/core/fleet/generated-repair-resolution.js.map +1 -0
- package/dist/core/local-runtime/llama/port.d.ts +17 -0
- package/dist/core/local-runtime/llama/port.js +47 -0
- package/dist/core/local-runtime/llama/port.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-host.d.ts +54 -0
- package/dist/core/local-runtime/llama/proxy-host.js +159 -0
- package/dist/core/local-runtime/llama/proxy-host.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-process.d.ts +148 -0
- package/dist/core/local-runtime/llama/proxy-process.js +250 -0
- package/dist/core/local-runtime/llama/proxy-process.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-record.d.ts +40 -0
- package/dist/core/local-runtime/llama/proxy-record.js +133 -0
- package/dist/core/local-runtime/llama/proxy-record.js.map +1 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.d.ts +118 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.js +511 -0
- package/dist/core/local-runtime/llama/proxy-supervisor.js.map +1 -0
- package/dist/core/policy/local-only.d.ts +142 -2
- package/dist/core/policy/local-only.js +204 -15
- package/dist/core/policy/local-only.js.map +1 -1
- package/dist/core/run/budget.d.ts +22 -8
- package/dist/core/run/budget.js +80 -12
- package/dist/core/run/budget.js.map +1 -1
- package/dist/core/run/sandboxed-engine.js +3 -3
- package/dist/core/run/sandboxed-engine.js.map +1 -1
- package/dist/core/universe/builtins/preparation/manifest.json +1 -1
- package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +61 -9
- package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +60 -8
- package/dist/core/verse/session-engine.d.ts +42 -1
- package/dist/core/verse/session-engine.js +79 -2
- package/dist/core/verse/session-engine.js.map +1 -1
- package/dist/core/web/public/next/assets/{App-BbVoUCIi.js → App-BsbJFA2j.js} +2 -2
- package/dist/core/web/public/next/assets/{App-BbVoUCIi.js.map → App-BsbJFA2j.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ApprovalsSection-BW9iVjUa.js → ApprovalsSection-2CCSoBTJ.js} +2 -2
- package/dist/core/web/public/next/assets/{ApprovalsSection-BW9iVjUa.js.map → ApprovalsSection-2CCSoBTJ.js.map} +1 -1
- package/dist/core/web/public/next/assets/{AutonomySection-Cpk_oEhc.css → AutonomySection-CHjxJt2z.css} +1 -1
- package/dist/core/web/public/next/assets/AutonomySection-yeNNUPur.js +2 -0
- package/dist/core/web/public/next/assets/AutonomySection-yeNNUPur.js.map +1 -0
- package/dist/core/web/public/next/assets/ChatSection-BfnLe5dT.css +1 -0
- package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js +80 -0
- package/dist/core/web/public/next/assets/ChatSection-DwDDnCZQ.js.map +1 -0
- package/dist/core/web/public/next/assets/{ConfirmDialog-CXevY3Au.js → ConfirmDialog-C363ty_c.js} +2 -2
- package/dist/core/web/public/next/assets/{ConfirmDialog-CXevY3Au.js.map → ConfirmDialog-C363ty_c.js.map} +1 -1
- package/dist/core/web/public/next/assets/{Dialog-Bn0CgEGz.js → Dialog-nK15L1M0.js} +2 -2
- package/dist/core/web/public/next/assets/{Dialog-Bn0CgEGz.js.map → Dialog-nK15L1M0.js.map} +1 -1
- package/dist/core/web/public/next/assets/{DiffViewer-QKyisN2L.js → DiffViewer-D369eZe8.js} +2 -2
- package/dist/core/web/public/next/assets/{DiffViewer-QKyisN2L.js.map → DiffViewer-D369eZe8.js.map} +1 -1
- package/dist/core/web/public/next/assets/McpSection-D88QlDpG.css +1 -0
- package/dist/core/web/public/next/assets/McpSection-DVgiSYG1.js +2 -0
- package/dist/core/web/public/next/assets/McpSection-DVgiSYG1.js.map +1 -0
- package/dist/core/web/public/next/assets/Meter-CLcr-4wS.css +1 -0
- package/dist/core/web/public/next/assets/Meter-Cmo1pLbk.js +2 -0
- package/dist/core/web/public/next/assets/Meter-Cmo1pLbk.js.map +1 -0
- package/dist/core/web/public/next/assets/{MutationTokenDialog-DbSHPy3w.js → MutationTokenDialog-B9I4PAcp.js} +2 -2
- package/dist/core/web/public/next/assets/{MutationTokenDialog-DbSHPy3w.js.map → MutationTokenDialog-B9I4PAcp.js.map} +1 -1
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-C-PCgCzo.js → ResourcePoolConsoleApp-z6tJj3s9.js} +2 -2
- package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-C-PCgCzo.js.map → ResourcePoolConsoleApp-z6tJj3s9.js.map} +1 -1
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-Dw8ZjmkS.js → RouteErrorBoundary-OSajL45F.js} +2 -2
- package/dist/core/web/public/next/assets/{RouteErrorBoundary-Dw8ZjmkS.js.map → RouteErrorBoundary-OSajL45F.js.map} +1 -1
- package/dist/core/web/public/next/assets/SettingsSection-B3YH2Npi.js +2 -0
- package/dist/core/web/public/next/assets/SettingsSection-B3YH2Npi.js.map +1 -0
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-Bk5HipM0.js → UniverseConsoleApp-wEKIzl1M.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseConsoleApp-Bk5HipM0.js.map → UniverseConsoleApp-wEKIzl1M.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UniverseView-D8k9q0ce.js → UniverseView-CQVpJqqI.js} +2 -2
- package/dist/core/web/public/next/assets/{UniverseView-D8k9q0ce.js.map → UniverseView-CQVpJqqI.js.map} +1 -1
- package/dist/core/web/public/next/assets/{UsageSection-B2E06ZH1.js → UsageSection-dToBThXe.js} +2 -2
- package/dist/core/web/public/next/assets/{UsageSection-B2E06ZH1.js.map → UsageSection-dToBThXe.js.map} +1 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-CMkCizvL.css +1 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js +3 -0
- package/dist/core/web/public/next/assets/VerseConsoleApp-DHU32JCQ.js.map +1 -0
- package/dist/core/web/public/next/assets/{charts-DSUpczxG.js → charts-BopViIha.js} +2 -2
- package/dist/core/web/public/next/assets/{charts-DSUpczxG.js.map → charts-BopViIha.js.map} +1 -1
- package/dist/core/web/public/next/assets/global-DMWakEhP.js +2 -0
- package/dist/core/web/public/next/assets/global-DMWakEhP.js.map +1 -0
- package/dist/core/web/public/next/assets/global-z-JfD3ow.css +1 -0
- package/dist/core/web/public/next/assets/{icons-BxOX8-XB.js → icons-L06pQH6j.js} +2 -2
- package/dist/core/web/public/next/assets/{icons-BxOX8-XB.js.map → icons-L06pQH6j.js.map} +1 -1
- package/dist/core/web/public/next/assets/{index-BMRIET7F.js → index-CXgVQHek.js} +3 -3
- package/dist/core/web/public/next/assets/{index-BMRIET7F.js.map → index-CXgVQHek.js.map} +1 -1
- package/dist/core/web/public/next/assets/{queries-AxnVa02h.js → queries-D3sSlan1.js} +2 -2
- package/dist/core/web/public/next/assets/{queries-AxnVa02h.js.map → queries-D3sSlan1.js.map} +1 -1
- package/dist/core/web/public/next/assets/{use-guarded-action-BO6c94Wp.js → use-guarded-action-BxfJ9E-n.js} +2 -2
- package/dist/core/web/public/next/assets/{use-guarded-action-BO6c94Wp.js.map → use-guarded-action-BxfJ9E-n.js.map} +1 -1
- package/dist/core/web/public/next/assets/workspace-model-v0a1gl-C.js +2 -0
- package/dist/core/web/public/next/assets/workspace-model-v0a1gl-C.js.map +1 -0
- package/dist/core/web/public/next/index.html +1 -1
- package/dist/release-dependency-inventory.json +1 -1
- package/package.json +1 -1
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js +0 -2
- package/dist/core/web/public/next/assets/AutonomySection-CRqHUhED.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js +0 -80
- package/dist/core/web/public/next/assets/ChatSection-5irrh6Jw.js.map +0 -1
- package/dist/core/web/public/next/assets/ChatSection-LQiAn6Eu.css +0 -1
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js +0 -2
- package/dist/core/web/public/next/assets/SettingsSection-CWxtTNlD.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js +0 -3
- package/dist/core/web/public/next/assets/VerseConsoleApp-CRfZUV-T.js.map +0 -1
- package/dist/core/web/public/next/assets/VerseConsoleApp-D2E3mU9a.css +0 -1
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js +0 -2
- package/dist/core/web/public/next/assets/global-C4jfYHrj.js.map +0 -1
- package/dist/core/web/public/next/assets/global-D463gG1X.css +0 -1
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js +0 -2
- package/dist/core/web/public/next/assets/workspace-model-cE4LkKpE.js.map +0 -1
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-window.ts — the BOUNDED RUN WINDOW. Pure maths, no I/O, never throws.
|
|
3
|
+
*
|
|
4
|
+
* ── WHY THIS EXISTS ────────────────────────────────────────────────────────
|
|
5
|
+
* Every scheduler in this repo is relative-elapsed: `Date.now()` deltas and
|
|
6
|
+
* `setTimeout`. Nothing parses a time of day, nothing knows a timezone, and
|
|
7
|
+
* `StartCalendarInterval` appears nowhere — the two launch-trigger allowlists
|
|
8
|
+
* (`daemon/runtime-activation-transaction.ts`, `daemon/resident-service-
|
|
9
|
+
* readiness.ts`) actively REJECT an extra OS trigger and their tests lock that.
|
|
10
|
+
* So an overnight run window cannot be an OS launch trigger. It is IN-PROCESS,
|
|
11
|
+
* and this module is the whole of its arithmetic.
|
|
12
|
+
*
|
|
13
|
+
* Three stop rules, chosen per run:
|
|
14
|
+
*
|
|
15
|
+
* { kind: 'until-paused' } run until the operator parks it
|
|
16
|
+
* { kind: 'until-clock', at: '07:00' } stop at a wall-clock local time
|
|
17
|
+
* { kind: 'iterations', max: 40 } stop after N loop iterations
|
|
18
|
+
*
|
|
19
|
+
* ── THE THINGS A NAIVE CLOCK GETS WRONG ────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* 1. THE MACHINE SLEEPS AND WAKES PAST THE END TIME.
|
|
22
|
+
* A window must NEVER be held as "remaining milliseconds" counted down by
|
|
23
|
+
* elapsed time, and must never be armed as one long `setTimeout`. A laptop
|
|
24
|
+
* suspended at 01:00 and opened at 09:00 fired no timers in between; a
|
|
25
|
+
* single `setTimeout(6h)` scheduled for 07:00 fires at 15:00 on macOS,
|
|
26
|
+
* eight hours of wall-clock too late, and a countdown of elapsed awake time
|
|
27
|
+
* never reaches zero at all. So the window is stored as an ABSOLUTE epoch
|
|
28
|
+
* instant (`endsAtMs`) and every check is `Date.now() >= endsAtMs`. A run
|
|
29
|
+
* that slept through its own end time is expired the first moment it looks,
|
|
30
|
+
* with `reason: 'clock-reached'` and a negative remaining — which is the
|
|
31
|
+
* correct answer, not an error. {@link runWindowParkMs} additionally caps
|
|
32
|
+
* every park at {@link RUN_WINDOW_MAX_PARK_MS} so a woken process re-checks
|
|
33
|
+
* within a minute instead of finishing a stale timer.
|
|
34
|
+
*
|
|
35
|
+
* 2. A STOP TIME THAT IS TOMORROW. The overnight case IS the case: a run
|
|
36
|
+
* started at 23:00 that stops at 07:00 must resolve to TOMORROW's 07:00,
|
|
37
|
+
* not to an instant eight hours in the past. {@link nextWallClockOccurrenceMs}
|
|
38
|
+
* always resolves strictly forward — when today's occurrence is already at
|
|
39
|
+
* or behind `now`, it advances one LOCAL CALENDAR DAY (not `+24h`, which is
|
|
40
|
+
* wrong across a DST transition by exactly the offset change).
|
|
41
|
+
*
|
|
42
|
+
* 3. THE LOCAL TIMEZONE, AND DST. '07:00' means 07:00 where the operator is.
|
|
43
|
+
* Offsets are resolved per-instant from the IANA zone via `Intl`, so a
|
|
44
|
+
* window spanning a DST boundary is 7 or 9 hours of real time as the clock
|
|
45
|
+
* requires, not a fixed 8. A local time that does not exist (the spring-
|
|
46
|
+
* forward gap) resolves to the instant the clock jumps to; a local time
|
|
47
|
+
* that happens twice (the autumn fall-back) resolves to the FIRST of the
|
|
48
|
+
* two. Both are documented choices, not accidents — see
|
|
49
|
+
* {@link wallClockToEpochMs}.
|
|
50
|
+
*
|
|
51
|
+
* This module decides nothing about stopping. It reports that the window is
|
|
52
|
+
* over; `loop.ts` parks the daemon by PAUSING (`~/.ashlr/daemon.paused`), never
|
|
53
|
+
* by `stopDaemon()`, which is `setKill(true)` and would also refuse the agent's
|
|
54
|
+
* own write tools. See `daemon/pause.ts`.
|
|
55
|
+
*
|
|
56
|
+
* No new runtime deps; `Intl` only. Never throws out of a public API.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* The three stop rules. A run picks exactly one.
|
|
60
|
+
*
|
|
61
|
+
* This is the wire shape `POST /api/verse/overnight` sends, shared verbatim
|
|
62
|
+
* with the client's `overnight-contract.ts`.
|
|
63
|
+
*
|
|
64
|
+
* `at` is an ABSOLUTE ISO-8601 instant, deliberately NOT a time of day. The
|
|
65
|
+
* caller resolves the operator's wall-clock pick to the next occurrence in
|
|
66
|
+
* their own timezone BEFORE arming, which removes the ambiguity entirely: a
|
|
67
|
+
* 23:50 arming for "07:00" can never be read here as ten minutes ago. A caller
|
|
68
|
+
* with no browser (the CLI) resolves it with {@link resolveWallClockStopRule},
|
|
69
|
+
* which is the same arithmetic the browser does.
|
|
70
|
+
*/
|
|
71
|
+
export type RunWindowStopRule = {
|
|
72
|
+
readonly kind: 'until-paused';
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: 'at-time';
|
|
75
|
+
readonly at: string;
|
|
76
|
+
} | {
|
|
77
|
+
readonly kind: 'after-iterations';
|
|
78
|
+
readonly iterations: number;
|
|
79
|
+
};
|
|
80
|
+
/** A stop rule resolved against a concrete `now` into absolute quantities. */
|
|
81
|
+
export interface ResolvedRunWindow {
|
|
82
|
+
readonly kind: RunWindowStopRule['kind'];
|
|
83
|
+
/** Epoch ms the window was armed at. */
|
|
84
|
+
readonly startedAtMs: number;
|
|
85
|
+
/**
|
|
86
|
+
* ABSOLUTE epoch ms the window ends, or null when the rule has no clock
|
|
87
|
+
* component. Never a duration — see note 1 in the file header.
|
|
88
|
+
*/
|
|
89
|
+
readonly endsAtMs: number | null;
|
|
90
|
+
/** Iteration ceiling, or null when the rule has no iteration component. */
|
|
91
|
+
readonly maxIterations: number | null;
|
|
92
|
+
/** The absolute ISO instant for an `at-time` rule; null otherwise. */
|
|
93
|
+
readonly at: string | null;
|
|
94
|
+
/** One line, safe for an audit summary. Metadata only. */
|
|
95
|
+
readonly describe: string;
|
|
96
|
+
}
|
|
97
|
+
/** Why a window is over. `null` while it is still open. */
|
|
98
|
+
export type RunWindowExpiry = 'clock-reached' | 'iterations-reached';
|
|
99
|
+
export interface RunWindowEvaluation {
|
|
100
|
+
readonly expired: boolean;
|
|
101
|
+
readonly reason: RunWindowExpiry | null;
|
|
102
|
+
/**
|
|
103
|
+
* Signed ms until `endsAtMs` (NEGATIVE when the end time already passed,
|
|
104
|
+
* which is how a slept-through window reports itself), or null.
|
|
105
|
+
*/
|
|
106
|
+
readonly remainingMs: number | null;
|
|
107
|
+
readonly remainingIterations: number | null;
|
|
108
|
+
readonly detail: string;
|
|
109
|
+
}
|
|
110
|
+
/** {@link resolveRunWindow}'s result. A refusal is a value, never a throw. */
|
|
111
|
+
export type ResolveRunWindowResult = {
|
|
112
|
+
readonly ok: true;
|
|
113
|
+
readonly window: ResolvedRunWindow;
|
|
114
|
+
} | {
|
|
115
|
+
readonly ok: false;
|
|
116
|
+
readonly reason: string;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* The longest a caller may park in one go while a window is open. A machine
|
|
120
|
+
* that suspends mid-park wakes with a stale timer; capping the chunk bounds
|
|
121
|
+
* how long a woken process can run past its own end time to one minute.
|
|
122
|
+
* Matches `sleepUntilNextUtcBudgetDay`'s existing 60s chunking in loop.ts.
|
|
123
|
+
*/
|
|
124
|
+
export declare const RUN_WINDOW_MAX_PARK_MS = 60000;
|
|
125
|
+
/** The host's IANA zone, or 'UTC' when the runtime cannot say. Never throws. */
|
|
126
|
+
export declare function hostTimeZone(): string;
|
|
127
|
+
/** True when `timeZone` is a zone this runtime can actually resolve. */
|
|
128
|
+
export declare function isResolvableTimeZone(timeZone: string): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* The epoch instant at which `timeZone`'s clock reads the given local
|
|
131
|
+
* date-and-time. Returns null when the zone cannot be resolved.
|
|
132
|
+
*
|
|
133
|
+
* Two passes: guess with the offset in force at the naive-UTC instant, then
|
|
134
|
+
* re-read the offset at the corrected instant and correct again if it changed.
|
|
135
|
+
* That second pass is what makes DST boundaries land correctly.
|
|
136
|
+
*
|
|
137
|
+
* AMBIGUOUS AND NONEXISTENT LOCAL TIMES — both documented, neither an error:
|
|
138
|
+
* - Spring forward (02:30 does not exist): both passes agree on an instant
|
|
139
|
+
* whose clock reads 03:30, i.e. the moment the clock jumps past the request.
|
|
140
|
+
* A window asking to stop at a skipped time stops when that time would have
|
|
141
|
+
* arrived, which is the operator's intent.
|
|
142
|
+
* - Autumn fall-back (01:30 happens twice): resolves to the FIRST occurrence,
|
|
143
|
+
* the earlier instant, so a window never runs an extra hour by surprise.
|
|
144
|
+
* Erring short is the safe direction for a bound on autonomous work.
|
|
145
|
+
*/
|
|
146
|
+
export declare function wallClockToEpochMs(year: number, month: number, day: number, hour: number, minute: number, timeZone: string): number | null;
|
|
147
|
+
/** Parse 'HH:MM' (24h). Returns null for anything else — no throw, no guess. */
|
|
148
|
+
export declare function parseWallClockTime(at: string): {
|
|
149
|
+
hour: number;
|
|
150
|
+
minute: number;
|
|
151
|
+
} | null;
|
|
152
|
+
/**
|
|
153
|
+
* The next instant at which `timeZone`'s clock reads `at`, STRICTLY AFTER
|
|
154
|
+
* `nowMs`. This is the overnight wrap: at 23:00 local, '07:00' is tomorrow's
|
|
155
|
+
* 07:00, because today's has already gone.
|
|
156
|
+
*
|
|
157
|
+
* Advances by one LOCAL CALENDAR DAY, never by `+24h` — across a DST boundary
|
|
158
|
+
* those differ by the offset change, and only the calendar-day advance keeps
|
|
159
|
+
* the clock reading the requested time.
|
|
160
|
+
*/
|
|
161
|
+
export declare function nextWallClockOccurrenceMs(at: string, nowMs: number, timeZone: string): number | null;
|
|
162
|
+
/**
|
|
163
|
+
* The iteration ceiling, matching the client contract's documented 1..500.
|
|
164
|
+
* A bound a typo can turn into "forever" is not a bound.
|
|
165
|
+
*/
|
|
166
|
+
export declare const RUN_WINDOW_MAX_ITERATIONS = 500;
|
|
167
|
+
/**
|
|
168
|
+
* The furthest ahead an `at-time` stop may be armed. A window is a NIGHT, not
|
|
169
|
+
* a standing instruction; an instant six weeks out is a mistake, not a plan.
|
|
170
|
+
*/
|
|
171
|
+
export declare const RUN_WINDOW_MAX_HORIZON_MS: number;
|
|
172
|
+
/**
|
|
173
|
+
* Turn an operator's wall-clock pick into the contract's absolute `at-time`
|
|
174
|
+
* rule. This is the arithmetic the browser does before it POSTs; it lives here
|
|
175
|
+
* so the CLI, which has no browser to do it, resolves it identically instead
|
|
176
|
+
* of inventing a second, subtly different answer.
|
|
177
|
+
*
|
|
178
|
+
* Handles the overnight wrap and DST — see {@link nextWallClockOccurrenceMs}.
|
|
179
|
+
*/
|
|
180
|
+
export declare function resolveWallClockStopRule(at: string, opts?: {
|
|
181
|
+
nowMs?: number;
|
|
182
|
+
timeZone?: string;
|
|
183
|
+
}): {
|
|
184
|
+
ok: true;
|
|
185
|
+
rule: RunWindowStopRule;
|
|
186
|
+
} | {
|
|
187
|
+
ok: false;
|
|
188
|
+
reason: string;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Resolve a stop rule against `nowMs` into absolute quantities. Never throws;
|
|
192
|
+
* every rejection is an `{ ok: false, reason }` value, and every reason is a
|
|
193
|
+
* SENTENCE, because it is rendered verbatim to the operator as the refusal
|
|
194
|
+
* `note` on `POST /api/verse/overnight`.
|
|
195
|
+
*/
|
|
196
|
+
export declare function resolveRunWindow(rule: RunWindowStopRule, opts?: {
|
|
197
|
+
nowMs?: number;
|
|
198
|
+
}): ResolveRunWindowResult;
|
|
199
|
+
/**
|
|
200
|
+
* Is the window over? Compares an ABSOLUTE deadline against an ABSOLUTE now,
|
|
201
|
+
* so a process that was suspended across its own end time reports expired on
|
|
202
|
+
* its first look, with a negative `remainingMs`.
|
|
203
|
+
*
|
|
204
|
+
* `iterations` is the count of iterations ALREADY COMPLETED.
|
|
205
|
+
*/
|
|
206
|
+
export declare function evaluateRunWindow(window: ResolvedRunWindow, state?: {
|
|
207
|
+
nowMs?: number;
|
|
208
|
+
iterations?: number;
|
|
209
|
+
}): RunWindowEvaluation;
|
|
210
|
+
/**
|
|
211
|
+
* How long a caller may park before it must look at the window again.
|
|
212
|
+
*
|
|
213
|
+
* Never longer than {@link RUN_WINDOW_MAX_PARK_MS}, so a suspended host that
|
|
214
|
+
* wakes past its end time notices within a minute instead of serving out a
|
|
215
|
+
* timer armed before the suspend. Never longer than the time actually left,
|
|
216
|
+
* so the window is not overshot by a park. Never negative.
|
|
217
|
+
*/
|
|
218
|
+
export declare function runWindowParkMs(window: ResolvedRunWindow, requestedMs: number, nowMs?: number): number;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-window.ts — the BOUNDED RUN WINDOW. Pure maths, no I/O, never throws.
|
|
3
|
+
*
|
|
4
|
+
* ── WHY THIS EXISTS ────────────────────────────────────────────────────────
|
|
5
|
+
* Every scheduler in this repo is relative-elapsed: `Date.now()` deltas and
|
|
6
|
+
* `setTimeout`. Nothing parses a time of day, nothing knows a timezone, and
|
|
7
|
+
* `StartCalendarInterval` appears nowhere — the two launch-trigger allowlists
|
|
8
|
+
* (`daemon/runtime-activation-transaction.ts`, `daemon/resident-service-
|
|
9
|
+
* readiness.ts`) actively REJECT an extra OS trigger and their tests lock that.
|
|
10
|
+
* So an overnight run window cannot be an OS launch trigger. It is IN-PROCESS,
|
|
11
|
+
* and this module is the whole of its arithmetic.
|
|
12
|
+
*
|
|
13
|
+
* Three stop rules, chosen per run:
|
|
14
|
+
*
|
|
15
|
+
* { kind: 'until-paused' } run until the operator parks it
|
|
16
|
+
* { kind: 'until-clock', at: '07:00' } stop at a wall-clock local time
|
|
17
|
+
* { kind: 'iterations', max: 40 } stop after N loop iterations
|
|
18
|
+
*
|
|
19
|
+
* ── THE THINGS A NAIVE CLOCK GETS WRONG ────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* 1. THE MACHINE SLEEPS AND WAKES PAST THE END TIME.
|
|
22
|
+
* A window must NEVER be held as "remaining milliseconds" counted down by
|
|
23
|
+
* elapsed time, and must never be armed as one long `setTimeout`. A laptop
|
|
24
|
+
* suspended at 01:00 and opened at 09:00 fired no timers in between; a
|
|
25
|
+
* single `setTimeout(6h)` scheduled for 07:00 fires at 15:00 on macOS,
|
|
26
|
+
* eight hours of wall-clock too late, and a countdown of elapsed awake time
|
|
27
|
+
* never reaches zero at all. So the window is stored as an ABSOLUTE epoch
|
|
28
|
+
* instant (`endsAtMs`) and every check is `Date.now() >= endsAtMs`. A run
|
|
29
|
+
* that slept through its own end time is expired the first moment it looks,
|
|
30
|
+
* with `reason: 'clock-reached'` and a negative remaining — which is the
|
|
31
|
+
* correct answer, not an error. {@link runWindowParkMs} additionally caps
|
|
32
|
+
* every park at {@link RUN_WINDOW_MAX_PARK_MS} so a woken process re-checks
|
|
33
|
+
* within a minute instead of finishing a stale timer.
|
|
34
|
+
*
|
|
35
|
+
* 2. A STOP TIME THAT IS TOMORROW. The overnight case IS the case: a run
|
|
36
|
+
* started at 23:00 that stops at 07:00 must resolve to TOMORROW's 07:00,
|
|
37
|
+
* not to an instant eight hours in the past. {@link nextWallClockOccurrenceMs}
|
|
38
|
+
* always resolves strictly forward — when today's occurrence is already at
|
|
39
|
+
* or behind `now`, it advances one LOCAL CALENDAR DAY (not `+24h`, which is
|
|
40
|
+
* wrong across a DST transition by exactly the offset change).
|
|
41
|
+
*
|
|
42
|
+
* 3. THE LOCAL TIMEZONE, AND DST. '07:00' means 07:00 where the operator is.
|
|
43
|
+
* Offsets are resolved per-instant from the IANA zone via `Intl`, so a
|
|
44
|
+
* window spanning a DST boundary is 7 or 9 hours of real time as the clock
|
|
45
|
+
* requires, not a fixed 8. A local time that does not exist (the spring-
|
|
46
|
+
* forward gap) resolves to the instant the clock jumps to; a local time
|
|
47
|
+
* that happens twice (the autumn fall-back) resolves to the FIRST of the
|
|
48
|
+
* two. Both are documented choices, not accidents — see
|
|
49
|
+
* {@link wallClockToEpochMs}.
|
|
50
|
+
*
|
|
51
|
+
* This module decides nothing about stopping. It reports that the window is
|
|
52
|
+
* over; `loop.ts` parks the daemon by PAUSING (`~/.ashlr/daemon.paused`), never
|
|
53
|
+
* by `stopDaemon()`, which is `setKill(true)` and would also refuse the agent's
|
|
54
|
+
* own write tools. See `daemon/pause.ts`.
|
|
55
|
+
*
|
|
56
|
+
* No new runtime deps; `Intl` only. Never throws out of a public API.
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* The longest a caller may park in one go while a window is open. A machine
|
|
60
|
+
* that suspends mid-park wakes with a stale timer; capping the chunk bounds
|
|
61
|
+
* how long a woken process can run past its own end time to one minute.
|
|
62
|
+
* Matches `sleepUntilNextUtcBudgetDay`'s existing 60s chunking in loop.ts.
|
|
63
|
+
*/
|
|
64
|
+
export const RUN_WINDOW_MAX_PARK_MS = 60_000;
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Timezone arithmetic
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/** The host's IANA zone, or 'UTC' when the runtime cannot say. Never throws. */
|
|
69
|
+
export function hostTimeZone() {
|
|
70
|
+
try {
|
|
71
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
72
|
+
return typeof zone === 'string' && zone.length > 0 ? zone : 'UTC';
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return 'UTC';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** True when `timeZone` is a zone this runtime can actually resolve. */
|
|
79
|
+
export function isResolvableTimeZone(timeZone) {
|
|
80
|
+
try {
|
|
81
|
+
new Intl.DateTimeFormat('en-US', { timeZone });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The wall-clock components `instantMs` shows in `timeZone`.
|
|
90
|
+
* Returns null when the zone cannot be resolved.
|
|
91
|
+
*/
|
|
92
|
+
function partsInZone(instantMs, timeZone) {
|
|
93
|
+
try {
|
|
94
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
95
|
+
timeZone,
|
|
96
|
+
hour12: false,
|
|
97
|
+
year: 'numeric',
|
|
98
|
+
month: '2-digit',
|
|
99
|
+
day: '2-digit',
|
|
100
|
+
hour: '2-digit',
|
|
101
|
+
minute: '2-digit',
|
|
102
|
+
second: '2-digit',
|
|
103
|
+
});
|
|
104
|
+
const read = {};
|
|
105
|
+
for (const part of formatter.formatToParts(new Date(instantMs))) {
|
|
106
|
+
if (part.type === 'literal')
|
|
107
|
+
continue;
|
|
108
|
+
const value = Number(part.value);
|
|
109
|
+
if (!Number.isFinite(value))
|
|
110
|
+
continue;
|
|
111
|
+
read[part.type] = value;
|
|
112
|
+
}
|
|
113
|
+
const year = read['year'];
|
|
114
|
+
const month = read['month'];
|
|
115
|
+
const day = read['day'];
|
|
116
|
+
// `hour12: false` yields 24 rather than 0 for midnight in some ICU builds.
|
|
117
|
+
const hour = read['hour'] === 24 ? 0 : read['hour'];
|
|
118
|
+
const minute = read['minute'];
|
|
119
|
+
const second = read['second'];
|
|
120
|
+
if (year === undefined || month === undefined || day === undefined ||
|
|
121
|
+
hour === undefined || minute === undefined || second === undefined) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return { year, month, day, hour, minute, second };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The zone's UTC offset (ms) AT a given instant. Positive east of Greenwich.
|
|
132
|
+
* Derived by reading the instant's wall clock in the zone and treating those
|
|
133
|
+
* components as if they were UTC — the difference is the offset in force then,
|
|
134
|
+
* which is what makes this DST-correct rather than fixed per zone.
|
|
135
|
+
*/
|
|
136
|
+
function zoneOffsetMsAt(instantMs, timeZone) {
|
|
137
|
+
const parts = partsInZone(instantMs, timeZone);
|
|
138
|
+
if (!parts)
|
|
139
|
+
return null;
|
|
140
|
+
const asIfUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
|
|
141
|
+
// Second-truncate the probe so the sub-second remainder does not leak in.
|
|
142
|
+
return asIfUtc - Math.floor(instantMs / 1000) * 1000;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The epoch instant at which `timeZone`'s clock reads the given local
|
|
146
|
+
* date-and-time. Returns null when the zone cannot be resolved.
|
|
147
|
+
*
|
|
148
|
+
* Two passes: guess with the offset in force at the naive-UTC instant, then
|
|
149
|
+
* re-read the offset at the corrected instant and correct again if it changed.
|
|
150
|
+
* That second pass is what makes DST boundaries land correctly.
|
|
151
|
+
*
|
|
152
|
+
* AMBIGUOUS AND NONEXISTENT LOCAL TIMES — both documented, neither an error:
|
|
153
|
+
* - Spring forward (02:30 does not exist): both passes agree on an instant
|
|
154
|
+
* whose clock reads 03:30, i.e. the moment the clock jumps past the request.
|
|
155
|
+
* A window asking to stop at a skipped time stops when that time would have
|
|
156
|
+
* arrived, which is the operator's intent.
|
|
157
|
+
* - Autumn fall-back (01:30 happens twice): resolves to the FIRST occurrence,
|
|
158
|
+
* the earlier instant, so a window never runs an extra hour by surprise.
|
|
159
|
+
* Erring short is the safe direction for a bound on autonomous work.
|
|
160
|
+
*/
|
|
161
|
+
export function wallClockToEpochMs(year, month, day, hour, minute, timeZone) {
|
|
162
|
+
const naive = Date.UTC(year, month - 1, day, hour, minute, 0, 0);
|
|
163
|
+
if (!Number.isFinite(naive))
|
|
164
|
+
return null;
|
|
165
|
+
const firstOffset = zoneOffsetMsAt(naive, timeZone);
|
|
166
|
+
if (firstOffset === null)
|
|
167
|
+
return null;
|
|
168
|
+
const firstGuess = naive - firstOffset;
|
|
169
|
+
const secondOffset = zoneOffsetMsAt(firstGuess, timeZone);
|
|
170
|
+
if (secondOffset === null)
|
|
171
|
+
return null;
|
|
172
|
+
if (secondOffset === firstOffset)
|
|
173
|
+
return firstGuess;
|
|
174
|
+
const secondGuess = naive - secondOffset;
|
|
175
|
+
// Fall-back hour: both candidates are real instants reading the requested
|
|
176
|
+
// clock time. Take the earlier one — a bound that errs short, never long.
|
|
177
|
+
const thirdOffset = zoneOffsetMsAt(secondGuess, timeZone);
|
|
178
|
+
if (thirdOffset === secondOffset)
|
|
179
|
+
return Math.min(firstGuess, secondGuess);
|
|
180
|
+
return firstGuess;
|
|
181
|
+
}
|
|
182
|
+
/** Parse 'HH:MM' (24h). Returns null for anything else — no throw, no guess. */
|
|
183
|
+
export function parseWallClockTime(at) {
|
|
184
|
+
if (typeof at !== 'string')
|
|
185
|
+
return null;
|
|
186
|
+
const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(at.trim());
|
|
187
|
+
if (!match)
|
|
188
|
+
return null;
|
|
189
|
+
return { hour: Number(match[1]), minute: Number(match[2]) };
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The next instant at which `timeZone`'s clock reads `at`, STRICTLY AFTER
|
|
193
|
+
* `nowMs`. This is the overnight wrap: at 23:00 local, '07:00' is tomorrow's
|
|
194
|
+
* 07:00, because today's has already gone.
|
|
195
|
+
*
|
|
196
|
+
* Advances by one LOCAL CALENDAR DAY, never by `+24h` — across a DST boundary
|
|
197
|
+
* those differ by the offset change, and only the calendar-day advance keeps
|
|
198
|
+
* the clock reading the requested time.
|
|
199
|
+
*/
|
|
200
|
+
export function nextWallClockOccurrenceMs(at, nowMs, timeZone) {
|
|
201
|
+
const hhmm = parseWallClockTime(at);
|
|
202
|
+
if (!hhmm)
|
|
203
|
+
return null;
|
|
204
|
+
if (!Number.isFinite(nowMs))
|
|
205
|
+
return null;
|
|
206
|
+
const today = partsInZone(nowMs, timeZone);
|
|
207
|
+
if (!today)
|
|
208
|
+
return null;
|
|
209
|
+
const sameDay = wallClockToEpochMs(today.year, today.month, today.day, hhmm.hour, hhmm.minute, timeZone);
|
|
210
|
+
if (sameDay !== null && sameDay > nowMs)
|
|
211
|
+
return sameDay;
|
|
212
|
+
// Already gone today (or landed exactly on now) — take tomorrow's. Date.UTC
|
|
213
|
+
// normalises a day overflow (e.g. the 32nd) into the next month/year for us;
|
|
214
|
+
// we only use it to get the next calendar date, never as an instant.
|
|
215
|
+
const tomorrowProbe = new Date(Date.UTC(today.year, today.month - 1, today.day + 1));
|
|
216
|
+
const nextDay = wallClockToEpochMs(tomorrowProbe.getUTCFullYear(), tomorrowProbe.getUTCMonth() + 1, tomorrowProbe.getUTCDate(), hhmm.hour, hhmm.minute, timeZone);
|
|
217
|
+
if (nextDay !== null && nextDay > nowMs)
|
|
218
|
+
return nextDay;
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
// Resolve
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
/**
|
|
225
|
+
* The iteration ceiling, matching the client contract's documented 1..500.
|
|
226
|
+
* A bound a typo can turn into "forever" is not a bound.
|
|
227
|
+
*/
|
|
228
|
+
export const RUN_WINDOW_MAX_ITERATIONS = 500;
|
|
229
|
+
/**
|
|
230
|
+
* The furthest ahead an `at-time` stop may be armed. A window is a NIGHT, not
|
|
231
|
+
* a standing instruction; an instant six weeks out is a mistake, not a plan.
|
|
232
|
+
*/
|
|
233
|
+
export const RUN_WINDOW_MAX_HORIZON_MS = 7 * 24 * 3_600_000;
|
|
234
|
+
/**
|
|
235
|
+
* Turn an operator's wall-clock pick into the contract's absolute `at-time`
|
|
236
|
+
* rule. This is the arithmetic the browser does before it POSTs; it lives here
|
|
237
|
+
* so the CLI, which has no browser to do it, resolves it identically instead
|
|
238
|
+
* of inventing a second, subtly different answer.
|
|
239
|
+
*
|
|
240
|
+
* Handles the overnight wrap and DST — see {@link nextWallClockOccurrenceMs}.
|
|
241
|
+
*/
|
|
242
|
+
export function resolveWallClockStopRule(at, opts = {}) {
|
|
243
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
244
|
+
if (!parseWallClockTime(at)) {
|
|
245
|
+
return { ok: false, reason: `A stop time must look like 'HH:MM' on a 24-hour clock. '${at}' does not.` };
|
|
246
|
+
}
|
|
247
|
+
const timeZone = opts.timeZone ?? hostTimeZone();
|
|
248
|
+
if (!isResolvableTimeZone(timeZone)) {
|
|
249
|
+
return { ok: false, reason: `This machine cannot resolve the timezone '${timeZone}', so '${at}' has no fixed meaning.` };
|
|
250
|
+
}
|
|
251
|
+
const endsAtMs = nextWallClockOccurrenceMs(at, nowMs, timeZone);
|
|
252
|
+
if (endsAtMs === null) {
|
|
253
|
+
return { ok: false, reason: `Could not work out when '${at}' next happens in ${timeZone}.` };
|
|
254
|
+
}
|
|
255
|
+
return { ok: true, rule: { kind: 'at-time', at: new Date(endsAtMs).toISOString() } };
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Resolve a stop rule against `nowMs` into absolute quantities. Never throws;
|
|
259
|
+
* every rejection is an `{ ok: false, reason }` value, and every reason is a
|
|
260
|
+
* SENTENCE, because it is rendered verbatim to the operator as the refusal
|
|
261
|
+
* `note` on `POST /api/verse/overnight`.
|
|
262
|
+
*/
|
|
263
|
+
export function resolveRunWindow(rule, opts = {}) {
|
|
264
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
265
|
+
if (!Number.isFinite(nowMs)) {
|
|
266
|
+
return { ok: false, reason: 'This machine could not read its own clock, so a timed run cannot be bounded.' };
|
|
267
|
+
}
|
|
268
|
+
if (rule === null || typeof rule !== 'object') {
|
|
269
|
+
return { ok: false, reason: 'A run needs exactly one stop rule, and none was given.' };
|
|
270
|
+
}
|
|
271
|
+
if (rule.kind === 'until-paused') {
|
|
272
|
+
return {
|
|
273
|
+
ok: true,
|
|
274
|
+
window: {
|
|
275
|
+
kind: 'until-paused',
|
|
276
|
+
startedAtMs: nowMs,
|
|
277
|
+
endsAtMs: null,
|
|
278
|
+
maxIterations: null,
|
|
279
|
+
at: null,
|
|
280
|
+
describe: 'run until paused (~/.ashlr/daemon.paused)',
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (rule.kind === 'after-iterations') {
|
|
285
|
+
const iterations = rule.iterations;
|
|
286
|
+
if (typeof iterations !== 'number' || !Number.isInteger(iterations) || iterations < 1) {
|
|
287
|
+
return { ok: false, reason: 'An iteration budget must be a whole number of at least 1.' };
|
|
288
|
+
}
|
|
289
|
+
if (iterations > RUN_WINDOW_MAX_ITERATIONS) {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
reason: `An iteration budget may be at most ${RUN_WINDOW_MAX_ITERATIONS}; ${iterations} was asked for.`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
window: {
|
|
298
|
+
kind: 'after-iterations',
|
|
299
|
+
startedAtMs: nowMs,
|
|
300
|
+
endsAtMs: null,
|
|
301
|
+
maxIterations: iterations,
|
|
302
|
+
at: null,
|
|
303
|
+
describe: `stop after ${iterations} iteration${iterations === 1 ? '' : 's'}`,
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (rule.kind === 'at-time') {
|
|
308
|
+
if (typeof rule.at !== 'string' || rule.at.trim().length === 0) {
|
|
309
|
+
return { ok: false, reason: 'A timed run needs an absolute ISO-8601 instant to stop at, and none was given.' };
|
|
310
|
+
}
|
|
311
|
+
const endsAtMs = Date.parse(rule.at);
|
|
312
|
+
if (!Number.isFinite(endsAtMs)) {
|
|
313
|
+
return {
|
|
314
|
+
ok: false,
|
|
315
|
+
reason: `'${rule.at}' is not an ISO-8601 instant. A stop time must be absolute (for example ` +
|
|
316
|
+
`2026-09-24T07:00:00.000Z), not a time of day.`,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// The client resolves forward before sending, but a request can still
|
|
320
|
+
// arrive stale — a queued POST, a hand-written call, a host whose clock
|
|
321
|
+
// moved. A window that already ended must REFUSE, not run zero iterations
|
|
322
|
+
// and not silently become unbounded.
|
|
323
|
+
if (endsAtMs <= nowMs) {
|
|
324
|
+
const agoMin = ((nowMs - endsAtMs) / 60_000).toFixed(1);
|
|
325
|
+
return {
|
|
326
|
+
ok: false,
|
|
327
|
+
reason: `That stop time (${rule.at}) is ${agoMin} minutes in the past, so the run would end before ` +
|
|
328
|
+
`it began. Pick a time later than now.`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (endsAtMs - nowMs > RUN_WINDOW_MAX_HORIZON_MS) {
|
|
332
|
+
const days = ((endsAtMs - nowMs) / 86_400_000).toFixed(1);
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
reason: `That stop time is ${days} days away. An unattended run is bounded to at most ` +
|
|
336
|
+
`${RUN_WINDOW_MAX_HORIZON_MS / 86_400_000} days.`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
const hours = (endsAtMs - nowMs) / 3_600_000;
|
|
340
|
+
return {
|
|
341
|
+
ok: true,
|
|
342
|
+
window: {
|
|
343
|
+
kind: 'at-time',
|
|
344
|
+
startedAtMs: nowMs,
|
|
345
|
+
endsAtMs,
|
|
346
|
+
maxIterations: null,
|
|
347
|
+
at: new Date(endsAtMs).toISOString(),
|
|
348
|
+
describe: `stop at ${new Date(endsAtMs).toISOString()} (in ${hours.toFixed(2)}h)`,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
ok: false,
|
|
354
|
+
reason: `'${String(rule.kind)}' is not a stop rule this engine knows.`,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
// Evaluate
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
/**
|
|
361
|
+
* Is the window over? Compares an ABSOLUTE deadline against an ABSOLUTE now,
|
|
362
|
+
* so a process that was suspended across its own end time reports expired on
|
|
363
|
+
* its first look, with a negative `remainingMs`.
|
|
364
|
+
*
|
|
365
|
+
* `iterations` is the count of iterations ALREADY COMPLETED.
|
|
366
|
+
*/
|
|
367
|
+
export function evaluateRunWindow(window, state = {}) {
|
|
368
|
+
const nowMs = state.nowMs ?? Date.now();
|
|
369
|
+
const iterations = state.iterations ?? 0;
|
|
370
|
+
const remainingIterations = window.maxIterations === null
|
|
371
|
+
? null
|
|
372
|
+
: window.maxIterations - iterations;
|
|
373
|
+
const remainingMs = window.endsAtMs === null ? null : window.endsAtMs - nowMs;
|
|
374
|
+
if (window.endsAtMs !== null && Number.isFinite(nowMs) && nowMs >= window.endsAtMs) {
|
|
375
|
+
const overshootMs = nowMs - window.endsAtMs;
|
|
376
|
+
return {
|
|
377
|
+
expired: true,
|
|
378
|
+
reason: 'clock-reached',
|
|
379
|
+
remainingMs,
|
|
380
|
+
remainingIterations,
|
|
381
|
+
detail: overshootMs > RUN_WINDOW_MAX_PARK_MS
|
|
382
|
+
? `run window ended at ${window.at}; ` +
|
|
383
|
+
`noticed ${(overshootMs / 60_000).toFixed(1)} min late (the host was likely asleep)`
|
|
384
|
+
: `run window ended at ${window.at}`,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (remainingIterations !== null && remainingIterations <= 0) {
|
|
388
|
+
return {
|
|
389
|
+
expired: true,
|
|
390
|
+
reason: 'iterations-reached',
|
|
391
|
+
remainingMs,
|
|
392
|
+
remainingIterations,
|
|
393
|
+
detail: `run window completed its ${window.maxIterations}-iteration budget`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
expired: false,
|
|
398
|
+
reason: null,
|
|
399
|
+
remainingMs,
|
|
400
|
+
remainingIterations,
|
|
401
|
+
detail: window.describe,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* How long a caller may park before it must look at the window again.
|
|
406
|
+
*
|
|
407
|
+
* Never longer than {@link RUN_WINDOW_MAX_PARK_MS}, so a suspended host that
|
|
408
|
+
* wakes past its end time notices within a minute instead of serving out a
|
|
409
|
+
* timer armed before the suspend. Never longer than the time actually left,
|
|
410
|
+
* so the window is not overshot by a park. Never negative.
|
|
411
|
+
*/
|
|
412
|
+
export function runWindowParkMs(window, requestedMs, nowMs = Date.now()) {
|
|
413
|
+
const requested = Number.isFinite(requestedMs) ? Math.max(0, requestedMs) : 0;
|
|
414
|
+
let park = Math.min(requested, RUN_WINDOW_MAX_PARK_MS);
|
|
415
|
+
if (window.endsAtMs !== null && Number.isFinite(nowMs)) {
|
|
416
|
+
park = Math.min(park, Math.max(0, window.endsAtMs - nowMs));
|
|
417
|
+
}
|
|
418
|
+
return park;
|
|
419
|
+
}
|
|
420
|
+
//# sourceMappingURL=run-window.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-window.js","sourceRoot":"","sources":["../../../src/core/daemon/run-window.ts"],"names":[],"mappings":"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"}
|