@aitne/daemon 0.1.9 → 0.1.11

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 (333) hide show
  1. package/dist/adapters/adapter-watchdog.d.ts +70 -0
  2. package/dist/adapters/adapter-watchdog.js +115 -0
  3. package/dist/adapters/discord.d.ts +17 -1
  4. package/dist/adapters/discord.js +33 -0
  5. package/dist/adapters/notification-manager.d.ts +27 -1
  6. package/dist/adapters/notification-manager.js +54 -39
  7. package/dist/adapters/slack-adapter.d.ts +26 -1
  8. package/dist/adapters/slack-adapter.js +41 -0
  9. package/dist/adapters/telegram-adapter.d.ts +18 -1
  10. package/dist/adapters/telegram-adapter.js +41 -2
  11. package/dist/adapters/types.d.ts +20 -0
  12. package/dist/adapters/whatsapp-adapter.d.ts +26 -7
  13. package/dist/adapters/whatsapp-adapter.js +74 -21
  14. package/dist/api/env-writer.d.ts +1 -0
  15. package/dist/api/env-writer.js +17 -7
  16. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  17. package/dist/api/helpers/agent-errors-registry.js +5 -5
  18. package/dist/api/routes/agent-schedule.js +5 -1
  19. package/dist/api/routes/agent.js +33 -12
  20. package/dist/api/routes/agents/index.js +75 -16
  21. package/dist/api/routes/agents/views.d.ts +37 -2
  22. package/dist/api/routes/agents/views.js +64 -2
  23. package/dist/api/routes/apple-calendar.js +4 -1
  24. package/dist/api/routes/background-task.d.ts +22 -0
  25. package/dist/api/routes/background-task.js +338 -0
  26. package/dist/api/routes/browser-history.js +9 -1
  27. package/dist/api/routes/calendar.js +12 -2
  28. package/dist/api/routes/context/path-resolve.js +6 -1
  29. package/dist/api/routes/context/permissions.js +12 -2
  30. package/dist/api/routes/context/snapshots.js +0 -3
  31. package/dist/api/routes/context/write.js +3 -17
  32. package/dist/api/routes/dashboard/config.js +58 -12
  33. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  34. package/dist/api/routes/dashboard/notifications.js +9 -9
  35. package/dist/api/routes/dashboard/oauth-google.js +5 -3
  36. package/dist/api/routes/feedback.d.ts +3 -0
  37. package/dist/api/routes/feedback.js +349 -0
  38. package/dist/api/routes/git.js +10 -3
  39. package/dist/api/routes/github.js +5 -1
  40. package/dist/api/routes/integrations/crud-patch.js +5 -1
  41. package/dist/api/routes/integrations-reconcile.js +2 -2
  42. package/dist/api/routes/mcp.js +65 -13
  43. package/dist/api/routes/notion.d.ts +1 -1
  44. package/dist/api/routes/observations.js +7 -7
  45. package/dist/api/routes/obsidian.d.ts +1 -1
  46. package/dist/api/routes/receipts.js +5 -1
  47. package/dist/api/routes/setup-migrate.js +1 -1
  48. package/dist/api/routes/setup.js +1 -1
  49. package/dist/api/routes/task-flows.d.ts +1 -1
  50. package/dist/api/routes/task-flows.js +1 -1
  51. package/dist/api/routes/tuning.d.ts +29 -0
  52. package/dist/api/routes/tuning.js +304 -0
  53. package/dist/api/server.d.ts +44 -16
  54. package/dist/api/server.js +12 -0
  55. package/dist/bootstrap/adapters.d.ts +19 -0
  56. package/dist/bootstrap/adapters.js +61 -0
  57. package/dist/bootstrap/api.d.ts +5 -3
  58. package/dist/bootstrap/api.js +45 -13
  59. package/dist/bootstrap/catchup.d.ts +1 -1
  60. package/dist/bootstrap/catchup.js +11 -11
  61. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  62. package/dist/bootstrap/event-pipeline.js +246 -8
  63. package/dist/bootstrap/observers.js +9 -6
  64. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  65. package/dist/bootstrap/schedule-helpers.js +172 -19
  66. package/dist/config.js +32 -12
  67. package/dist/core/agent-core.d.ts +33 -1
  68. package/dist/core/agent-core.js +36 -1
  69. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  70. package/dist/core/agents/activity-scan-cadence.js +127 -0
  71. package/dist/core/agents/agent-route-override.d.ts +53 -0
  72. package/dist/core/agents/agent-route-override.js +69 -0
  73. package/dist/core/agents/builtin-registry.d.ts +51 -14
  74. package/dist/core/agents/builtin-registry.js +92 -15
  75. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  76. package/dist/core/agents/config-gate-reconcile.js +51 -0
  77. package/dist/core/agents/cron-substitute.d.ts +1 -1
  78. package/dist/core/agents/cron-substitute.js +1 -1
  79. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  80. package/dist/core/agents/custom-routine-migration.js +149 -0
  81. package/dist/core/agents/firing-blocked.d.ts +1 -1
  82. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  83. package/dist/core/agents/hourly-cadence.js +126 -0
  84. package/dist/core/agents/loader-boot.js +23 -0
  85. package/dist/core/agents/loader.d.ts +19 -0
  86. package/dist/core/agents/loader.js +34 -2
  87. package/dist/core/agents/override-merge.d.ts +1 -1
  88. package/dist/core/agents/override-merge.js +9 -1
  89. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  90. package/dist/core/agents/recurrence-convert.js +1 -1
  91. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  92. package/dist/core/alerts.js +6 -6
  93. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  94. package/dist/core/backends/auth-health-monitor.js +1 -1
  95. package/dist/core/backends/backend-router.d.ts +27 -1
  96. package/dist/core/backends/backend-router.js +165 -1
  97. package/dist/core/backends/claude-code-core.d.ts +71 -31
  98. package/dist/core/backends/claude-code-core.js +282 -54
  99. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  100. package/dist/core/backends/cli-quota-guards.js +40 -5
  101. package/dist/core/backends/codex-core.d.ts +6 -0
  102. package/dist/core/backends/codex-core.js +22 -6
  103. package/dist/core/backends/failure-spend.d.ts +58 -0
  104. package/dist/core/backends/failure-spend.js +137 -0
  105. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  106. package/dist/core/backends/gemini-cli-core.js +38 -6
  107. package/dist/core/backends/model-registry.d.ts +1 -1
  108. package/dist/core/backends/model-registry.js +4 -4
  109. package/dist/core/backends/opencode-core.d.ts +1 -1
  110. package/dist/core/backends/opencode-core.js +5 -5
  111. package/dist/core/backends/plan-presets.js +47 -18
  112. package/dist/core/bang-commands/commands-cost.js +3 -1
  113. package/dist/core/bang-commands/commands-report.js +4 -3
  114. package/dist/core/bang-commands/commands-research.js +4 -1
  115. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  116. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  117. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  118. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  119. package/dist/core/bang-commands/commands-task-control.js +147 -0
  120. package/dist/core/bang-commands/commands-wiki.js +5 -5
  121. package/dist/core/bang-commands/index.d.ts +2 -0
  122. package/dist/core/bang-commands/index.js +12 -0
  123. package/dist/core/bang-commands/registry.d.ts +12 -0
  124. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  125. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  126. package/dist/core/channel-timeline.d.ts +5 -1
  127. package/dist/core/channel-timeline.js +13 -0
  128. package/dist/core/context/index-reconciler.js +5 -2
  129. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  130. package/dist/core/context/policy-index-runner.js +25 -6
  131. package/dist/core/context-builder-calendar.js +10 -2
  132. package/dist/core/context-builder-conversation.d.ts +8 -1
  133. package/dist/core/context-builder-conversation.js +41 -7
  134. package/dist/core/context-builder-yesterday.js +4 -3
  135. package/dist/core/context-builder.d.ts +7 -2
  136. package/dist/core/context-builder.js +193 -5
  137. package/dist/core/context-file-serializer.d.ts +1 -1
  138. package/dist/core/context-file-serializer.js +1 -1
  139. package/dist/core/context-health.js +2 -2
  140. package/dist/core/context-paths.d.ts +11 -1
  141. package/dist/core/context-paths.js +17 -1
  142. package/dist/core/context-validation/prepare-write.js +1 -1
  143. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  144. package/dist/core/context-vault-aliases.d.ts +0 -13
  145. package/dist/core/context-vault-aliases.js +37 -0
  146. package/dist/core/custom-routines.d.ts +99 -0
  147. package/dist/core/custom-routines.js +187 -0
  148. package/dist/core/daemon-api-cli.js +50 -1
  149. package/dist/core/day-boundary.d.ts +46 -0
  150. package/dist/core/day-boundary.js +40 -0
  151. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  152. package/dist/core/dispatcher-activity-scan.js +775 -0
  153. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  154. package/dist/core/dispatcher-error-handling.js +38 -62
  155. package/dist/core/dispatcher-hourly-check.js +6 -1
  156. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  157. package/dist/core/dispatcher-message-handler.js +24 -0
  158. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  159. package/dist/core/dispatcher-morning-routine.js +13 -13
  160. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  161. package/dist/core/dispatcher-result-processor.js +167 -11
  162. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  163. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  164. package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
  165. package/dist/core/dispatcher-scheduled-tasks.js +480 -8
  166. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  167. package/dist/core/dispatcher-task-delivery.js +555 -0
  168. package/dist/core/dispatcher-types.d.ts +48 -9
  169. package/dist/core/dispatcher-types.js +3 -3
  170. package/dist/core/dispatcher.d.ts +112 -31
  171. package/dist/core/dispatcher.js +297 -60
  172. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  173. package/dist/core/drift-effects.js +2 -2
  174. package/dist/core/feedback/consolidation-prep.d.ts +94 -0
  175. package/dist/core/feedback/consolidation-prep.js +254 -0
  176. package/dist/core/feedback/eviction-scorer.d.ts +81 -0
  177. package/dist/core/feedback/eviction-scorer.js +136 -0
  178. package/dist/core/feedback/lesson-format.d.ts +79 -0
  179. package/dist/core/feedback/lesson-format.js +199 -0
  180. package/dist/core/feedback/lesson-injection.d.ts +98 -0
  181. package/dist/core/feedback/lesson-injection.js +174 -0
  182. package/dist/core/feedback/lesson-merge.d.ts +51 -0
  183. package/dist/core/feedback/lesson-merge.js +88 -0
  184. package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
  185. package/dist/core/feedback/lesson-store-overview.js +42 -0
  186. package/dist/core/feedback/promotion-gate.d.ts +69 -0
  187. package/dist/core/feedback/promotion-gate.js +117 -0
  188. package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
  189. package/dist/core/feedback/regeneralization-prep.js +152 -0
  190. package/dist/core/feedback/scope-parser.d.ts +86 -0
  191. package/dist/core/feedback/scope-parser.js +141 -0
  192. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  193. package/dist/core/feedback/self-performance-prep.js +541 -0
  194. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  195. package/dist/core/feedback/tuning-actuator.js +432 -0
  196. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  197. package/dist/core/feedback/tuning-recommender.js +580 -0
  198. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  199. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  200. package/dist/core/health-monitor.d.ts +6 -0
  201. package/dist/core/health-monitor.js +1 -1
  202. package/dist/core/injection-policy.d.ts +83 -1
  203. package/dist/core/injection-policy.js +61 -3
  204. package/dist/core/integration-main-backend.js +4 -0
  205. package/dist/core/management-md.d.ts +2 -2
  206. package/dist/core/management-md.js +51 -13
  207. package/dist/core/morning/orchestrator.d.ts +2 -2
  208. package/dist/core/morning/orchestrator.js +2 -2
  209. package/dist/core/notification-gate.d.ts +64 -0
  210. package/dist/core/notification-gate.js +51 -0
  211. package/dist/core/notification-rate-limit.d.ts +40 -0
  212. package/dist/core/notification-rate-limit.js +50 -0
  213. package/dist/core/policy-files.d.ts +1 -1
  214. package/dist/core/policy-files.js +2 -2
  215. package/dist/core/pre-pass-freshness.d.ts +4 -4
  216. package/dist/core/retention.d.ts +5 -0
  217. package/dist/core/retention.js +20 -4
  218. package/dist/core/review-context.d.ts +1 -1
  219. package/dist/core/review-context.js +10 -5
  220. package/dist/core/roadmap-write-lock.d.ts +2 -1
  221. package/dist/core/roadmap-write-lock.js +15 -10
  222. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  223. package/dist/core/routine-acquisition-plan.js +78 -20
  224. package/dist/core/routine-fetch-window-retry.js +7 -4
  225. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  226. package/dist/core/routine-fetch-window-runner.js +264 -13
  227. package/dist/core/routine-windows.d.ts +2 -2
  228. package/dist/core/routine-windows.js +8 -5
  229. package/dist/core/scheduler.d.ts +175 -16
  230. package/dist/core/scheduler.js +559 -102
  231. package/dist/core/signal-detector.d.ts +51 -1
  232. package/dist/core/signal-detector.js +321 -24
  233. package/dist/core/skills-compiler-denied-tools.js +2 -2
  234. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  235. package/dist/core/skills-compiler-skill-index.js +2 -2
  236. package/dist/core/skills-compiler-variants.d.ts +1 -1
  237. package/dist/core/skills-compiler-variants.js +8 -0
  238. package/dist/core/skills-compiler.d.ts +29 -26
  239. package/dist/core/skills-compiler.js +117 -81
  240. package/dist/core/skills-manifest.d.ts +37 -0
  241. package/dist/core/skills-manifest.js +73 -2
  242. package/dist/core/sleep-inhibitor.d.ts +79 -0
  243. package/dist/core/sleep-inhibitor.js +132 -0
  244. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  245. package/dist/core/slim-system-prompt-loader.js +141 -0
  246. package/dist/core/spawn-gates.d.ts +126 -0
  247. package/dist/core/spawn-gates.js +180 -0
  248. package/dist/core/today-direct-writer.d.ts +60 -14
  249. package/dist/core/today-direct-writer.js +90 -13
  250. package/dist/core/today-write-lock.d.ts +4 -2
  251. package/dist/core/today-write-lock.js +30 -20
  252. package/dist/core/wake-detector.d.ts +55 -0
  253. package/dist/core/wake-detector.js +80 -0
  254. package/dist/core/wiki/compile-lock.d.ts +1 -1
  255. package/dist/core/wiki/compile-lock.js +1 -1
  256. package/dist/core/wiki/wiki-fts.js +13 -6
  257. package/dist/core/workdir.js +15 -6
  258. package/dist/db/activity-scan-signals.d.ts +77 -0
  259. package/dist/db/activity-scan-signals.js +378 -0
  260. package/dist/db/agents-store.d.ts +28 -0
  261. package/dist/db/agents-store.js +62 -0
  262. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  263. package/dist/db/background-task-clarifications-store.js +152 -0
  264. package/dist/db/background-task-store.d.ts +207 -0
  265. package/dist/db/background-task-store.js +380 -0
  266. package/dist/db/browser-history-store.d.ts +39 -6
  267. package/dist/db/browser-history-store.js +51 -7
  268. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  269. package/dist/db/browser-task-clarifications-store.js +35 -5
  270. package/dist/db/browser-task-store.d.ts +3 -0
  271. package/dist/db/browser-task-store.js +29 -4
  272. package/dist/db/deferred-dm.d.ts +86 -0
  273. package/dist/db/deferred-dm.js +199 -0
  274. package/dist/db/feedback-signals-store.d.ts +77 -0
  275. package/dist/db/feedback-signals-store.js +144 -0
  276. package/dist/db/migrations.js +380 -0
  277. package/dist/db/observations.d.ts +2 -2
  278. package/dist/db/observations.js +3 -3
  279. package/dist/db/schema.js +260 -22
  280. package/dist/db/voice-transcripts-store.d.ts +1 -1
  281. package/dist/index.js +86 -29
  282. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  283. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  284. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  285. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  286. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  287. package/dist/observers/delegated-sync-worker.js +10 -10
  288. package/dist/observers/git-delegated-cron.d.ts +1 -1
  289. package/dist/observers/git-delegated-cron.js +2 -2
  290. package/dist/observers/github-poller-classifier.d.ts +3 -3
  291. package/dist/observers/github-poller-classifier.js +3 -3
  292. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  293. package/dist/observers/imminent-event-scheduler.js +1 -1
  294. package/dist/observers/mail-poller.d.ts +1 -0
  295. package/dist/observers/mail-poller.js +42 -3
  296. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  297. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  298. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  299. package/dist/observers/observation-summarizer/worker.js +4 -4
  300. package/dist/observers/obsidian-watcher.d.ts +1 -1
  301. package/dist/observers/obsidian-watcher.js +1 -1
  302. package/dist/safety/agent-write-tracker.d.ts +4 -4
  303. package/dist/safety/agent-write-tracker.js +4 -4
  304. package/dist/safety/always-disallowed.d.ts +1 -1
  305. package/dist/safety/always-disallowed.js +39 -0
  306. package/dist/safety/audit.d.ts +43 -5
  307. package/dist/safety/audit.js +86 -18
  308. package/dist/safety/risk-classifier.d.ts +6 -0
  309. package/dist/safety/risk-classifier.js +97 -18
  310. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  311. package/dist/scheduler/activity-scan-gate.js +132 -0
  312. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  313. package/dist/services/background-task/background-task-budget.js +91 -0
  314. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  315. package/dist/services/background-task/background-task-driver.js +416 -0
  316. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  317. package/dist/services/background-task/background-task-runner.js +673 -0
  318. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  319. package/dist/services/background-task/background-task-tools.js +247 -0
  320. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  321. package/dist/services/background-task/background-task-transition-events.js +54 -0
  322. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  323. package/dist/services/browser-history/automation/egress-denylist.js +34 -8
  324. package/dist/services/browser-history/lifecycle/platform.js +44 -2
  325. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  326. package/dist/services/browser-task/browser-task-runner.js +53 -8
  327. package/dist/services/mcp/probe.js +30 -8
  328. package/dist/services/observations-batch.d.ts +1 -1
  329. package/dist/services/observations-batch.js +2 -2
  330. package/dist/settings/runtime-settings.d.ts +45 -12
  331. package/dist/settings/runtime-settings.js +215 -40
  332. package/dist/settings/settings-store.js +11 -3
  333. package/package.json +4 -4
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Outbound-notification gate — QUIET_HOURS_HARDENING_PLAN.md Phase 1.
3
+ *
4
+ * Single decision function for the API-deps `sendNotification` chokepoint
5
+ * (`bootstrap/api.ts`), closing finding F1: `POST /api/notify` used to
6
+ * bypass quiet hours AND rate limits without the explicit-user-intent
7
+ * justification the other delivery paths encode. The intent rule —
8
+ * "explicit user-chosen time → deliver regardless of quiet hours; ambient
9
+ * autonomous output → suppress/defer" — now holds here too:
10
+ *
11
+ * 1. safety / critical → send immediately (mirrors NotificationManager);
12
+ * 2. inside quiet hours → defer to a `task_type='dm'` agent_schedule row
13
+ * at the quiet-hours edge (durable, coalesced per origin — see
14
+ * `db/deferred-dm.ts`), never silently dropped;
15
+ * 3. outside quiet hours → enforce the same hourly/daily rate limits the
16
+ * proactive path enforces; the live session gets a `rate_limit`
17
+ * verdict it can adapt to (write to today.md instead) rather than a
18
+ * silent queue.
19
+ *
20
+ * Pure composition over covered helpers — keep glue out of bootstrap.
21
+ */
22
+ import type Database from "better-sqlite3";
23
+ /** Safety categories bypass quiet hours and user preferences. Owned here
24
+ * so the NotificationManager and this gate share one list. */
25
+ export declare const SAFETY_CATEGORIES: readonly ["security", "deadline", "error", "critical"];
26
+ export interface OutboundGateConfig {
27
+ quietHoursStart: string;
28
+ quietHoursEnd: string;
29
+ /** IANA tz; empty string falls back to system timezone. */
30
+ timezone: string;
31
+ maxNotificationsPerHour: number;
32
+ maxNotificationsPerDay: number;
33
+ dayBoundaryHour: number;
34
+ }
35
+ export interface OutboundGateParams {
36
+ message: string;
37
+ platforms?: string[] | undefined;
38
+ priority?: string | undefined;
39
+ notificationType?: string | undefined;
40
+ originSessionId?: number | undefined;
41
+ agentId?: string | null | undefined;
42
+ /** Origin marker stamped into the deferred row, e.g. `"api.notify"`. */
43
+ deferredFrom: string;
44
+ }
45
+ export type OutboundGateResult = {
46
+ action: "send";
47
+ } | {
48
+ action: "defer";
49
+ scheduleId: string;
50
+ /** SQLite-format UTC datetime the deferred DM fires at. */
51
+ deliverAfter: string;
52
+ coalesced: boolean;
53
+ } | {
54
+ action: "rate_limit";
55
+ retryAfter: string | null;
56
+ };
57
+ /**
58
+ * Critical priority and safety-tagged notification types deliver
59
+ * immediately — same bypass set as `NotificationManager.isSafetyCategory`
60
+ * ("urgent" accepted defensively; the notify schema only emits
61
+ * critical/high/normal/low).
62
+ */
63
+ export declare function bypassesOutboundGate(priority: string | undefined, notificationType: string | undefined): boolean;
64
+ export declare function gateOutboundNotification(db: Database.Database, config: OutboundGateConfig, params: OutboundGateParams, now?: Date): OutboundGateResult;
@@ -0,0 +1,51 @@
1
+ import { deferDmToQuietHoursEnd } from "../db/deferred-dm.js";
2
+ import { evaluateNotificationRateLimit, } from "./notification-rate-limit.js";
3
+ /** Safety categories bypass quiet hours and user preferences. Owned here
4
+ * so the NotificationManager and this gate share one list. */
5
+ export const SAFETY_CATEGORIES = [
6
+ "security",
7
+ "deadline",
8
+ "error",
9
+ "critical",
10
+ ];
11
+ /**
12
+ * Critical priority and safety-tagged notification types deliver
13
+ * immediately — same bypass set as `NotificationManager.isSafetyCategory`
14
+ * ("urgent" accepted defensively; the notify schema only emits
15
+ * critical/high/normal/low).
16
+ */
17
+ export function bypassesOutboundGate(priority, notificationType) {
18
+ if (priority === "critical" || priority === "urgent")
19
+ return true;
20
+ return (notificationType !== undefined &&
21
+ SAFETY_CATEGORIES.includes(notificationType));
22
+ }
23
+ export function gateOutboundNotification(db, config, params, now = new Date()) {
24
+ if (bypassesOutboundGate(params.priority, params.notificationType)) {
25
+ return { action: "send" };
26
+ }
27
+ const deferred = deferDmToQuietHoursEnd(db, {
28
+ start: config.quietHoursStart,
29
+ end: config.quietHoursEnd,
30
+ timezone: config.timezone || undefined,
31
+ }, {
32
+ message: params.message,
33
+ platforms: params.platforms,
34
+ deferredFrom: params.deferredFrom,
35
+ originSessionId: params.originSessionId,
36
+ agentId: params.agentId,
37
+ }, now);
38
+ if (deferred !== null) {
39
+ return { action: "defer", ...deferred };
40
+ }
41
+ const rateLimit = evaluateNotificationRateLimit(db, {
42
+ maxNotificationsPerHour: config.maxNotificationsPerHour,
43
+ maxNotificationsPerDay: config.maxNotificationsPerDay,
44
+ timezone: config.timezone,
45
+ dayBoundaryHour: config.dayBoundaryHour,
46
+ }, now);
47
+ if (rateLimit.limited) {
48
+ return { action: "rate_limit", retryAfter: rateLimit.retryAfter };
49
+ }
50
+ return { action: "send" };
51
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Outbound-notification rate-limit evaluation — pure(ish) helper shared by
3
+ * NotificationManager (proactive suppression) and the `/api/notify` gate
4
+ * (QUIET_HOURS_HARDENING_PLAN.md Phase 1). Extracted so the two call sites
5
+ * cannot drift on the counting semantics: distinct dispatches, delivered
6
+ * only, `message.received` replies excluded, hourly window + agent-day
7
+ * window both enforced.
8
+ *
9
+ * 100% covered. NotificationManager itself stays excluded from the
10
+ * coverage gate as I/O-heavy; this helper is the pure leg it shares with
11
+ * the notify-route gate.
12
+ */
13
+ import type Database from "better-sqlite3";
14
+ export interface NotificationRateLimitOptions {
15
+ maxNotificationsPerHour: number;
16
+ maxNotificationsPerDay: number;
17
+ /** IANA tz; empty/undefined falls back to system timezone. */
18
+ timezone?: string | undefined;
19
+ /** Agent day boundary hour (default config: 4). */
20
+ dayBoundaryHour: number;
21
+ }
22
+ export interface NotificationRateLimitState {
23
+ limited: boolean;
24
+ /**
25
+ * SQLite-format UTC datetime (`YYYY-MM-DD HH:MM:SS`) when a retry could
26
+ * succeed, or `null` when not limited. Hourly limit → the moment the
27
+ * oldest delivery in the trailing hour ages out of the window; daily
28
+ * limit → the agent-day end boundary. Advisory — the caller's retry can
29
+ * still lose to a concurrent delivery.
30
+ */
31
+ retryAfter: string | null;
32
+ }
33
+ /**
34
+ * Count semantics mirror the pre-extraction `NotificationManager`
35
+ * implementation byte-for-byte: a multi-channel dispatch counts once
36
+ * (DISTINCT on dispatch_id, falling back to the row id for legacy rows
37
+ * with an empty dispatch_id), only `delivered` rows count, and
38
+ * `message.received` reply forwards never count against proactive budget.
39
+ */
40
+ export declare function evaluateNotificationRateLimit(db: Database.Database, opts: NotificationRateLimitOptions, now?: Date): NotificationRateLimitState;
@@ -0,0 +1,50 @@
1
+ import { formatSqliteDatetime, getAgentDayBoundsUtc } from "@aitne/shared";
2
+ const HOUR_MS = 60 * 60 * 1000;
3
+ /**
4
+ * Count semantics mirror the pre-extraction `NotificationManager`
5
+ * implementation byte-for-byte: a multi-channel dispatch counts once
6
+ * (DISTINCT on dispatch_id, falling back to the row id for legacy rows
7
+ * with an empty dispatch_id), only `delivered` rows count, and
8
+ * `message.received` reply forwards never count against proactive budget.
9
+ */
10
+ export function evaluateNotificationRateLimit(db, opts, now = new Date()) {
11
+ const hourFloor = formatSqliteDatetime(new Date(now.getTime() - HOUR_MS));
12
+ const hourly = db
13
+ .prepare(`SELECT COUNT(DISTINCT CASE
14
+ WHEN dispatch_id != '' THEN dispatch_id
15
+ ELSE CAST(id AS TEXT)
16
+ END) as cnt,
17
+ MIN(created_at) as oldest
18
+ FROM notification_log
19
+ WHERE status = 'delivered'
20
+ AND COALESCE(notification_type, '') != 'message.received'
21
+ AND created_at > ?`)
22
+ .get(hourFloor);
23
+ if (hourly.cnt >= opts.maxNotificationsPerHour) {
24
+ // The window opens when the oldest in-window delivery ages past 1 h.
25
+ // `oldest` is non-null whenever cnt > 0; the cnt===0 ∧ limit<=0 corner
26
+ // (a zero/negative configured cap) degrades to "retry at now + 1 h".
27
+ const oldestMs = hourly.oldest
28
+ ? new Date(`${hourly.oldest.replace(" ", "T")}Z`).getTime()
29
+ : now.getTime();
30
+ return {
31
+ limited: true,
32
+ retryAfter: formatSqliteDatetime(new Date(oldestMs + HOUR_MS)),
33
+ };
34
+ }
35
+ const bounds = getAgentDayBoundsUtc(opts.timezone || undefined, opts.dayBoundaryHour, now);
36
+ const daily = db
37
+ .prepare(`SELECT COUNT(DISTINCT CASE
38
+ WHEN dispatch_id != '' THEN dispatch_id
39
+ ELSE CAST(id AS TEXT)
40
+ END) as cnt
41
+ FROM notification_log
42
+ WHERE status = 'delivered'
43
+ AND COALESCE(notification_type, '') != 'message.received'
44
+ AND created_at >= ? AND created_at < ?`)
45
+ .get(bounds.start, bounds.end);
46
+ if (daily.cnt >= opts.maxNotificationsPerDay) {
47
+ return { limited: true, retryAfter: bounds.end };
48
+ }
49
+ return { limited: false, retryAfter: null };
50
+ }
@@ -31,7 +31,7 @@ export declare function createPromptInjectionBudget(maxBytes?: number): PromptIn
31
31
  * - `policies/mcp.md` — MCP usage rules (B-003; inject when any MCP enabled)
32
32
  * - `policies/journal-format.md` — daily journal format (morning routine)
33
33
  * - `policies/redaction.md` — secret patterns (all flows)
34
- * - `policies/routines/hourly.md` — hourly check list
34
+ * - `policies/routines/activity-scan.md` — activity scan list
35
35
  * - `policies/routines/morning.md` — 04:00 checks (morning routine)
36
36
  * - `policies/routines/custom/<slug>.md` — per-custom-routine check list
37
37
  *
@@ -51,8 +51,8 @@ export const POLICY_FILE_REGISTRY = {
51
51
  injectIf: (ctx) => ctx.flags?.mcpEnabled === true,
52
52
  },
53
53
  ],
