@company-semantics/contracts 9.0.0 → 9.2.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.
Files changed (141) hide show
  1. package/package.json +6 -3
  2. package/src/__tests__/resource-keys.test.ts +30 -23
  3. package/src/admin/authz-simulate.ts +4 -4
  4. package/src/admin/direct-grants.ts +2 -2
  5. package/src/api/generated-spec-hash.ts +2 -2
  6. package/src/api/generated.ts +97 -0
  7. package/src/api/http/routes/ai-chat.ts +3 -3
  8. package/src/api/http/utils/resource-response.ts +5 -2
  9. package/src/api/index.ts +4 -4
  10. package/src/api/primitives.ts +6 -2
  11. package/src/auth/README.md +1 -0
  12. package/src/auth/index.ts +12 -5
  13. package/src/autotune.ts +5 -1
  14. package/src/billing/index.ts +1 -1
  15. package/src/billing/types.ts +1 -1
  16. package/src/chat/README.md +3 -0
  17. package/src/chat/__tests__/runtime-profile.test.ts +68 -48
  18. package/src/chat/index.ts +10 -4
  19. package/src/chat/runtime-profile.ts +25 -10
  20. package/src/chat/schemas.ts +49 -41
  21. package/src/chat/types.ts +48 -42
  22. package/src/ci-envelope/README.md +2 -0
  23. package/src/ci-envelope/__tests__/transitions.test.ts +56 -56
  24. package/src/ci-envelope/index.ts +2 -2
  25. package/src/ci-envelope/types.ts +20 -20
  26. package/src/ci-results/index.ts +2 -2
  27. package/src/ci-results/repo-ci-result.ts +15 -12
  28. package/src/compatibility.ts +6 -6
  29. package/src/content/index.ts +10 -4
  30. package/src/content/schemas.ts +42 -24
  31. package/src/dispatch/index.ts +18 -15
  32. package/src/email/__tests__/registry.test.ts +81 -77
  33. package/src/email/index.ts +3 -3
  34. package/src/email/registry.ts +25 -25
  35. package/src/email/types.ts +43 -43
  36. package/src/errors/index.ts +8 -8
  37. package/src/execution/__tests__/events.test.ts +42 -42
  38. package/src/execution/__tests__/lifecycle.test.ts +192 -190
  39. package/src/execution/__tests__/registry.test.ts +114 -114
  40. package/src/execution/audit-export.ts +4 -4
  41. package/src/execution/errors.ts +7 -7
  42. package/src/execution/event-metadata.ts +4 -4
  43. package/src/execution/events.ts +23 -21
  44. package/src/execution/expiry.ts +5 -5
  45. package/src/execution/hash-chain.ts +2 -2
  46. package/src/execution/index.ts +19 -28
  47. package/src/execution/kinds.ts +7 -7
  48. package/src/execution/lifecycle.ts +33 -33
  49. package/src/execution/registry.ts +63 -63
  50. package/src/execution/schemas.ts +31 -23
  51. package/src/execution/status.ts +45 -26
  52. package/src/execution/summary.ts +16 -17
  53. package/src/execution/timeline-ui.ts +9 -9
  54. package/src/execution/types.ts +31 -25
  55. package/src/generated/openapi-routes.ts +2 -0
  56. package/src/guards/config.ts +22 -18
  57. package/src/guards/index.ts +4 -4
  58. package/src/guards/types.ts +32 -24
  59. package/src/identity/__tests__/avatar.test.ts +68 -59
  60. package/src/identity/avatar.ts +8 -8
  61. package/src/identity/display-name.ts +3 -3
  62. package/src/identity/index.ts +8 -8
  63. package/src/identity/people-org-chart.ts +8 -4
  64. package/src/identity/schemas.ts +28 -18
  65. package/src/identity/types.ts +5 -5
  66. package/src/impersonation/index.ts +5 -5
  67. package/src/impersonation/schemas.ts +15 -9
  68. package/src/impersonation-events.ts +21 -21
  69. package/src/impersonation.ts +25 -24
  70. package/src/index.ts +118 -90
  71. package/src/interfaces/mcp/tools/help.ts +19 -19
  72. package/src/internal-admin.ts +6 -6
  73. package/src/mcp/README.md +2 -0
  74. package/src/mcp/__tests__/capability-graph.test.ts +290 -290
  75. package/src/mcp/capability-graph.ts +42 -40
  76. package/src/mcp/failure-context.ts +1 -3
  77. package/src/mcp/index.ts +69 -56
  78. package/src/mcp/resources.ts +9 -9
  79. package/src/meetings/index.ts +2 -2
  80. package/src/meetings/schemas.ts +51 -34
  81. package/src/message-parts/README.md +2 -0
  82. package/src/message-parts/__tests__/builder.test.ts +142 -142
  83. package/src/message-parts/__tests__/confirmation.test.ts +100 -86
  84. package/src/message-parts/__tests__/preview.test.ts +63 -63
  85. package/src/message-parts/__tests__/wire.test.ts +130 -124
  86. package/src/message-parts/builder.ts +23 -23
  87. package/src/message-parts/confirmation.ts +17 -14
  88. package/src/message-parts/execution.ts +7 -7
  89. package/src/message-parts/index.ts +10 -10
  90. package/src/message-parts/lifecycle.ts +25 -25
  91. package/src/message-parts/preview.ts +30 -30
  92. package/src/message-parts/types.ts +27 -27
  93. package/src/message-parts/wire.ts +24 -24
  94. package/src/mutations.ts +2 -2
  95. package/src/observability.ts +23 -11
  96. package/src/org/__tests__/org-units.test.ts +131 -96
  97. package/src/org/__tests__/tree-ordering.test.ts +57 -37
  98. package/src/org/__tests__/view-scopes.test.ts +40 -40
  99. package/src/org/domain.ts +9 -9
  100. package/src/org/index.ts +31 -21
  101. package/src/org/org-units.ts +34 -20
  102. package/src/org/schemas.ts +261 -124
  103. package/src/org/sharing.ts +17 -13
  104. package/src/org/tree-ordering.ts +3 -1
  105. package/src/org/types.ts +54 -47
  106. package/src/org/view-scopes.ts +9 -9
  107. package/src/permissions/access-levels.ts +7 -2
  108. package/src/permissions/access-source.ts +6 -6
  109. package/src/permissions/index.ts +5 -5
  110. package/src/permissions/orgchart-roles.ts +7 -7
  111. package/src/permissions/permission-introspection.ts +7 -5
  112. package/src/permissions/share-api.ts +19 -9
  113. package/src/pressure.ts +4 -4
  114. package/src/queryIntent.ts +21 -21
  115. package/src/ralph/__tests__/prd-groups.test.ts +159 -159
  116. package/src/ralph/__tests__/prd.test.ts +30 -30
  117. package/src/ralph/index.ts +3 -8
  118. package/src/ralph/prd.ts +33 -33
  119. package/src/ralph/progress.ts +1 -1
  120. package/src/rate-limit/README.md +4 -4
  121. package/src/rate-limit/index.ts +3 -3
  122. package/src/requests.ts +36 -8
  123. package/src/resource-keys.ts +207 -124
  124. package/src/resource-registry.ts +5 -5
  125. package/src/route-builder.ts +3 -3
  126. package/src/safe-mode.ts +2 -2
  127. package/src/security/index.ts +4 -4
  128. package/src/security/org-secrets.ts +13 -9
  129. package/src/security/secret.ts +3 -3
  130. package/src/sse.ts +3 -1
  131. package/src/system/README.md +3 -0
  132. package/src/system/capabilities.ts +22 -23
  133. package/src/system/diagram.ts +45 -45
  134. package/src/system/index.ts +14 -14
  135. package/src/tiers.ts +1 -1
  136. package/src/timeouts.ts +1 -1
  137. package/src/tracing.ts +30 -30
  138. package/src/types/analytics.ts +2 -2
  139. package/src/usage/README.md +3 -0
  140. package/src/usage/execution-types.ts +69 -69
  141. package/src/usage/types.ts +7 -3
