@remit/backend 0.0.142 → 0.0.144

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 (269) hide show
  1. package/dist/dev-server/content-handler.d.ts +38 -0
  2. package/dist/dev-server/content-handler.js +46 -0
  3. package/dist/src/api.d.ts +7 -0
  4. package/dist/src/api.js +144 -0
  5. package/dist/src/auth.d.ts +30 -0
  6. package/dist/src/auth.js +73 -0
  7. package/dist/src/config/msoauth.d.ts +30 -0
  8. package/dist/src/config/msoauth.js +30 -0
  9. package/{src/data-backend.ts → dist/src/data-backend.d.ts} +2 -4
  10. package/dist/src/data-backend.js +12 -0
  11. package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
  12. package/dist/src/derive/autoMoved.js +49 -0
  13. package/dist/src/derive/contentSignature.d.ts +41 -0
  14. package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +27 -72
  15. package/dist/src/derive/contentUrl.d.ts +46 -0
  16. package/dist/src/derive/contentUrl.js +46 -0
  17. package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
  18. package/dist/src/derive/deriveMuted.js +7 -0
  19. package/dist/src/derive/enrichThreadRows.d.ts +118 -0
  20. package/dist/src/derive/enrichThreadRows.js +151 -0
  21. package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
  22. package/dist/src/derive/filterThreadCriteria.js +24 -0
  23. package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
  24. package/dist/src/derive/pendingMoveCounts.js +53 -0
  25. package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
  26. package/dist/src/derive/senderTrust.js +19 -0
  27. package/dist/src/error.d.ts +21 -0
  28. package/dist/src/error.js +85 -0
  29. package/dist/src/handlers/account-guards.d.ts +36 -0
  30. package/dist/src/handlers/account-guards.js +107 -0
  31. package/dist/src/handlers/account-oauth.d.ts +14 -0
  32. package/dist/src/handlers/account-oauth.js +387 -0
  33. package/dist/src/handlers/account-overrides.d.ts +53 -0
  34. package/dist/src/handlers/account-overrides.js +233 -0
  35. package/dist/src/handlers/account-signature.d.ts +28 -0
  36. package/dist/src/handlers/account-signature.js +85 -0
  37. package/dist/src/handlers/account.d.ts +6 -0
  38. package/dist/src/handlers/account.js +350 -0
  39. package/dist/src/handlers/address.d.ts +35 -0
  40. package/dist/src/handlers/address.js +141 -0
  41. package/dist/src/handlers/calendar-event.d.ts +70 -0
  42. package/dist/src/handlers/calendar-event.js +425 -0
  43. package/dist/src/handlers/calendar-feed.d.ts +47 -0
  44. package/dist/src/handlers/calendar-feed.js +192 -0
  45. package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
  46. package/dist/src/handlers/calendar-suggestion.js +212 -0
  47. package/dist/src/handlers/calendar.d.ts +116 -0
  48. package/dist/src/handlers/calendar.js +330 -0
  49. package/dist/src/handlers/config.d.ts +43 -0
  50. package/dist/src/handlers/config.js +176 -0
  51. package/dist/src/handlers/ensure-account-config.d.ts +9 -0
  52. package/dist/src/handlers/ensure-account-config.js +27 -0
  53. package/dist/src/handlers/filter.d.ts +3 -0
  54. package/dist/src/handlers/filter.js +103 -0
  55. package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
  56. package/dist/src/handlers/folder-role-appointments.js +168 -0
  57. package/dist/src/handlers/folder-role-labels.d.ts +26 -0
  58. package/dist/src/handlers/folder-role-labels.js +40 -0
  59. package/dist/src/handlers/folder-role.d.ts +9 -0
  60. package/dist/src/handlers/folder-role.js +40 -0
  61. package/dist/src/handlers/index.d.ts +2 -0
  62. package/dist/src/handlers/index.js +60 -0
  63. package/dist/src/handlers/label.d.ts +43 -0
  64. package/dist/src/handlers/label.js +114 -0
  65. package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
  66. package/dist/src/handlers/mailbox-bindings.js +61 -0
  67. package/dist/src/handlers/mailbox.d.ts +23 -0
  68. package/dist/src/handlers/mailbox.js +219 -0
  69. package/dist/src/handlers/me.d.ts +2 -0
  70. package/dist/src/handlers/me.js +138 -0
  71. package/dist/src/handlers/message.d.ts +84 -0
  72. package/dist/src/handlers/message.js +607 -0
  73. package/dist/src/handlers/organize.d.ts +10 -0
  74. package/dist/src/handlers/organize.js +112 -0
  75. package/dist/src/handlers/outbox-attachment.d.ts +23 -0
  76. package/dist/src/handlers/outbox-attachment.js +81 -0
  77. package/dist/src/handlers/outbox.d.ts +4 -0
  78. package/dist/src/handlers/outbox.js +144 -0
  79. package/dist/src/handlers/sync-progress.d.ts +46 -0
  80. package/dist/src/handlers/sync-progress.js +63 -0
  81. package/dist/src/handlers/sync.d.ts +28 -0
  82. package/dist/src/handlers/sync.js +82 -0
  83. package/dist/src/handlers/system-update.d.ts +2 -0
  84. package/dist/src/handlers/system-update.js +172 -0
  85. package/dist/src/handlers/thread.d.ts +148 -0
  86. package/dist/src/handlers/thread.js +233 -0
  87. package/dist/src/handlers/unified-threads.d.ts +247 -0
  88. package/dist/src/handlers/unified-threads.js +409 -0
  89. package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
  90. package/dist/src/handlers/vip-suggestions.js +16 -0
  91. package/dist/src/index.d.ts +2 -0
  92. package/dist/src/index.js +37 -0
  93. package/{src/json.ts → dist/src/json.d.ts} +1 -4
  94. package/dist/src/json.js +10 -0
  95. package/dist/src/jwt-auth.d.ts +18 -0
  96. package/dist/src/jwt-auth.js +97 -0
  97. package/dist/src/request-context.d.ts +9 -0
  98. package/dist/src/request-context.js +22 -0
  99. package/dist/src/request.d.ts +3 -0
  100. package/dist/src/request.js +7 -0
  101. package/dist/src/response.d.ts +27 -0
  102. package/dist/src/response.js +87 -0
  103. package/dist/src/service/account-deletion.d.ts +22 -0
  104. package/dist/src/service/account-deletion.js +39 -0
  105. package/dist/src/service/compose-sqlite.d.ts +2 -0
  106. package/dist/src/service/compose-sqlite.js +11 -0
  107. package/dist/src/service/create-remit-client.d.ts +95 -0
  108. package/dist/src/service/create-remit-client.js +207 -0
  109. package/dist/src/service/data-client.d.ts +12 -0
  110. package/dist/src/service/data-client.js +44 -0
  111. package/dist/src/service/export-identity.d.ts +3 -0
  112. package/dist/src/service/export-identity.js +35 -0
  113. package/dist/src/service/fire-and-forget.d.ts +54 -0
  114. package/dist/src/service/fire-and-forget.js +38 -0
  115. package/dist/src/service/organize.d.ts +184 -0
  116. package/dist/src/service/organize.js +384 -0
  117. package/dist/src/service/refusal.d.ts +4 -0
  118. package/dist/src/service/refusal.js +20 -0
  119. package/dist/src/service/sender-category.d.ts +51 -0
  120. package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
  121. package/dist/src/service/sqs.d.ts +1 -0
  122. package/{src/service/sqs.ts → dist/src/service/sqs.js} +0 -1
  123. package/dist/src/service/trigger-sync.d.ts +51 -0
  124. package/dist/src/service/trigger-sync.js +39 -0
  125. package/dist/src/types.d.ts +49 -0
  126. package/dist/src/types.js +1 -0
  127. package/package.json +113 -98
  128. package/.env.test +0 -4
  129. package/dev-server/content-auth.test.ts +0 -113
  130. package/dev-server/content-auth.ts +0 -54
  131. package/dev-server/content-handler.test.ts +0 -105
  132. package/dev-server/content-handler.ts +0 -79
  133. package/dev-server/content-path.test.ts +0 -37
  134. package/dev-server/content-path.ts +0 -27
  135. package/dev-server/cors.test.ts +0 -48
  136. package/dev-server/cors.ts +0 -25
  137. package/dev-server/keep-alive.test.ts +0 -24
  138. package/dev-server/lambda-helpers.ts +0 -69
  139. package/dev-server/log-output.test.ts +0 -173
  140. package/dev-server/oauth-callback-route.test.ts +0 -66
  141. package/dev-server/relational-health.test.ts +0 -83
  142. package/dev-server/relational-health.ts +0 -39
  143. package/dev-server/server.ts +0 -384
  144. package/dev-server/sync-age.test.ts +0 -91
  145. package/dev-server/sync-age.ts +0 -55
  146. package/dev-server/upload-handler.ts +0 -132
  147. package/dev-server/upload-route.test.ts +0 -196
  148. package/scripts/backfill-classification.ts +0 -91
  149. package/scripts/backfill-list-id.ts +0 -83
  150. package/scripts/config-save.ts +0 -128
  151. package/src/auth.test.ts +0 -12
  152. package/src/auth.ts +0 -92
  153. package/src/config/msoauth.test.ts +0 -48
  154. package/src/config/msoauth.ts +0 -48
  155. package/src/data-backend.test.ts +0 -23
  156. package/src/derive/autoMoved.test.ts +0 -104
  157. package/src/derive/contentSignature.test.ts +0 -161
  158. package/src/derive/contentUrl.test.ts +0 -172
  159. package/src/derive/contentUrl.ts +0 -86
  160. package/src/derive/deriveMuted.test.ts +0 -34
  161. package/src/derive/enrichThreadRows.test.ts +0 -360
  162. package/src/derive/enrichThreadRows.ts +0 -234
  163. package/src/derive/filterThreadCriteria.test.ts +0 -126
  164. package/src/derive/filterThreadCriteria.ts +0 -51
  165. package/src/derive/senderTrust.test.ts +0 -67
  166. package/src/error.test.ts +0 -201
  167. package/src/error.ts +0 -123
  168. package/src/handlers/account-guards.test.ts +0 -67
  169. package/src/handlers/account-guards.ts +0 -169
  170. package/src/handlers/account-oauth-callback.test.ts +0 -155
  171. package/src/handlers/account-oauth-consent.test.ts +0 -336
  172. package/src/handlers/account-oauth-start.test.ts +0 -191
  173. package/src/handlers/account-oauth.test.ts +0 -439
  174. package/src/handlers/account-oauth.ts +0 -635
  175. package/src/handlers/account-overrides.test.ts +0 -88
  176. package/src/handlers/account-overrides.ts +0 -323
  177. package/src/handlers/account-ownership.ts +0 -25
  178. package/src/handlers/account-signature.ts +0 -117
  179. package/src/handlers/account-synced-services.test.ts +0 -245
  180. package/src/handlers/account.ts +0 -552
  181. package/src/handlers/address.test.ts +0 -381
  182. package/src/handlers/address.ts +0 -200
  183. package/src/handlers/calendar-event.test.ts +0 -685
  184. package/src/handlers/calendar-event.ts +0 -738
  185. package/src/handlers/calendar-feed-validators.test.ts +0 -228
  186. package/src/handlers/calendar-feed.test.ts +0 -439
  187. package/src/handlers/calendar-feed.ts +0 -291
  188. package/src/handlers/calendar-sqlite-fixture.ts +0 -36
  189. package/src/handlers/calendar-suggestion.test.ts +0 -1269
  190. package/src/handlers/calendar-suggestion.ts +0 -425
  191. package/src/handlers/calendar.test.ts +0 -1080
  192. package/src/handlers/calendar.ts +0 -517
  193. package/src/handlers/config.test.ts +0 -245
  194. package/src/handlers/config.ts +0 -332
  195. package/src/handlers/ensure-account-config.ts +0 -30
  196. package/src/handlers/filter.test.ts +0 -644
  197. package/src/handlers/filter.ts +0 -518
  198. package/src/handlers/folder-role-appointments.test.ts +0 -438
  199. package/src/handlers/folder-role-appointments.ts +0 -249
  200. package/src/handlers/folder-role-labels.ts +0 -63
  201. package/src/handlers/folder-role.test.ts +0 -209
  202. package/src/handlers/folder-role.ts +0 -80
  203. package/src/handlers/index.ts +0 -83
  204. package/src/handlers/label.test.ts +0 -112
  205. package/src/handlers/label.ts +0 -210
  206. package/src/handlers/mailbox-bindings.ts +0 -111
  207. package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
  208. package/src/handlers/mailbox-settled-target.test.ts +0 -251
  209. package/src/handlers/mailbox.test.ts +0 -280
  210. package/src/handlers/mailbox.ts +0 -581
  211. package/src/handlers/me.test.ts +0 -81
  212. package/src/handlers/me.ts +0 -203
  213. package/src/handlers/message-read-backfill.test.ts +0 -103
  214. package/src/handlers/message.test.ts +0 -131
  215. package/src/handlers/message.ts +0 -1131
  216. package/src/handlers/organize.test.ts +0 -277
  217. package/src/handlers/organize.ts +0 -197
  218. package/src/handlers/outbox-attachment.test.ts +0 -765
  219. package/src/handlers/outbox-attachment.ts +0 -126
  220. package/src/handlers/outbox-list.test.ts +0 -179
  221. package/src/handlers/outbox.test.ts +0 -772
  222. package/src/handlers/outbox.ts +0 -258
  223. package/src/handlers/search.test.ts +0 -161
  224. package/src/handlers/search.ts +0 -105
  225. package/src/handlers/sync-progress.test.ts +0 -205
  226. package/src/handlers/sync-progress.ts +0 -99
  227. package/src/handlers/sync.test.ts +0 -168
  228. package/src/handlers/sync.ts +0 -118
  229. package/src/handlers/system-update.test.ts +0 -497
  230. package/src/handlers/system-update.ts +0 -230
  231. package/src/handlers/thread.test.ts +0 -396
  232. package/src/handlers/thread.ts +0 -427
  233. package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
  234. package/src/handlers/unified-threads.test.ts +0 -785
  235. package/src/handlers/unified-threads.ts +0 -669
  236. package/src/index.test.ts +0 -83
  237. package/src/index.ts +0 -228
  238. package/src/jwt-auth.test.ts +0 -152
  239. package/src/jwt-auth.ts +0 -132
  240. package/src/log-redaction.test.ts +0 -144
  241. package/src/openapi-spec.test.ts +0 -118
  242. package/src/request-context.test.ts +0 -61
  243. package/src/request-context.ts +0 -33
  244. package/src/request-pipeline.test.ts +0 -88
  245. package/src/request.ts +0 -10
  246. package/src/response.test.ts +0 -130
  247. package/src/response.ts +0 -144
  248. package/src/service/compose-sqlite.ts +0 -98
  249. package/src/service/config-import.ts +0 -39
  250. package/src/service/create-remit-client.ts +0 -521
  251. package/src/service/data-client.test.ts +0 -97
  252. package/src/service/data-client.ts +0 -58
  253. package/src/service/export-identity.ts +0 -39
  254. package/src/service/filter.ts +0 -40
  255. package/src/service/fire-and-forget.test.ts +0 -126
  256. package/src/service/fire-and-forget.ts +0 -79
  257. package/src/service/localhost-env-config.test.ts +0 -36
  258. package/src/service/organize-queue.ts +0 -98
  259. package/src/service/organize.test.ts +0 -1451
  260. package/src/service/organize.ts +0 -1025
  261. package/src/service/read-path-filter-config.test.ts +0 -243
  262. package/src/service/semantic-capability.test.ts +0 -78
  263. package/src/service/semantic-capability.ts +0 -95
  264. package/src/service/sender-category.test.ts +0 -235
  265. package/src/service/trigger-sync.test.ts +0 -205
  266. package/src/service/trigger-sync.ts +0 -100
  267. package/src/stdout-capture-fixture.ts +0 -40
  268. package/src/types.ts +0 -271
  269. package/tsconfig.json +0 -7