54
- "routine.hourly_check": [
55
- { path: CONTEXT_RELATIVE_PATHS.routines.hourly, label: "Hourly checks" },
54
+ "routine.activity_scan": [
55
+ { path: CONTEXT_RELATIVE_PATHS.routines.activityScan, label: "Activity scans" },
56
56
  ],
57
57
  "routine.morning_routine": [
58
58
  {
@@ -1,17 +1,17 @@
1
1
  /**
2
- * Freshness-window helpers for the hourly_check pre-pass harvester
2
+ * Freshness-window helpers for the activity_scan pre-pass harvester
3
3
  * (HOURLY_CHECK_GATE_REDESIGN_PLAN.md §3.4).
4
4
  *
5
5
  * The pre-pass fetcher writes a `runtime_state` row keyed by integration
6
- * after every successful per-integration completion. The hourly_check
6
+ * after every successful per-integration completion. The activity_scan
7
7
  * coordinator's `harvestForGate` reads the row to decide whether the
8
8
  * window is fresh enough to skip pre-pass on this tick.
9
9
  *
10
10
  * The key prefix is intentionally shared across every routine that
11
- * spawns `routine.fetch_window` (morning_routine, hourly_check,
11
+ * spawns `routine.fetch_window` (morning_routine, activity_scan,
12
12
  * evening_review, weekly_review, today_refresh). Sharing means a
13
13
  * morning_routine that just ran at 04:00 suppresses the 05:00
14
- * hourly_check pre-pass — exactly what we want to avoid double-fetching.
14
+ * activity_scan pre-pass — exactly what we want to avoid double-fetching.
15
15
  *
16
16
  * Module is intentionally trivial — separated so both the runner (writer)
17
17
  * and the coordinator (reader) can depend on a single string-builder
@@ -90,6 +90,11 @@ export interface RetentionResult {
90
90
  * rows are never counted here; boot-recovery owns them.
91
91
  */
92
92
  browserTask: number;
93
+ /**
94
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §6 — terminal `background_task` rows
95
+ * pruned during this sweep (children cascade via FK).
96
+ */
97
+ backgroundTask: number;
93
98
  /**
94
99
  * BROWSER_TASK_REDESIGN_PLAN.md §5 — pending lite-final-confirm
95
100
  * tokens past their 5-min TTL flipped to `expired` during this sweep
@@ -6,6 +6,7 @@ import { expireStalePurchaseTokens, scrubRotatedPurchaseTokens, sweepOrphanedCon
6
6
  import { deletePurchaseRepliesOlderThan } from "../db/browser-automation-purchase-replies-store.js";
7
7
  import { deleteWorkflowRunsOlderThan } from "../db/browser-automation-store.js";
8
8
  import { deleteTerminalBrowserTasksOlderThan } from "../db/browser-task-store.js";
9
+ import { deleteTerminalBackgroundTasksOlderThan } from "../db/background-task-store.js";
9
10
  import { expireStaleLiteFinalConfirmTokens, scrubRotatedLiteFinalConfirmTokens, } from "../db/browser-task-final-confirm-tokens-store.js";
10
11
  import { cleanupConsumedObservations, getStalePendingObservationStats, } from "../db/observations.js";
11
12
  import { pruneOldMcpToolCalls } from "../services/mcp/tool-audit.js";
@@ -61,6 +62,16 @@ const RETENTION_DAYS = {
61
62
  * sweep itself is broken and we should not paper over it.
62
63
  */
63
64
  browserTask: TRACE_RETENTION_DAYS,
65
+ /**
66
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §6 — terminal `background_task` rows
67
+ * age out at 30 days. Unlike browser_task there are no trace
68
+ * screenshots to keep in sync; 30 days keeps a month of completed-task
69
+ * history so a late "what did that find?" follow-up can still
70
+ * `GET /api/background-task/:id`. Children
71
+ * (`background_task_clarifications`) cascade via FK. Non-terminal rows
72
+ * are NEVER deleted — boot re-dispatch owns them.
73
+ */
74
+ backgroundTask: 30,
64
75
  /**
65
76
  * BROWSER_TASK_REDESIGN_PLAN.md §14.11 Q#6 — lite-final-confirm tokens
66
77
  * carry the same `!~xxxxxxxx` shape as B-4 purchase tokens. Mirror the
@@ -79,7 +90,7 @@ const RETENTION_DAYS = {
79
90
  skillCurationRunningMaxHours: 24,
80
91
  tempFiles: 1,
81
92
  /**
82
- * Pending observations are NEVER deleted by retention (the hourly_check
93
+ * Pending observations are NEVER deleted by retention (the activity_scan
83
94
  * dispatcher owns consumption). After this many days unconsumed, retention
84
95
  * logs a warning so the operator notices a stalled pipeline.
85
96
  */
@@ -193,6 +204,7 @@ export function runRetentionCleanup(db, config) {
193
204
  browserAutomationPurchaseTokensScrubbed: 0,
194
205
  browserAutomationPurchaseRepliesDeleted: 0,
195
206
  browserTask: 0,
207
+ backgroundTask: 0,
196
208
  browserTaskFinalConfirmTokensExpired: 0,
197
209
  browserTaskFinalConfirmTokensScrubbed: 0,
198
210
  ftsOptimized: false,
@@ -232,6 +244,7 @@ export function runRetentionCleanup(db, config) {
232
244
  browserAutomationPurchaseTokensScrubbed: 0,
233
245
  browserAutomationPurchaseRepliesDeleted: 0,
234
246
  browserTask: 0,
247
+ backgroundTask: 0,
235
248
  browserTaskFinalConfirmTokensExpired: 0,
236
249
  browserTaskFinalConfirmTokensScrubbed: 0,
237
250
  };
@@ -380,10 +393,12 @@ export function runRetentionCleanup(db, config) {
380
393
  scrubRotatedLiteFinalConfirmTokens(db, tokenScrubCutoff);
381
394
  const browserTaskCutoff = now - RETENTION_DAYS.browserTask * 86_400_000;
382
395
  counts.browserTask = deleteTerminalBrowserTasksOlderThan(db, browserTaskCutoff);
396
+ const backgroundTaskCutoff = now - RETENTION_DAYS.backgroundTask * 86_400_000;
397
+ counts.backgroundTask = deleteTerminalBackgroundTasksOlderThan(db, backgroundTaskCutoff);
383
398
  }
384
399
  catch (err) {
385
400
  /* c8 ignore next 5 */
386
- logger.warn({ err }, "browser_task retention sweep skipped (tables missing)");
401
+ logger.warn({ err }, "browser_task / background_task retention sweep skipped (tables missing)");
387
402
  }
388
403
  })();
389
404
  // Transaction committed — safe to copy counts into result.
@@ -416,6 +431,7 @@ export function runRetentionCleanup(db, config) {
416
431
  result.imminentEventNotifications = counts.imminentEventNotifications;
417
432
  result.browserAutomationWorkflows = counts.browserAutomationWorkflows;
418
433
  result.browserTask = counts.browserTask;
434
+ result.backgroundTask = counts.backgroundTask;
419
435
  result.browserTaskFinalConfirmTokensExpired =
420
436
  counts.browserTaskFinalConfirmTokensExpired;
421
437
  result.browserTaskFinalConfirmTokensScrubbed =
@@ -433,7 +449,7 @@ export function runRetentionCleanup(db, config) {
433
449
  result.attachmentOrphanRows = attachmentCleanup.orphanRows;
434
450
  result.attachmentDanglingRows = attachmentCleanup.danglingRows;
435
451
  result.attachmentUntrackedDirs = attachmentCleanup.untrackedDirs;
436
- // Surface stale pending observations so a stalled hourly_check pipeline
452
+ // Surface stale pending observations so a stalled activity_scan pipeline
437
453
  // becomes visible in daemon logs. Pending rows are intentionally not
438
454
  // deleted (see RETENTION_DAYS.stalePendingObservationsWarn comment).
439
455
  const stalePending = getStalePendingObservationStats(db, RETENTION_DAYS.stalePendingObservationsWarn);
@@ -442,7 +458,7 @@ export function runRetentionCleanup(db, config) {
442
458
  stalePendingCount: stalePending.count,
443
459
  oldestObservedAt: stalePending.oldestObservedAt,
444
460
  thresholdDays: RETENTION_DAYS.stalePendingObservationsWarn,
445
- }, "Stale pending observations detected — hourly_check may be skipping or stalled");
461
+ }, "Stale pending observations detected — activity_scan may be skipping or stalled");
446
462
  }