@@ -1,261 +1,263 @@
1
- import { describe, it, expect } from 'vitest'
2
- import type { ExecutionState } from '../status.js'
1
+ import { describe, it, expect } from "vitest";
2
+ import type { ExecutionState } from "../status.js";
3
3
  import {
4
4
  VALID_TRANSITIONS,
5
5
  TERMINAL_STATES,
6
6
  EFFECTIVE_TERMINAL_STATES,
7
7
  assertValidTransition,
8
- } from '../status.js'
9
- import { LIFECYCLE_DECISIONS, applyIntent } from '../lifecycle.js'
10
- import type { ExecutionErrorCode } from '../errors.js'
11
- import { parseExpiresAt, isConfirmationExpired } from '../expiry.js'
8
+ } from "../status.js";
9
+ import { LIFECYCLE_DECISIONS, applyIntent } from "../lifecycle.js";
10
+ import type { ExecutionErrorCode } from "../errors.js";
11
+ import { parseExpiresAt, isConfirmationExpired } from "../expiry.js";
12
12
 
13
13
  const ALL_STATES: ExecutionState[] = [
14
- 'pending_confirmation',
15
- 'blocked_pending_approval',
16
- 'ready',
17
- 'executing',
18
- 'completed',
19
- 'completed_with_rollbacks',
20
- 'failed_retryable',
21
- 'failed_exhausted',
22
- 'failed_terminal',
23
- 'failed_with_partial_execution',
24
- 'cancelled',
25
- 'expired',
26
- 'undone',
27
- ]
14
+ "pending_confirmation",
15
+ "blocked_pending_approval",
16
+ "ready",
17
+ "executing",
18
+ "completed",
19
+ "completed_with_rollbacks",
20
+ "failed_retryable",
21
+ "failed_exhausted",
22
+ "failed_terminal",
23
+ "failed_with_partial_execution",
24
+ "cancelled",
25
+ "expired",
26
+ "undone",
27
+ ];
28
28
 