@@ -1,497 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import {
3
- existsSync,
4
- mkdirSync,
5
- mkdtempSync,
6
- readFileSync,
7
- rmSync,
8
- writeFileSync,
9
- } from "node:fs";
10
- import { join } from "node:path";
11
- import { after, afterEach, before, beforeEach, describe, it } from "node:test";
12
- import { fileURLToPath } from "node:url";
13
- import type { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
14
- import type { Context } from "openapi-backend";
15
- import { normalizeRequest } from "../request.js";
16
- import { formatResponse } from "../response.js";
17
- import { SystemOperations } from "./system-update.js";
18
-
19
- const tmpRoot = join(
20
- fileURLToPath(new URL(".", import.meta.url)),
21
- "..",
22
- "..",
23
- ".tmp",
24
- "system-update",
25
- );
26
-
27
- const USER = "any-signed-in-user";
28
- const MANIFEST_URL = "https://updates.example.com/stable.json";
29
-
30
- let controlDir: string;
31
-
32
- const okState = {
33
- currentVersion: "v1.4.1",
34
- check: {
35
- status: "ok",
36
- lastCheckedAt: "2026-07-20T08:00:00Z",
37
- latestVersion: "v1.5.0",
38
- publishedAt: "2026-07-18T09:00:00Z",
39
- summary: "Faster search.",
40
- releaseNotesUrl: "https://example.com/notes",
41
- updateAvailable: true,
42
- },
43
- run: null,
44
- };
45
-
46
- const writeState = (state: unknown): void => {
47
- writeFileSync(join(controlDir, "state.json"), JSON.stringify(state));
48
- };
49
-
50
- before(() => {
51
- mkdirSync(tmpRoot, { recursive: true });
52
- });
53
-
54
- after(() => {
55
- rmSync(tmpRoot, { recursive: true, force: true });
56
- });
57
-
58
- beforeEach(() => {
59
- controlDir = mkdtempSync(join(tmpRoot, "control-"));
60
- process.env.REMIT_UPDATE_CONTROL_DIR = controlDir;
61
- process.env.REMIT_UPDATE_MANIFEST_URL = MANIFEST_URL;
62
- process.env.REMIT_TAG = "v1.4.1";
63
- });
64
-
65
- afterEach(() => {
66
- delete process.env.REMIT_UPDATE_MANIFEST_URL;
67
- delete process.env.REMIT_UPDATE_CONTROL_DIR;
68
- delete process.env.REMIT_TAG;
69
- });
70
-
71
- const buildEvent = (sub?: string): APIGatewayProxyEvent =>
72
- ({
73
- headers: {},
74
- requestContext: sub ? { authorizer: { claims: { sub } } } : {},
75
- }) as unknown as APIGatewayProxyEvent;
76
-
77
- // The query as openapi-backend hands it over: validated and coerced against the
78
- // spec, so `refresh` reaches the handler as a boolean or not at all.
79
- const getContext = (query: Record<string, unknown> = {}): Context =>
80
- ({ request: { query } }) as unknown as Context;
81
-
82
- const postContext = (targetVersion: string): Context =>
83
- ({ request: { requestBody: { targetVersion } } }) as unknown as Context;
84
-
85
- const getSystemUpdate =
86
- SystemOperations.SystemOperations_getSystemUpdate as unknown as (
87
- context: Context,
88
- event: APIGatewayProxyEvent,
89
- ) => Promise<unknown>;
90
-
91
- const applySystemUpdate =
92
- SystemOperations.SystemOperations_applySystemUpdate as unknown as (
93
- context: Context,
94
- event: APIGatewayProxyEvent,
95
- ) => Promise<unknown>;
96
-
97
- const getUpdate = (
98
- event: APIGatewayProxyEvent,
99
- query: Record<string, unknown> = {},
100
- ) => getSystemUpdate(getContext(query), event);
101
-
102
- const applyUpdate = (targetVersion: string, event: APIGatewayProxyEvent) =>
103
- applySystemUpdate(postContext(targetVersion), event);
104
-
105
- const hasStatus = (value: unknown, code: number): boolean =>
106
- typeof value === "object" &&
107
- value !== null &&
108
- (value as { statusCode?: number }).statusCode === code;
109
-
110
- describe("GET /system/update", () => {
111
- it("returns 404 when no manifest URL is configured", async () => {
112
- delete process.env.REMIT_UPDATE_MANIFEST_URL;
113
-
114
- const result = await getUpdate(buildEvent(USER));
115
-
116
- assert.ok(hasStatus(result, 404));
117
- });
118
-
119
- it("returns 401 when the caller is not authenticated", async () => {
120
- writeState(okState);
121
-
122
- const result = await getUpdate(buildEvent());
123
-
124
- assert.ok(hasStatus(result, 401));
125
- });
126
-
127
- it("returns 200 for any authenticated caller", async () => {
128
- writeState(okState);
129
-
130
- const result = await getUpdate(buildEvent("another-signed-in-user"));
131
-
132
- assert.deepEqual(result, okState);
133
- });
134
-
135
- it("returns 200 with the state file verbatim", async () => {
136
- writeState(okState);
137
-
138
- const result = await getUpdate(buildEvent(USER));
139
-
140
- assert.deepEqual(result, okState);
141
- });
142
-
143
- it("records a check request on the control volume when refresh is set (#599)", async () => {
144
- writeState(okState);
145
-
146
- const result = await getUpdate(buildEvent(USER), { refresh: true });
147
-
148
- // The press reaches the updater through the seam, and the answer is the
149
- // stored state — lastCheckedAt included, so the panel can say how old the
150
- // verdict it is still showing is.
151
- const file = readFileSync(join(controlDir, "check-request.json"), "utf8");
152
- assert.deepEqual(JSON.parse(file), {});
153
- assert.deepEqual(result, okState);
154
- });
155
-
156
- it("records a check request over an unknown version when no state exists yet", async () => {
157
- const result = await getUpdate(buildEvent(USER), { refresh: true });
158
-
159
- assert.equal(existsSync(join(controlDir, "check-request.json")), true);
160
- assert.deepEqual(result, {
161
- currentVersion: "unknown",
162
- check: { status: "disabled" },
163
- run: null,
164
- });
165
- });
166
-
167
- it("records nothing when the query carries no refresh", async () => {
168
- // `?refresh=1` is rejected by the spec before it reaches here, so the only
169
- // value that records a request is the boolean the validator produced.
170
- writeState(okState);
171
-
172
- await getUpdate(buildEvent(USER));
173
-
174
- assert.equal(existsSync(join(controlDir, "check-request.json")), false);
175
- });
176
-
177
- it("returns 401 and records nothing when a refresh is not authenticated", async () => {
178
- writeState(okState);
179
-
180
- const result = await getUpdate(buildEvent(), { refresh: true });
181
-
182
- assert.ok(hasStatus(result, 401));
183
- assert.equal(existsSync(join(controlDir, "check-request.json")), false);
184
- });
185
-
186
- it("reports an unknown version, not its own process env, when no state file exists", async () => {
187
- // The updater owns the running version and writes it into state.json; with
188
- // no state file the backend has nothing authoritative, so it says unknown
189
- // rather than surfacing REMIT_TAG, which drifts from the real running tag.
190
- const result = await getUpdate(buildEvent(USER));
191
-
192
- assert.deepEqual(result, {
193
- currentVersion: "unknown",
194
- check: { status: "disabled" },
195
- run: null,
196
- });
197
- });
198
-
199
- it("returns 200 with the run block untouched when the check has failed", async () => {
200
- const run = {
201
- runId: "run-1",
202
- fromVersion: "v1.3.0",
203
- targetVersion: "v1.4.1",
204
- phase: "committing",
205
- outcome: "succeeded",
206
- startedAt: "2026-07-19T08:00:00Z",
207
- updatedAt: "2026-07-19T08:05:00Z",
208
- message: "Update installed.",
209
- logCommand: "remit logs backend",
210
- };
211
- writeState({
212
- currentVersion: "v1.4.1",
213
- check: {
214
- status: "failed",
215
- lastCheckedAt: "2026-07-20T08:00:00Z",
216
- error: "The update manifest could not be fetched.",
217
- },
218
- run,
219
- });
220
-
221
- const result = (await getUpdate(buildEvent(USER))) as {
222
- check: { status: string };
223
- run: unknown;
224
- };
225
-
226
- assert.equal(result.check.status, "failed");
227
- assert.deepEqual(result.run, run);
228
- });
229
-
230
- it("passes both schema-version fields through when the state file carries them", async () => {
231
- writeState({
232
- currentVersion: "v1.4.1",
233
- currentSchemaVersion: 5,
234
- check: { ...okState.check, schemaVersion: 6 },
235
- run: null,
236
- });
237
-
238
- const result = (await getUpdate(buildEvent(USER))) as {
239
- currentSchemaVersion?: number;
240
- check: { schemaVersion?: number };
241
- };
242
-
243
- assert.equal(result.currentSchemaVersion, 5);
244
- assert.equal(result.check.schemaVersion, 6);
245
- });
246
-
247
- it("omits both schema-version fields when the state file predates them", async () => {
248
- writeState(okState);
249
-
250
- const result = (await getUpdate(buildEvent(USER))) as {
251
- currentSchemaVersion?: number;
252
- check: { schemaVersion?: number };
253
- };
254
-
255
- assert.ok(!("currentSchemaVersion" in result));
256
- assert.ok(!("schemaVersion" in result.check));
257
- });
258
-
259
- it("carries currentSchemaVersion alone when the check has no schema version", async () => {
260
- writeState({
261
- currentVersion: "v1.4.1",
262
- currentSchemaVersion: 5,
263
- check: okState.check,
264
- run: null,
265
- });
266
-
267
- const result = (await getUpdate(buildEvent(USER))) as {
268
- currentSchemaVersion?: number;
269
- check: { schemaVersion?: number };
270
- };
271
-
272
- assert.equal(result.currentSchemaVersion, 5);
273
- assert.ok(!("schemaVersion" in result.check));
274
- });
275
-
276
- it("carries check.schemaVersion alone when currentSchemaVersion is absent", async () => {
277
- writeState({
278
- currentVersion: "v1.4.1",
279
- check: { ...okState.check, schemaVersion: 6 },
280
- run: null,
281
- });
282
-
283
- const result = (await getUpdate(buildEvent(USER))) as {
284
- currentSchemaVersion?: number;
285
- check: { schemaVersion?: number };
286
- };
287
-
288
- assert.ok(!("currentSchemaVersion" in result));
289
- assert.equal(result.check.schemaVersion, 6);
290
- });
291
- });
292
-
293
- describe("POST /system/update", () => {
294
- it("returns 404 when no manifest URL is configured", async () => {
295
- delete process.env.REMIT_UPDATE_MANIFEST_URL;
296
-
297
- const result = await applyUpdate("v1.5.0", buildEvent(USER));
298
-
299
- assert.ok(hasStatus(result, 404));
300
- });
301
-
302
- it("returns 401 without writing request.json when the caller is not authenticated", async () => {
303
- writeState(okState);
304
-
305
- const result = await applyUpdate("v1.5.0", buildEvent());
306
-
307
- assert.ok(hasStatus(result, 401));
308
- assert.throws(() => readFileSync(join(controlDir, "request.json"), "utf8"));
309
- });
310
-
311
- it("returns 202 for any authenticated caller and records that identity", async () => {
312
- writeState(okState);
313
-
314
- const result = (await applyUpdate(
315
- "v1.5.0",
316
- buildEvent("another-signed-in-user"),
317
- )) as { statusCode: number };
318
-
319
- assert.equal(result.statusCode, 202);
320
-
321
- const request = JSON.parse(
322
- readFileSync(join(controlDir, "request.json"), "utf8"),
323
- );
324
- assert.equal(request.requestedBy, "another-signed-in-user");
325
- });
326
-
327
- it("returns 409 when a run is already in flight", async () => {
328
- writeState({
329
- ...okState,
330
- run: {
331
- runId: "run-9",
332
- fromVersion: "v1.4.1",
333
- targetVersion: "v1.5.0",
334
- phase: "verifying",
335
- outcome: null,
336
- startedAt: "2026-07-20T08:01:00Z",
337
- updatedAt: "2026-07-20T08:04:00Z",
338
- message: "Waiting for the new version to answer.",
339
- logCommand: "remit logs backend",
340
- },
341
- });
342
-
343
- await assert.rejects(applyUpdate("v1.5.0", buildEvent(USER)), {
344
- statusCode: 409,
345
- });
346
- });
347
-
348
- it("returns 422 when the target is not the checked version", async () => {
349
- writeState(okState);
350
-
351
- await assert.rejects(applyUpdate("v9.9.9", buildEvent(USER)), {
352
- statusCode: 422,
353
- });
354
- });
355
-
356
- it("returns 422 when no check has ever succeeded", async () => {
357
- await assert.rejects(applyUpdate("v1.5.0", buildEvent(USER)), {
358
- statusCode: 422,
359
- });
360
- });
361
-
362
- it("returns 202 with a run block carrying a fresh runId", async () => {
363
- writeState(okState);
364
-
365
- const result = (await applyUpdate("v1.5.0", buildEvent(USER))) as {
366
- statusCode: number;
367
- body: { run: { runId: string; targetVersion: string; outcome: null } };
368
- };
369
-
370
- assert.equal(result.statusCode, 202);
371
- assert.equal(result.body.run.targetVersion, "v1.5.0");
372
- assert.equal(result.body.run.outcome, null);
373
- assert.ok(result.body.run.runId.length > 0);
374
- });
375
-
376
- it("writes request.json with exactly the four seam fields and nothing else", async () => {
377
- writeState(okState);
378
-
379
- await applyUpdate("v1.5.0", buildEvent(USER));
380
-
381
- const request = JSON.parse(
382
- readFileSync(join(controlDir, "request.json"), "utf8"),
383
- );
384
-
385
- assert.deepEqual(Object.keys(request).sort(), [
386
- "requestedAt",
387
- "requestedBy",
388
- "runId",
389
- "targetVersion",
390
- ]);
391
- assert.equal(request.targetVersion, "v1.5.0");
392
- assert.equal(request.requestedBy, USER);
393
- for (const forbidden of ["registry", "image", "digest"]) {
394
- assert.ok(!(forbidden in request));
395
- }
396
- });
397
-
398
- it("stamps requestedAt with the moment of the request, in UTC", async () => {
399
- // The updater installs a request only while it is still current (#587), so
400
- // requestedAt is the field that decides it. A missing, fixed or non-UTC
401
- // stamp would make every request expire on arrival.
402
- writeState(okState);
403
- const before = Date.now();
404
-
405
- await applyUpdate("v1.5.0", buildEvent(USER));
406
-
407
- const after = Date.now();
408
- const request = JSON.parse(
409
- readFileSync(join(controlDir, "request.json"), "utf8"),
410
- ) as { requestedAt: string };
411
-
412
- assert.match(
413
- request.requestedAt,
414
- /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/,
415
- );
416
- const stamped = Date.parse(request.requestedAt);
417
- assert.ok(stamped >= before - 1000 && stamped <= after + 1000);
418
- });
419
- });
420
-
421
- describe("GET /system/update through the whole request pipeline", () => {
422
- it("records the check for ?refresh=true off the wire", async () => {
423
- // The rest of this file hands the handler a query the validator has already
424
- // coerced. Here the query arrives as API Gateway delivers it — every value a
425
- // string — and goes through the real built spec, which is where the press
426
- // was being answered with 400 instead of reaching the handler at all.
427
- writeState(okState);
428
- const { api } = await import("../index.js");
429
-
430
- const event = {
431
- httpMethod: "GET",
432
- path: "/system/update",
433
- queryStringParameters: { refresh: "true" },
434
- headers: {},
435
- requestContext: { authorizer: { claims: { sub: USER } } },
436
- } as unknown as APIGatewayProxyEvent;
437
-
438
- const result = (await api.handleRequest(
439
- normalizeRequest(event),
440
- event,
441
- {} as never,
442
- )) as APIGatewayProxyResult;
443
-
444
- assert.equal(result.statusCode, 200);
445
- assert.deepEqual(
446
- JSON.parse(readFileSync(join(controlDir, "check-request.json"), "utf8")),
447
- {},
448
- );
449
- assert.deepEqual(JSON.parse(result.body), okState);
450
- });
451
- });
452
-
453
- // A refusal here is the object `formatResponse` unwraps, never one that has
454
- // already been serialized: handing it a `body` string publishes the envelope
455
- // itself — `{"statusCode":…,"headers":…,"body":"{…}"}` — and what the caller
456
- // then parses is not an ApiError at all. Only the status was ever asserted, so
457
- // the shape went unguarded (issue #371).
458
- describe("the wire body of a self-update refusal", () => {
459
- it("answers an unconfigured seam with a flat not_found", async () => {
460
- delete process.env.REMIT_UPDATE_MANIFEST_URL;
461
-
462
- const result = await getUpdate(buildEvent(USER));
463
- const wire = formatResponse(result as Record<string, unknown>);
464
-
465
- assert.equal(wire.statusCode, 404);
466
- assert.deepEqual(JSON.parse(wire.body), {
467
- code: "not_found",
468
- message: "Not found",
469
- });
470
- });
471
-
472
- it("answers an unauthenticated caller with a flat unauthorized", async () => {
473
- writeState(okState);
474
-
475
- const result = await getUpdate(buildEvent());
476
- const wire = formatResponse(result as Record<string, unknown>);
477
-
478
- assert.equal(wire.statusCode, 401);
479
- assert.deepEqual(JSON.parse(wire.body), {
480
- code: "unauthorized",
481
- message: "Unauthorized",
482
- });
483
- });
484
-
485
- it("answers an unauthenticated install request the same way", async () => {
486
- writeState(okState);
487
-
488
- const result = await applyUpdate("v1.5.0", buildEvent());
489
- const wire = formatResponse(result as Record<string, unknown>);
490
-
491
- assert.equal(wire.statusCode, 401);
492
- assert.deepEqual(JSON.parse(wire.body), {
493
- code: "unauthorized",
494
- message: "Unauthorized",
495
- });
496
- });
497
- });