447
463
  // ── FTS5 segment optimization ──
448
464
  //
@@ -1,5 +1,5 @@
1
1
  import { type PromptInjectionBudget } from "./policy-files.js";
2
- export type ReviewFlowSlug = "hourly" | "morning" | "evening" | "weekly" | "monthly" | "roadmap";
2
+ export type ReviewFlowSlug = "activity-scan" | "morning" | "evening" | "weekly" | "monthly" | "roadmap";
3
3
  interface ReviewFlowConfig {
4
4
  flow: ReviewFlowSlug;
5
5
  dossierPath: string;
@@ -6,10 +6,10 @@ import { POLICY_FILE_MAX_BYTES, createPromptInjectionBudget, } from "./policy-fi
6
6
  import { createLogger } from "../logging.js";
7
7
  const logger = createLogger("review-context");
8
8
  const REVIEW_FLOW_BY_PROCESS_KEY = {
9
- "routine.hourly_check": {
10
- flow: "hourly",
11
- dossierPath: dossierPath("hourly"),
12
- dossierLabel: "Hourly dossier",
9
+ "routine.activity_scan": {
10
+ flow: "activity-scan",
11
+ dossierPath: dossierPath("activity-scan"),
12
+ dossierLabel: "Activity scan dossier",
13
13
  },
14
14
  "routine.morning_routine": {
15
15
  flow: "morning",
@@ -295,7 +295,12 @@ function reviewFlowsMatch(raw, flow) {
295
295
  .split(/[,;/\s]+/)
296
296
  .map((token) => token.trim())
297
297
  .filter(Boolean);
298
- return tokens.includes(flow);
298
+ if (tokens.includes(flow))
299
+ return true;
300
+ // v0.1.10 → v0.1.11 rename: user-vault `_index.md` rows written before the
301
+ // rename tag the activity-scan flow as "hourly". Accept the legacy token
302
+ // until the index reconciler has naturally rewritten those rows.
303
+ return flow === "activity-scan" && tokens.includes("hourly");
299
304
  }
300
305
  function sanitizeContextIndexPath(rawPath) {
301
306
  const path = rawPath.trim().replace(/^\.\//, "");
@@ -31,8 +31,9 @@ export interface RoadmapWriteLockManager {
31
31
  export declare class InMemoryRoadmapWriteLockManager implements RoadmapWriteLockManager {
32
32
  private readonly timeoutMs;
33
33
  private holder;
34
- private timer;
34
+ private expiresAtMs;
35
35
  constructor(timeoutMs: number);
36
+ private expireIfStale;
36
37
  acquire(): {
37
38
  ok: true;
38
39
  lockId: string;
@@ -4,44 +4,49 @@ const logger = createLogger("roadmap-write-lock");
4
4
  export class InMemoryRoadmapWriteLockManager {
5
5
  timeoutMs;
6
6
  holder = null;
7
- timer = null;
7
+ expiresAtMs = 0;
8
8
  constructor(timeoutMs) {
9
9
  this.timeoutMs = timeoutMs;
10
10
  }
11
+ // Wall-clock lazy expiry — mirrors today-write-lock.ts. A setTimeout
12
+ // here would freeze across machine sleep (monotonic clock) and hold
13
+ // the lock long past its TTL after wake.
14
+ expireIfStale() {
15
+ if (this.holder && Date.now() >= this.expiresAtMs) {
16
+ logger.warn({ lockId: this.holder }, "Roadmap write lock expired by timeout");
17
+ this.holder = null;
18
+ }
19
+ }
11
20
  acquire() {
21
+ this.expireIfStale();
12
22
  if (this.holder) {
13
23
  logger.debug({ existingHolder: this.holder }, "Lock acquire rejected — already held");
14
24
  return { ok: false, holder: this.holder };
15
25
  }
16
26
  const lockId = randomUUID();
17
27
  this.holder = lockId;
18
- this.timer = setTimeout(() => {
19
- logger.warn({ lockId: this.holder }, "Roadmap write lock expired by timeout");
20
- this.holder = null;
21
- this.timer = null;
22
- }, this.timeoutMs);
28
+ this.expiresAtMs = Date.now() + this.timeoutMs;
23
29
  logger.debug({ lockId }, "Roadmap write lock acquired");
24
30
  return { ok: true, lockId };
25
31
  }
26
32
  release(lockId) {
33
+ this.expireIfStale();
27
34
  if (!this.holder || this.holder !== lockId) {
28
35
  return false;
29
36
  }
30
37
  this.holder = null;
31
- if (this.timer) {
32
- clearTimeout(this.timer);
33
- this.timer = null;
34
- }
35
38
  logger.debug({ lockId }, "Roadmap write lock released");
36
39
  return true;
37
40
  }
38
41
  isHeldBy(lockId) {
42
+ this.expireIfStale();
39
43
  if (!this.holder) {
40
44
  return false;
41
45
  }
42
46
  return this.holder === lockId;
43
47
  }
44
48
  getHolder() {
49
+ this.expireIfStale();
45
50
  return this.holder;
46
51
  }
47
52
  }
@@ -27,12 +27,45 @@
27
27
  * ```
28
28
  */
29
29
  import { type BackendId, type IntegrationKey, type IntegrationState } from "@aitne/shared";
30
- import { type RoutineWindowKey } from "./routine-windows.js";
30
+ import { type RoutineWindowKey, type WindowSymbol } from "./routine-windows.js";
31
31
  /**
32
32
  * Per-mode predicate string the partial filters on. Matches the predicate
33
33
  * names used by `applyIntegrationModeFilter` in `@aitne/shared`.
34
34
  */
35
35
  export type AcquisitionFetchMode = "direct" | "delegated-same" | "delegated-cross" | "native";
36
+ /**
37
+ * Why a (window × integration) cell was dropped at plan-assembly time —
38
+ * PREPASS_COST_REDUCTION_PLAN.md N3. Before N3 these drops vanished
39
+ * without a trace; the runner now writes one `skipped` audit row per
40
+ * (integration × reason) group so the deferred no-surface streak skip
41
+ * (R5) and the empty-window backoff (R4) can be sized from data.
42
+ *
43
+ * - `no_state` — integration absent from the `readIntegrations` snapshot.
44
+ * - `no_binding` — delegated/native mode with a null backend binding.
45
+ * - `disabled` — integration mode is explicitly `disabled`.
46
+ * - `unknown_mode` — unrecognized mode string (forward-compat guard).
47
+ * - `no_window_query` — `WINDOW_QUERIES` has no cell for the
48
+ * (window, integration, mode) tuple where one was expected — a
49
+ * genuine catalog hole.
50
+ * - `no_accounts` — direct-mode per-account fan-out with zero active
51
+ * accounts for the integration.
52
+ * - `no_fetch_targets` — Notion routine fetches require an explicit
53
+ * user allowlist so the pre-pass cannot scan the whole workspace.
54
+ * - `direct_inline_prefetch` — the catalog *deliberately* omits the
55
+ * `direct` cell because the daemon serves that data inline
56
+ * (ContextBuilder pre-fetch / REST route) and a pre-pass row would
57
+ * double-fetch (cf. `cal_morning_7d` in routine-windows.ts). Working
58
+ * as designed, so the runner does NOT write an audit row for it —
59
+ * counting it as a drop would pollute the R4/R5 sizing data the N3
60
+ * audit stream exists to provide.
61
+ */
62
+ export type AcquisitionPlanDropReason = "no_state" | "no_binding" | "disabled" | "unknown_mode" | "no_window_query" | "no_accounts" | "no_fetch_targets" | "direct_inline_prefetch";
63
+ /** One dropped (window × integration) cell. */
64
+ export interface AcquisitionPlanDrop {
65
+ integration: IntegrationKey;
66
+ window: WindowSymbol;
67
+ reason: AcquisitionPlanDropReason;
68
+ }
36
69
  export interface AcquisitionAccount {
37
70
  /**
38
71
  * Integration key the account belongs to. Today only `gmail` and
@@ -215,3 +248,16 @@ export interface AcquisitionSubPlan {
215
248
  * `scoped="<key>"` attribute and the partition itself.
216
249
  */
217
250
  export declare function splitAcquisitionPlanByIntegration(input: BuildAcquisitionPlanInput): readonly AcquisitionSubPlan[];
251
+ /**
252
+ * `splitAcquisitionPlanByIntegration` + the drop trace —
253
+ * PREPASS_COST_REDUCTION_PLAN.md N3. The fan-out runner consumes this
254
+ * variant so every (window × integration) cell dropped at plan-assembly
255
+ * time can be surfaced as a `skipped` audit row instead of vanishing.
256
+ * Same purity / ordering / row-preservation contract as the wrapper
257
+ * above.
258
+ */
259
+ export interface AcquisitionPlanAssembly {
260
+ subPlans: readonly AcquisitionSubPlan[];
261
+ drops: readonly AcquisitionPlanDrop[];
262
+ }
263
+ export declare function buildAcquisitionPlanAssembly(input: BuildAcquisitionPlanInput): AcquisitionPlanAssembly;