29
29
  // ---------------------------------------------------------------------------
30
30
  // 1. EXHAUSTIVE TRANSITION MATRIX
31
31
  // ---------------------------------------------------------------------------
32
- describe('exhaustive transition matrix', () => {
32
+ describe("exhaustive transition matrix", () => {
33
33
  for (const from of ALL_STATES) {
34
34
  for (const to of ALL_STATES) {
35
- const allowed = VALID_TRANSITIONS[from].includes(to)
36
- it(`${from} -> ${to} should be ${allowed ? 'valid' : 'invalid'}`, () => {
35
+ const allowed = VALID_TRANSITIONS[from].includes(to);
36
+ it(`${from} -> ${to} should be ${allowed ? "valid" : "invalid"}`, () => {
37
37
  if (allowed) {
38
- expect(() => assertValidTransition(from, to)).not.toThrow()
38
+ expect(() => assertValidTransition(from, to)).not.toThrow();
39
39
  } else {
40
40
  expect(() => assertValidTransition(from, to)).toThrow(
41
41
  `Invalid execution state transition: ${from} -> ${to}`,
42
- )
42
+ );
43
43
  }
44
- })
44
+ });
45
45
  }
46
46
  }
47
- })
47
+ });
48
48
 
49
49
  // ---------------------------------------------------------------------------
50
50
  // 2. TERMINAL_STATES CONSISTENCY
51
51
  // ---------------------------------------------------------------------------
52
- describe('TERMINAL_STATES consistency', () => {
53
- it('contains exactly the states with empty transition arrays', () => {
52
+ describe("TERMINAL_STATES consistency", () => {
53
+ it("contains exactly the states with empty transition arrays", () => {
54
54
  const expectedTerminal = ALL_STATES.filter(
55
55
  (s) => VALID_TRANSITIONS[s].length === 0,
56
- )
57
- expect(expectedTerminal.length).toBeGreaterThan(0)
56
+ );
57
+ expect(expectedTerminal.length).toBeGreaterThan(0);
58
58
  for (const state of expectedTerminal) {
59
- expect(TERMINAL_STATES.has(state)).toBe(true)
59
+ expect(TERMINAL_STATES.has(state)).toBe(true);
60
60
  }
61
- expect(TERMINAL_STATES.size).toBe(expectedTerminal.length)
62
- })
61
+ expect(TERMINAL_STATES.size).toBe(expectedTerminal.length);
62
+ });
63
63
 
64
- it('does not contain states with non-empty transition arrays', () => {
64
+ it("does not contain states with non-empty transition arrays", () => {
65
65
  const nonTerminal = ALL_STATES.filter(
66
66
  (s) => VALID_TRANSITIONS[s].length > 0,
67
- )
67
+ );
68
68
  for (const state of nonTerminal) {
69
- expect(TERMINAL_STATES.has(state)).toBe(false)
69
+ expect(TERMINAL_STATES.has(state)).toBe(false);
70
70
  }
71
- })
72
- })
71
+ });
72
+ });
73
73
 
74
74
  // ---------------------------------------------------------------------------
75
75
  // 3. EFFECTIVE_TERMINAL_STATES
76
76
  // ---------------------------------------------------------------------------
77
- describe('EFFECTIVE_TERMINAL_STATES', () => {
78
- it('contains all TERMINAL_STATES', () => {
77
+ describe("EFFECTIVE_TERMINAL_STATES", () => {
78
+ it("contains all TERMINAL_STATES", () => {
79
79
  for (const state of TERMINAL_STATES) {
80
- expect(EFFECTIVE_TERMINAL_STATES.has(state)).toBe(true)
80
+ expect(EFFECTIVE_TERMINAL_STATES.has(state)).toBe(true);
81
81
  }
82
- })
82
+ });
83
83
 
84
- it('contains completed', () => {
85
- expect(EFFECTIVE_TERMINAL_STATES.has('completed')).toBe(true)
86
- })
84
+ it("contains completed", () => {
85
+ expect(EFFECTIVE_TERMINAL_STATES.has("completed")).toBe(true);
86
+ });
87
87
 
88
- it('has size equal to TERMINAL_STATES + 1 (completed)', () => {
89
- expect(EFFECTIVE_TERMINAL_STATES.size).toBe(TERMINAL_STATES.size + 1)
90
- })
88
+ it("has size equal to TERMINAL_STATES + 1 (completed)", () => {
89
+ expect(EFFECTIVE_TERMINAL_STATES.size).toBe(TERMINAL_STATES.size + 1);
90
+ });
91
91
 
92
- it('does not contain non-terminal, non-completed states', () => {
92
+ it("does not contain non-terminal, non-completed states", () => {
93
93
  const nonEffectiveTerminal = ALL_STATES.filter(
94
- (s) => !TERMINAL_STATES.has(s) && s !== 'completed',
95
- )
94
+ (s) => !TERMINAL_STATES.has(s) && s !== "completed",
95
+ );
96
96
  for (const state of nonEffectiveTerminal) {
97
- expect(EFFECTIVE_TERMINAL_STATES.has(state)).toBe(false)
97
+ expect(EFFECTIVE_TERMINAL_STATES.has(state)).toBe(false);
98
98
  }
99
- })
100
- })
99
+ });
100
+ });
101
101
 
102
102
  // ---------------------------------------------------------------------------
103
103
  // 4. DECISION TABLE KEY CONSISTENCY
104
104
  // ---------------------------------------------------------------------------
105
- describe('decision table key consistency', () => {
106
- it('LIFECYCLE_DECISIONS.confirm keys match VALID_TRANSITIONS keys', () => {
107
- const transitionKeys = Object.keys(VALID_TRANSITIONS).sort()
108
- const decisionKeys = Object.keys(LIFECYCLE_DECISIONS.confirm).sort()
109
- expect(decisionKeys).toEqual(transitionKeys)
110
- })
111
-
112
- it('LIFECYCLE_DECISIONS.reject keys match VALID_TRANSITIONS keys', () => {
113
- const transitionKeys = Object.keys(VALID_TRANSITIONS).sort()
114
- const decisionKeys = Object.keys(LIFECYCLE_DECISIONS.reject).sort()
115
- expect(decisionKeys).toEqual(transitionKeys)
116
- })
117
-
118
- it('all intent rows have the same set of state keys', () => {
119
- const intents = Object.keys(LIFECYCLE_DECISIONS) as Array<keyof typeof LIFECYCLE_DECISIONS>
120
- const referenceKeys = Object.keys(LIFECYCLE_DECISIONS[intents[0]]).sort()
105
+ describe("decision table key consistency", () => {
106
+ it("LIFECYCLE_DECISIONS.confirm keys match VALID_TRANSITIONS keys", () => {
107
+ const transitionKeys = Object.keys(VALID_TRANSITIONS).sort();
108
+ const decisionKeys = Object.keys(LIFECYCLE_DECISIONS.confirm).sort();
109
+ expect(decisionKeys).toEqual(transitionKeys);
110
+ });
111
+
112
+ it("LIFECYCLE_DECISIONS.reject keys match VALID_TRANSITIONS keys", () => {
113
+ const transitionKeys = Object.keys(VALID_TRANSITIONS).sort();
114
+ const decisionKeys = Object.keys(LIFECYCLE_DECISIONS.reject).sort();
115
+ expect(decisionKeys).toEqual(transitionKeys);
116
+ });
117
+
118
+ it("all intent rows have the same set of state keys", () => {
119
+ const intents = Object.keys(LIFECYCLE_DECISIONS) as Array<
120
+ keyof typeof LIFECYCLE_DECISIONS
121
+ >;
122
+ const referenceKeys = Object.keys(LIFECYCLE_DECISIONS[intents[0]]).sort();
121
123
  for (const intent of intents) {
122
- expect(Object.keys(LIFECYCLE_DECISIONS[intent]).sort()).toEqual(referenceKeys)
124
+ expect(Object.keys(LIFECYCLE_DECISIONS[intent]).sort()).toEqual(
125
+ referenceKeys,
126
+ );
123
127
  }
124
- })
125
- })
128
+ });
129
+ });
126
130
 
127
131
  // ---------------------------------------------------------------------------
128
132
  // 5. APPROVE-TRANSITION DRIFT
129
133
  // ---------------------------------------------------------------------------
130
- describe('approve-transition drift', () => {
131
- it('every state with approve decision can transition to ready', () => {
134
+ describe("approve-transition drift", () => {
135
+ it("every state with approve decision can transition to ready", () => {
132
136
  for (const state of ALL_STATES) {
133
- if (LIFECYCLE_DECISIONS.confirm[state] === 'approve') {
134
- expect(
135
- VALID_TRANSITIONS[state].includes('ready'),
136
- ).toBe(true)
137
+ if (LIFECYCLE_DECISIONS.confirm[state] === "approve") {
138
+ expect(VALID_TRANSITIONS[state].includes("ready")).toBe(true);
137
139
  }
138
140
  }
139
- })
140
- })
141
+ });
142
+ });
141
143
 
142
144
  // ---------------------------------------------------------------------------
143
145
  // 6. APPLY_INTENT UNIT TESTS
144
146
  // ---------------------------------------------------------------------------
145
- describe('applyIntent', () => {
146
- it('pending_confirmation + confirm = approve', () => {
147
- expect(applyIntent('pending_confirmation', 'confirm')).toBe('approve')
148
- })
149
-
150
- it('expired + confirm = expired', () => {
151
- expect(applyIntent('expired', 'confirm')).toBe('expired')
152
- })
153
-
154
- it('cancelled + confirm = conflict', () => {
155
- expect(applyIntent('cancelled', 'confirm')).toBe('conflict')
156
- })
157
-
158
- it('ready + confirm = already_confirmed', () => {
159
- expect(applyIntent('ready', 'confirm')).toBe('already_confirmed')
160
- })
161
-
162
- it('blocked_pending_approval + confirm = awaiting_approval', () => {
163
- expect(applyIntent('blocked_pending_approval', 'confirm')).toBe(
164
- 'awaiting_approval',
165
- )
166
- })
167
-
168
- it('failed_terminal + confirm = conflict', () => {
169
- expect(applyIntent('failed_terminal', 'confirm')).toBe('conflict')
170
- })
171
-
172
- it('undone + confirm = conflict', () => {
173
- expect(applyIntent('undone', 'confirm')).toBe('conflict')
174
- })
175
-
176
- it('pending_confirmation + reject resolves as approve (rejection uses same cancel path)', () => {
177
- expect(applyIntent('pending_confirmation', 'reject')).toBe('approve')
178
- })
179
-
180
- it('expired + reject = expired', () => {
181
- expect(applyIntent('expired', 'reject')).toBe('expired')
182
- })
183
-
184
- it('cancelled + reject = conflict', () => {
185
- expect(applyIntent('cancelled', 'reject')).toBe('conflict')
186
- })
187
-
188
- it('ready + reject = conflict', () => {
189
- expect(applyIntent('ready', 'reject')).toBe('conflict')
190
- })
191
-
192
- it('executing + reject = conflict', () => {
193
- expect(applyIntent('executing', 'reject')).toBe('conflict')
194
- })
195
-
196
- it('completed + reject = conflict', () => {
197
- expect(applyIntent('completed', 'reject')).toBe('conflict')
198
- })
199
- })
147
+ describe("applyIntent", () => {
148
+ it("pending_confirmation + confirm = approve", () => {
149
+ expect(applyIntent("pending_confirmation", "confirm")).toBe("approve");
150
+ });
151
+
152
+ it("expired + confirm = expired", () => {
153
+ expect(applyIntent("expired", "confirm")).toBe("expired");
154
+ });
155
+
156
+ it("cancelled + confirm = conflict", () => {
157
+ expect(applyIntent("cancelled", "confirm")).toBe("conflict");
158
+ });
159
+
160
+ it("ready + confirm = already_confirmed", () => {
161
+ expect(applyIntent("ready", "confirm")).toBe("already_confirmed");
162
+ });
163
+
164
+ it("blocked_pending_approval + confirm = awaiting_approval", () => {
165
+ expect(applyIntent("blocked_pending_approval", "confirm")).toBe(
166
+ "awaiting_approval",
167
+ );
168
+ });
169
+
170
+ it("failed_terminal + confirm = conflict", () => {
171
+ expect(applyIntent("failed_terminal", "confirm")).toBe("conflict");
172
+ });
173
+
174
+ it("undone + confirm = conflict", () => {
175
+ expect(applyIntent("undone", "confirm")).toBe("conflict");
176
+ });
177
+
178
+ it("pending_confirmation + reject resolves as approve (rejection uses same cancel path)", () => {
179
+ expect(applyIntent("pending_confirmation", "reject")).toBe("approve");
180
+ });
181
+
182
+ it("expired + reject = expired", () => {
183
+ expect(applyIntent("expired", "reject")).toBe("expired");
184
+ });
185
+
186
+ it("cancelled + reject = conflict", () => {
187
+ expect(applyIntent("cancelled", "reject")).toBe("conflict");
188
+ });
189
+
190
+ it("ready + reject = conflict", () => {
191
+ expect(applyIntent("ready", "reject")).toBe("conflict");
192
+ });
193
+
194
+ it("executing + reject = conflict", () => {
195
+ expect(applyIntent("executing", "reject")).toBe("conflict");
196
+ });
197
+
198
+ it("completed + reject = conflict", () => {
199
+ expect(applyIntent("completed", "reject")).toBe("conflict");
200
+ });
201
+ });
200
202
 
201
203
  // ---------------------------------------------------------------------------
202
204
  // 7. EXECUTION_ERROR TYPE TESTS
203
205
  // ---------------------------------------------------------------------------
204
- describe('ExecutionErrorCode', () => {
205
- it('covers all expected error codes', () => {
206
+ describe("ExecutionErrorCode", () => {
207
+ it("covers all expected error codes", () => {
206
208
  const codes: ExecutionErrorCode[] = [
207
- 'execution_expired',
208
- 'execution_already_confirmed',
209
- 'execution_awaiting_approval',
210
- 'execution_not_found',
211
- 'execution_forbidden',
212
- 'execution_invalid_transition',
213
- ]
214
- expect(codes).toHaveLength(6)
215
- })
216
- })
209
+ "execution_expired",
210
+ "execution_already_confirmed",
211
+ "execution_awaiting_approval",
212
+ "execution_not_found",
213
+ "execution_forbidden",
214
+ "execution_invalid_transition",
215
+ ];
216
+ expect(codes).toHaveLength(6);
217
+ });
218
+ });
217
219
 
218
220
  // ---------------------------------------------------------------------------
219
221
  // 8. EXPIRY HELPER TESTS
220
222
  // ---------------------------------------------------------------------------
221
- describe('parseExpiresAt', () => {
222
- it('returns undefined for undefined input', () => {
223
- expect(parseExpiresAt(undefined)).toBeUndefined()
224
- })
225
-
226
- it('returns undefined for invalid date string', () => {
227
- expect(parseExpiresAt('invalid')).toBeUndefined()
228
- })
229
-
230
- it('returns correct epoch ms for valid ISO string', () => {
231
- const result = parseExpiresAt('2026-01-01T00:00:00Z')
232
- expect(result).toBe(new Date('2026-01-01T00:00:00Z').getTime())
233
- })
234
-
235
- it('returns undefined for empty string', () => {
236
- expect(parseExpiresAt('')).toBeUndefined()
237
- })
238
- })
239
-
240
- describe('isConfirmationExpired', () => {
241
- it('returns false for undefined expiresAtMs', () => {
242
- expect(isConfirmationExpired(undefined)).toBe(false)
243
- })
244
-
245
- it('returns true for past timestamp', () => {
246
- const pastMs = Date.now() - 60_000
247
- expect(isConfirmationExpired(pastMs)).toBe(true)
248
- })
249
-
250
- it('returns false for future timestamp', () => {
251
- const futureMs = Date.now() + 60_000
252
- expect(isConfirmationExpired(futureMs)).toBe(false)
253
- })
254
-
255
- it('works with custom now parameter', () => {
256
- const expiresAtMs = 1000
257
- expect(isConfirmationExpired(expiresAtMs, 999)).toBe(false)
258
- expect(isConfirmationExpired(expiresAtMs, 1000)).toBe(true)
259
- expect(isConfirmationExpired(expiresAtMs, 1001)).toBe(true)
260
- })
261
- })
223
+ describe("parseExpiresAt", () => {
224
+ it("returns undefined for undefined input", () => {
225
+ expect(parseExpiresAt(undefined)).toBeUndefined();
226
+ });
227
+
228
+ it("returns undefined for invalid date string", () => {
229
+ expect(parseExpiresAt("invalid")).toBeUndefined();
230
+ });
231
+
232
+ it("returns correct epoch ms for valid ISO string", () => {
233
+ const result = parseExpiresAt("2026-01-01T00:00:00Z");
234
+ expect(result).toBe(new Date("2026-01-01T00:00:00Z").getTime());
235
+ });
236
+
237
+ it("returns undefined for empty string", () => {
238
+ expect(parseExpiresAt("")).toBeUndefined();
239
+ });
240
+ });
241
+
242
+ describe("isConfirmationExpired", () => {
243
+ it("returns false for undefined expiresAtMs", () => {
244
+ expect(isConfirmationExpired(undefined)).toBe(false);
245
+ });
246
+
247
+ it("returns true for past timestamp", () => {
248
+ const pastMs = Date.now() - 60_000;
249
+ expect(isConfirmationExpired(pastMs)).toBe(true);
250
+ });
251
+
252
+ it("returns false for future timestamp", () => {
253
+ const futureMs = Date.now() + 60_000;
254
+ expect(isConfirmationExpired(futureMs)).toBe(false);
255
+ });
256
+
257
+ it("works with custom now parameter", () => {
258
+ const expiresAtMs = 1000;
259
+ expect(isConfirmationExpired(expiresAtMs, 999)).toBe(false);
260
+ expect(isConfirmationExpired(expiresAtMs, 1000)).toBe(true);
261
+ expect(isConfirmationExpired(expiresAtMs, 1001)).toBe(true);
262
+ });
263
+ });