@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.
- package/package.json +6 -3
- package/src/__tests__/resource-keys.test.ts +30 -23
- package/src/admin/authz-simulate.ts +4 -4
- package/src/admin/direct-grants.ts +2 -2
- package/src/api/generated-spec-hash.ts +2 -2
- package/src/api/generated.ts +97 -0
- package/src/api/http/routes/ai-chat.ts +3 -3
- package/src/api/http/utils/resource-response.ts +5 -2
- package/src/api/index.ts +4 -4
- package/src/api/primitives.ts +6 -2
- package/src/auth/README.md +1 -0
- package/src/auth/index.ts +12 -5
- package/src/autotune.ts +5 -1
- package/src/billing/index.ts +1 -1
- package/src/billing/types.ts +1 -1
- package/src/chat/README.md +3 -0
- package/src/chat/__tests__/runtime-profile.test.ts +68 -48
- package/src/chat/index.ts +10 -4
- package/src/chat/runtime-profile.ts +25 -10
- package/src/chat/schemas.ts +49 -41
- package/src/chat/types.ts +48 -42
- package/src/ci-envelope/README.md +2 -0
- package/src/ci-envelope/__tests__/transitions.test.ts +56 -56
- package/src/ci-envelope/index.ts +2 -2
- package/src/ci-envelope/types.ts +20 -20
- package/src/ci-results/index.ts +2 -2
- package/src/ci-results/repo-ci-result.ts +15 -12
- package/src/compatibility.ts +6 -6
- package/src/content/index.ts +10 -4
- package/src/content/schemas.ts +42 -24
- package/src/dispatch/index.ts +18 -15
- package/src/email/__tests__/registry.test.ts +81 -77
- package/src/email/index.ts +3 -3
- package/src/email/registry.ts +25 -25
- package/src/email/types.ts +43 -43
- package/src/errors/index.ts +8 -8
- package/src/execution/__tests__/events.test.ts +42 -42
- package/src/execution/__tests__/lifecycle.test.ts +192 -190
- package/src/execution/__tests__/registry.test.ts +114 -114
- package/src/execution/audit-export.ts +4 -4
- package/src/execution/errors.ts +7 -7
- package/src/execution/event-metadata.ts +4 -4
- package/src/execution/events.ts +23 -21
- package/src/execution/expiry.ts +5 -5
- package/src/execution/hash-chain.ts +2 -2
- package/src/execution/index.ts +19 -28
- package/src/execution/kinds.ts +7 -7
- package/src/execution/lifecycle.ts +33 -33
- package/src/execution/registry.ts +63 -63
- package/src/execution/schemas.ts +31 -23
- package/src/execution/status.ts +45 -26
- package/src/execution/summary.ts +16 -17
- package/src/execution/timeline-ui.ts +9 -9
- package/src/execution/types.ts +31 -25
- package/src/generated/openapi-routes.ts +2 -0
- package/src/guards/config.ts +22 -18
- package/src/guards/index.ts +4 -4
- package/src/guards/types.ts +32 -24
- package/src/identity/__tests__/avatar.test.ts +68 -59
- package/src/identity/avatar.ts +8 -8
- package/src/identity/display-name.ts +3 -3
- package/src/identity/index.ts +8 -8
- package/src/identity/people-org-chart.ts +8 -4
- package/src/identity/schemas.ts +28 -18
- package/src/identity/types.ts +5 -5
- package/src/impersonation/index.ts +5 -5
- package/src/impersonation/schemas.ts +15 -9
- package/src/impersonation-events.ts +21 -21
- package/src/impersonation.ts +25 -24
- package/src/index.ts +118 -90
- package/src/interfaces/mcp/tools/help.ts +19 -19
- package/src/internal-admin.ts +6 -6
- package/src/mcp/README.md +2 -0
- package/src/mcp/__tests__/capability-graph.test.ts +290 -290
- package/src/mcp/capability-graph.ts +42 -40
- package/src/mcp/failure-context.ts +1 -3
- package/src/mcp/index.ts +69 -56
- package/src/mcp/resources.ts +9 -9
- package/src/meetings/index.ts +2 -2
- package/src/meetings/schemas.ts +51 -34
- package/src/message-parts/README.md +2 -0
- package/src/message-parts/__tests__/builder.test.ts +142 -142
- package/src/message-parts/__tests__/confirmation.test.ts +100 -86
- package/src/message-parts/__tests__/preview.test.ts +63 -63
- package/src/message-parts/__tests__/wire.test.ts +130 -124
- package/src/message-parts/builder.ts +23 -23
- package/src/message-parts/confirmation.ts +17 -14
- package/src/message-parts/execution.ts +7 -7
- package/src/message-parts/index.ts +10 -10
- package/src/message-parts/lifecycle.ts +25 -25
- package/src/message-parts/preview.ts +30 -30
- package/src/message-parts/types.ts +27 -27
- package/src/message-parts/wire.ts +24 -24
- package/src/mutations.ts +2 -2
- package/src/observability.ts +23 -11
- package/src/org/__tests__/org-units.test.ts +131 -96
- package/src/org/__tests__/tree-ordering.test.ts +57 -37
- package/src/org/__tests__/view-scopes.test.ts +40 -40
- package/src/org/domain.ts +9 -9
- package/src/org/index.ts +31 -21
- package/src/org/org-units.ts +34 -20
- package/src/org/schemas.ts +261 -124
- package/src/org/sharing.ts +17 -13
- package/src/org/tree-ordering.ts +3 -1
- package/src/org/types.ts +54 -47
- package/src/org/view-scopes.ts +9 -9
- package/src/permissions/access-levels.ts +7 -2
- package/src/permissions/access-source.ts +6 -6
- package/src/permissions/index.ts +5 -5
- package/src/permissions/orgchart-roles.ts +7 -7
- package/src/permissions/permission-introspection.ts +7 -5
- package/src/permissions/share-api.ts +19 -9
- package/src/pressure.ts +4 -4
- package/src/queryIntent.ts +21 -21
- package/src/ralph/__tests__/prd-groups.test.ts +159 -159
- package/src/ralph/__tests__/prd.test.ts +30 -30
- package/src/ralph/index.ts +3 -8
- package/src/ralph/prd.ts +33 -33
- package/src/ralph/progress.ts +1 -1
- package/src/rate-limit/README.md +4 -4
- package/src/rate-limit/index.ts +3 -3
- package/src/requests.ts +36 -8
- package/src/resource-keys.ts +207 -124
- package/src/resource-registry.ts +5 -5
- package/src/route-builder.ts +3 -3
- package/src/safe-mode.ts +2 -2
- package/src/security/index.ts +4 -4
- package/src/security/org-secrets.ts +13 -9
- package/src/security/secret.ts +3 -3
- package/src/sse.ts +3 -1
- package/src/system/README.md +3 -0
- package/src/system/capabilities.ts +22 -23
- package/src/system/diagram.ts +45 -45
- package/src/system/index.ts +14 -14
- package/src/tiers.ts +1 -1
- package/src/timeouts.ts +1 -1
- package/src/tracing.ts +30 -30
- package/src/types/analytics.ts +2 -2
- package/src/usage/README.md +3 -0
- package/src/usage/execution-types.ts +69 -69
- package/src/usage/types.ts +7 -3
package/src/meetings/schemas.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* descriptions live in `.meta({ description })` — structured metadata, not
|
|
21
21
|
* stringified JSON blobs on the schema.
|
|
22
22
|
*/
|
|
23
|
-
import { z } from
|
|
23
|
+
import { z } from "zod";
|
|
24
24
|
|
|
25
25
|
// =============================================================================
|
|
26
26
|
// Recording identity
|
|
@@ -33,8 +33,10 @@ import { z } from 'zod';
|
|
|
33
33
|
*/
|
|
34
34
|
export const RecordingIdSchema = z
|
|
35
35
|
.string()
|
|
36
|
-
.regex(/^[0-9A-HJKMNP-TV-Z]{26}$/,
|
|
37
|
-
.meta({
|
|
36
|
+
.regex(/^[0-9A-HJKMNP-TV-Z]{26}$/, "ULID required")
|
|
37
|
+
.meta({
|
|
38
|
+
description: "ULID; unified trace key for the recording (INV-MTG-7).",
|
|
39
|
+
});
|
|
38
40
|
export type RecordingId = z.infer<typeof RecordingIdSchema>;
|
|
39
41
|
|
|
40
42
|
// =============================================================================
|
|
@@ -42,8 +44,9 @@ export type RecordingId = z.infer<typeof RecordingIdSchema>;
|
|
|
42
44
|
// =============================================================================
|
|
43
45
|
|
|
44
46
|
/** Which capture stream a chunk came from. Two channels: mic (channel 0) and system (channel 1). */
|
|
45
|
-
export const RecordingSourceSchema = z.enum([
|
|
46
|
-
description:
|
|
47
|
+
export const RecordingSourceSchema = z.enum(["mic", "system"]).meta({
|
|
48
|
+
description:
|
|
49
|
+
"Capture stream identity. Mic = channel 0, system audio = channel 1.",
|
|
47
50
|
});
|
|
48
51
|
export type RecordingSource = z.infer<typeof RecordingSourceSchema>;
|
|
49
52
|
|
|
@@ -54,8 +57,10 @@ export type RecordingSource = z.infer<typeof RecordingSourceSchema>;
|
|
|
54
57
|
* which the BrowserAudioDetector heuristic identifies (PRD-00655).
|
|
55
58
|
*/
|
|
56
59
|
export const DetectedMeetingAppSchema = z
|
|
57
|
-
.enum([
|
|
58
|
-
.meta({
|
|
60
|
+
.enum(["zoom", "teams", "meet-browser", "slack-huddle", "manual"])
|
|
61
|
+
.meta({
|
|
62
|
+
description: "Auto-detected meeting app that owns the system audio stream.",
|
|
63
|
+
});
|
|
59
64
|
export type DetectedMeetingApp = z.infer<typeof DetectedMeetingAppSchema>;
|
|
60
65
|
|
|
61
66
|
/**
|
|
@@ -64,8 +69,11 @@ export type DetectedMeetingApp = z.infer<typeof DetectedMeetingAppSchema>;
|
|
|
64
69
|
* the mac side stays provider-opaque).
|
|
65
70
|
*/
|
|
66
71
|
export const TranscriptionProviderSchema = z
|
|
67
|
-
.enum([
|
|
68
|
-
.meta({
|
|
72
|
+
.enum(["deepgram", "assemblyai", "whisper-local"])
|
|
73
|
+
.meta({
|
|
74
|
+
description:
|
|
75
|
+
"Server-side selection of transcription provider; never crosses to mac.",
|
|
76
|
+
});
|
|
69
77
|
export type TranscriptionProvider = z.infer<typeof TranscriptionProviderSchema>;
|
|
70
78
|
|
|
71
79
|
/**
|
|
@@ -76,20 +84,20 @@ export type TranscriptionProvider = z.infer<typeof TranscriptionProviderSchema>;
|
|
|
76
84
|
*/
|
|
77
85
|
export const RecordingStatusSchema = z
|
|
78
86
|
.enum([
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
"draft",
|
|
88
|
+
"recording",
|
|
89
|
+
"processing",
|
|
90
|
+
"finalized",
|
|
91
|
+
"failed",
|
|
92
|
+
"partial-transcript-network",
|
|
93
|
+
"cancelled",
|
|
86
94
|
])
|
|
87
|
-
.meta({ description:
|
|
95
|
+
.meta({ description: "Coarse lifecycle state for a recording entity." });
|
|
88
96
|
export type RecordingStatus = z.infer<typeof RecordingStatusSchema>;
|
|
89
97
|
|
|
90
98
|
/** Quality signal emitted by the capture runtime. Used to surface degradation in the UI. */
|
|
91
|
-
export const RecordingQualitySchema = z.enum([
|
|
92
|
-
description:
|
|
99
|
+
export const RecordingQualitySchema = z.enum(["clean", "degraded"]).meta({
|
|
100
|
+
description: "Capture-runtime quality signal. `degraded` triggers a UI hint.",
|
|
93
101
|
});
|
|
94
102
|
export type RecordingQuality = z.infer<typeof RecordingQualitySchema>;
|
|
95
103
|
|
|
@@ -102,10 +110,10 @@ export type RecordingQuality = z.infer<typeof RecordingQualitySchema>;
|
|
|
102
110
|
* (`feedback_retrieval_default_off_for_noisy_sources`).
|
|
103
111
|
*/
|
|
104
112
|
export const MeetingVisibilitySchema = z
|
|
105
|
-
.enum([
|
|
113
|
+
.enum(["meeting_only", "shared", "org", "finalized_private"])
|
|
106
114
|
.meta({
|
|
107
115
|
description:
|
|
108
|
-
|
|
116
|
+
"Visibility band for the finalized meeting projection. Default `meeting_only`.",
|
|
109
117
|
});
|
|
110
118
|
export type MeetingVisibility = z.infer<typeof MeetingVisibilitySchema>;
|
|
111
119
|
|
|
@@ -128,7 +136,9 @@ export const SourceSegmentSchema = z
|
|
|
128
136
|
sampleRateHz: z.literal(16000),
|
|
129
137
|
channels: z.literal(1),
|
|
130
138
|
})
|
|
131
|
-
.meta({
|
|
139
|
+
.meta({
|
|
140
|
+
description: "Per-source continuous capture segment within a recording.",
|
|
141
|
+
});
|
|
132
142
|
export type SourceSegment = z.infer<typeof SourceSegmentSchema>;
|
|
133
143
|
|
|
134
144
|
// =============================================================================
|
|
@@ -153,7 +163,10 @@ export const TranscriptChunkSchema = z
|
|
|
153
163
|
speakerLabel: z.string().nullable().optional(),
|
|
154
164
|
confidence: z.number().min(0).max(1).optional(),
|
|
155
165
|
})
|
|
156
|
-
.meta({
|
|
166
|
+
.meta({
|
|
167
|
+
description:
|
|
168
|
+
"A single transcript window (interim or final) from the provider.",
|
|
169
|
+
});
|
|
157
170
|
export type TranscriptChunk = z.infer<typeof TranscriptChunkSchema>;
|
|
158
171
|
|
|
159
172
|
// =============================================================================
|
|
@@ -161,26 +174,26 @@ export type TranscriptChunk = z.infer<typeof TranscriptChunkSchema>;
|
|
|
161
174
|
// =============================================================================
|
|
162
175
|
|
|
163
176
|
const SessionOpenedEventSchema = z.object({
|
|
164
|
-
kind: z.literal(
|
|
177
|
+
kind: z.literal("session_opened"),
|
|
165
178
|
recordingId: RecordingIdSchema,
|
|
166
179
|
atMs: z.number().int().nonnegative(),
|
|
167
180
|
});
|
|
168
181
|
|
|
169
182
|
const ChunkBatchEventSchema = z.object({
|
|
170
|
-
kind: z.literal(
|
|
183
|
+
kind: z.literal("chunk_batch"),
|
|
171
184
|
recordingId: RecordingIdSchema,
|
|
172
185
|
chunks: z.array(TranscriptChunkSchema).min(1),
|
|
173
186
|
});
|
|
174
187
|
|
|
175
188
|
const QualityDegradedEventSchema = z.object({
|
|
176
|
-
kind: z.literal(
|
|
189
|
+
kind: z.literal("quality_degraded"),
|
|
177
190
|
recordingId: RecordingIdSchema,
|
|
178
191
|
reason: z.string().min(1),
|
|
179
192
|
atMs: z.number().int().nonnegative(),
|
|
180
193
|
});
|
|
181
194
|
|
|
182
195
|
const SessionStoppedEventSchema = z.object({
|
|
183
|
-
kind: z.literal(
|
|
196
|
+
kind: z.literal("session_stopped"),
|
|
184
197
|
recordingId: RecordingIdSchema,
|
|
185
198
|
atMs: z.number().int().nonnegative(),
|
|
186
199
|
expectedLastSequence: z.number().int().nonnegative(),
|
|
@@ -196,7 +209,7 @@ const SessionStoppedEventSchema = z.object({
|
|
|
196
209
|
* `quality_degraded` may interleave.
|
|
197
210
|
*/
|
|
198
211
|
export const RecordingEventSchema = z
|
|
199
|
-
.discriminatedUnion(
|
|
212
|
+
.discriminatedUnion("kind", [
|
|
200
213
|
SessionOpenedEventSchema,
|
|
201
214
|
ChunkBatchEventSchema,
|
|
202
215
|
QualityDegradedEventSchema,
|
|
@@ -204,7 +217,7 @@ export const RecordingEventSchema = z
|
|
|
204
217
|
])
|
|
205
218
|
.meta({
|
|
206
219
|
description:
|
|
207
|
-
|
|
220
|
+
"Event emitted by the capture runtime during a recording session (INV-MTG ordering).",
|
|
208
221
|
});
|
|
209
222
|
export type RecordingEvent = z.infer<typeof RecordingEventSchema>;
|
|
210
223
|
|
|
@@ -228,7 +241,7 @@ export const TranscriptionSessionGrantSchema = z
|
|
|
228
241
|
audioConfig: z.object({
|
|
229
242
|
sampleRateHz: z.literal(16000),
|
|
230
243
|
channels: z.literal(2),
|
|
231
|
-
encoding: z.literal(
|
|
244
|
+
encoding: z.literal("linear16"),
|
|
232
245
|
chunkMs: z.literal(200),
|
|
233
246
|
}),
|
|
234
247
|
capabilities: z.object({
|
|
@@ -239,9 +252,11 @@ export const TranscriptionSessionGrantSchema = z
|
|
|
239
252
|
})
|
|
240
253
|
.meta({
|
|
241
254
|
description:
|
|
242
|
-
|
|
255
|
+
"Provider-agnostic grant the mac shell consumes. Mac never sees provider name (INV-MTG-15).",
|
|
243
256
|
});
|
|
244
|
-
export type TranscriptionSessionGrant = z.infer<
|
|
257
|
+
export type TranscriptionSessionGrant = z.infer<
|
|
258
|
+
typeof TranscriptionSessionGrantSchema
|
|
259
|
+
>;
|
|
245
260
|
|
|
246
261
|
// =============================================================================
|
|
247
262
|
// Meeting metadata projection (finalized read model)
|
|
@@ -271,6 +286,8 @@ export const MeetingMetadataProjectionSchema = z
|
|
|
271
286
|
transcriptAvailable: z.boolean(),
|
|
272
287
|
})
|
|
273
288
|
.meta({
|
|
274
|
-
description:
|
|
289
|
+
description: "Finalized read projection for a recording (excludes drafts).",
|
|
275
290
|
});
|
|
276
|
-
export type MeetingMetadataProjection = z.infer<
|
|
291
|
+
export type MeetingMetadataProjection = z.infer<
|
|
292
|
+
typeof MeetingMetadataProjectionSchema
|
|
293
|
+
>;
|
|
@@ -18,6 +18,7 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
18
18
|
## Public API
|
|
19
19
|
|
|
20
20
|
**Types:**
|
|
21
|
+
|
|
21
22
|
- `TextPart`, `SurfacePart`, `AssistantMessagePart` — core part unions
|
|
22
23
|
- `ToolListPart`, `StatusPanelPart`, `ChartPart`, `TablePart` — surface part variants
|
|
23
24
|
- `PreviewData`, `PreviewArtifact`, `PreviewPart` — action preview surfaces
|
|
@@ -27,6 +28,7 @@ Canonical vocabulary for structured assistant message output. Defines the type s
|
|
|
27
28
|
- `IntegrationKey`, `IntegrationProvider` — integration identifiers
|
|
28
29
|
|
|
29
30
|
**Runtime values:**
|
|
31
|
+
|
|
30
32
|
- `CONFIRMATION_LABELS` — exhaustive map of ExecutionKind to human-readable labels
|
|
31
33
|
- `getConfirmationLabel(kind, fallback)` — label lookup with fallback
|
|
32
34
|
- `WireSurfaceBuilder` — factory for creating wire-format data parts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect } from
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
createPartBuilder,
|
|
4
4
|
addText,
|
|
@@ -6,176 +6,176 @@ import {
|
|
|
6
6
|
addPart,
|
|
7
7
|
buildParts,
|
|
8
8
|
getDroppedCount,
|
|
9
|
-
} from
|
|
10
|
-
import type { SurfacePart, TextPart } from
|
|
9
|
+
} from "../builder.js";
|
|
10
|
+
import type { SurfacePart, TextPart } from "../types.js";
|
|
11
11
|
|
|
12
12
|
const mockSurface: SurfacePart = {
|
|
13
|
-
type:
|
|
13
|
+
type: "tool-list",
|
|
14
14
|
tools: [],
|
|
15
|
-
} as unknown as SurfacePart
|
|
15
|
+
} as unknown as SurfacePart;
|
|
16
16
|
|
|
17
17
|
const anotherSurface: SurfacePart = {
|
|
18
|
-
type:
|
|
19
|
-
title:
|
|
18
|
+
type: "status-panel",
|
|
19
|
+
title: "Status",
|
|
20
20
|
entries: [],
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
// =============================================================================
|
|
24
24
|
// Builder Creation
|
|
25
25
|
// =============================================================================
|
|
26
26
|
|
|
27
|
-
describe(
|
|
28
|
-
it(
|
|
29
|
-
const state = createPartBuilder()
|
|
30
|
-
expect(state.parts).toEqual([])
|
|
31
|
-
expect(state.hasSurface).toBe(false)
|
|
32
|
-
expect(state.devMode).toBe(false)
|
|
33
|
-
expect(state.droppedCount).toBe(0)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it(
|
|
37
|
-
const state = createPartBuilder(true)
|
|
38
|
-
expect(state.devMode).toBe(true)
|
|
39
|
-
expect(state.parts).toEqual([])
|
|
40
|
-
expect(state.hasSurface).toBe(false)
|
|
41
|
-
expect(state.droppedCount).toBe(0)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
27
|
+
describe("createPartBuilder", () => {
|
|
28
|
+
it("returns initial state with defaults", () => {
|
|
29
|
+
const state = createPartBuilder();
|
|
30
|
+
expect(state.parts).toEqual([]);
|
|
31
|
+
expect(state.hasSurface).toBe(false);
|
|
32
|
+
expect(state.devMode).toBe(false);
|
|
33
|
+
expect(state.droppedCount).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("returns state with devMode=true when passed true", () => {
|
|
37
|
+
const state = createPartBuilder(true);
|
|
38
|
+
expect(state.devMode).toBe(true);
|
|
39
|
+
expect(state.parts).toEqual([]);
|
|
40
|
+
expect(state.hasSurface).toBe(false);
|
|
41
|
+
expect(state.droppedCount).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
44
|
|
|
45
45
|
// =============================================================================
|
|
46
46
|
// NarrativeState (before any surface)
|
|
47
47
|
// =============================================================================
|
|
48
48
|
|
|
49
|
-
describe(
|
|
50
|
-
it(
|
|
51
|
-
const state = createPartBuilder()
|
|
52
|
-
const result = addText(state,
|
|
53
|
-
expect(result.accepted).toBe(true)
|
|
54
|
-
expect(result.state.parts).toEqual([{ type:
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it(
|
|
58
|
-
let state = createPartBuilder()
|
|
59
|
-
state = addText(state,
|
|
60
|
-
state = addText(state,
|
|
61
|
-
state = addText(state,
|
|
49
|
+
describe("NarrativeState", () => {
|
|
50
|
+
it("addText is accepted and appends text part", () => {
|
|
51
|
+
const state = createPartBuilder();
|
|
52
|
+
const result = addText(state, "hello");
|
|
53
|
+
expect(result.accepted).toBe(true);
|
|
54
|
+
expect(result.state.parts).toEqual([{ type: "text", text: "hello" }]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("multiple addText calls accumulate parts in order", () => {
|
|
58
|
+
let state = createPartBuilder();
|
|
59
|
+
state = addText(state, "first").state;
|
|
60
|
+
state = addText(state, "second").state;
|
|
61
|
+
state = addText(state, "third").state;
|
|
62
62
|
expect(state.parts).toEqual([
|
|
63
|
-
{ type:
|
|
64
|
-
{ type:
|
|
65
|
-
{ type:
|
|
66
|
-
])
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it(
|
|
70
|
-
const state = createPartBuilder()
|
|
71
|
-
const result = addSurface(state, mockSurface)
|
|
72
|
-
expect(result.accepted).toBe(true)
|
|
73
|
-
expect(result.state.hasSurface).toBe(true)
|
|
74
|
-
})
|
|
75
|
-
})
|
|
63
|
+
{ type: "text", text: "first" },
|
|
64
|
+
{ type: "text", text: "second" },
|
|
65
|
+
{ type: "text", text: "third" },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("addSurface is accepted and transitions hasSurface to true", () => {
|
|
70
|
+
const state = createPartBuilder();
|
|
71
|
+
const result = addSurface(state, mockSurface);
|
|
72
|
+
expect(result.accepted).toBe(true);
|
|
73
|
+
expect(result.state.hasSurface).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
76
|
|
|
77
77
|
// =============================================================================
|
|
78
78
|
// SurfaceState — THE CRITICAL INVARIANT
|
|
79
79
|
// =============================================================================
|
|
80
80
|
|
|
81
|
-
describe(
|
|
82
|
-
it(
|
|
83
|
-
let state = createPartBuilder(false)
|
|
84
|
-
state = addSurface(state, mockSurface).state
|
|
85
|
-
const partsBefore = state.parts.length
|
|
86
|
-
|
|
87
|
-
const result = addText(state,
|
|
88
|
-
expect(result.accepted).toBe(false)
|
|
89
|
-
expect(result.state.droppedCount).toBe(1)
|
|
90
|
-
expect(result.state.parts.length).toBe(partsBefore)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it(
|
|
94
|
-
let state = createPartBuilder(true)
|
|
95
|
-
state = addSurface(state, mockSurface).state
|
|
96
|
-
|
|
97
|
-
expect(() => addText(state,
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it(
|
|
101
|
-
let state = createPartBuilder()
|
|
102
|
-
state = addSurface(state, mockSurface).state
|
|
103
|
-
const result = addSurface(state, anotherSurface)
|
|
104
|
-
expect(result.accepted).toBe(true)
|
|
105
|
-
expect(result.state.parts.length).toBe(2)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it(
|
|
109
|
-
let state = createPartBuilder(false)
|
|
110
|
-
state = addSurface(state, mockSurface).state
|
|
111
|
-
|
|
112
|
-
state = addText(state,
|
|
113
|
-
state = addText(state,
|
|
114
|
-
state = addText(state,
|
|
115
|
-
|
|
116
|
-
expect(state.droppedCount).toBe(3)
|
|
117
|
-
})
|
|
118
|
-
})
|
|
81
|
+
describe("SurfaceState (text after surface)", () => {
|
|
82
|
+
it("silently drops text in prod mode (devMode=false)", () => {
|
|
83
|
+
let state = createPartBuilder(false);
|
|
84
|
+
state = addSurface(state, mockSurface).state;
|
|
85
|
+
const partsBefore = state.parts.length;
|
|
86
|
+
|
|
87
|
+
const result = addText(state, "dropped");
|
|
88
|
+
expect(result.accepted).toBe(false);
|
|
89
|
+
expect(result.state.droppedCount).toBe(1);
|
|
90
|
+
expect(result.state.parts.length).toBe(partsBefore);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("throws invariant violation in dev mode (devMode=true)", () => {
|
|
94
|
+
let state = createPartBuilder(true);
|
|
95
|
+
state = addSurface(state, mockSurface).state;
|
|
96
|
+
|
|
97
|
+
expect(() => addText(state, "illegal")).toThrow("invariant violation");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("addSurface after addSurface is always accepted", () => {
|
|
101
|
+
let state = createPartBuilder();
|
|
102
|
+
state = addSurface(state, mockSurface).state;
|
|
103
|
+
const result = addSurface(state, anotherSurface);
|
|
104
|
+
expect(result.accepted).toBe(true);
|
|
105
|
+
expect(result.state.parts.length).toBe(2);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("droppedCount accumulates across multiple drops in prod mode", () => {
|
|
109
|
+
let state = createPartBuilder(false);
|
|
110
|
+
state = addSurface(state, mockSurface).state;
|
|
111
|
+
|
|
112
|
+
state = addText(state, "drop1").state;
|
|
113
|
+
state = addText(state, "drop2").state;
|
|
114
|
+
state = addText(state, "drop3").state;
|
|
115
|
+
|
|
116
|
+
expect(state.droppedCount).toBe(3);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
119
|
|
|
120
120
|
// =============================================================================
|
|
121
121
|
// addPart dispatch
|
|
122
122
|
// =============================================================================
|
|
123
123
|
|
|
124
|
-
describe(
|
|
125
|
-
it(
|
|
126
|
-
const state = createPartBuilder()
|
|
127
|
-
const textPart: TextPart = { type:
|
|
128
|
-
const result = addPart(state, textPart)
|
|
129
|
-
expect(result.accepted).toBe(true)
|
|
130
|
-
expect(result.state.parts).toEqual([{ type:
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it(
|
|
134
|
-
const state = createPartBuilder()
|
|
135
|
-
const result = addPart(state, mockSurface)
|
|
136
|
-
expect(result.accepted).toBe(true)
|
|
137
|
-
expect(result.state.hasSurface).toBe(true)
|
|
138
|
-
})
|
|
139
|
-
})
|
|
124
|
+
describe("addPart", () => {
|
|
125
|
+
it("dispatches TextPart to addText behavior", () => {
|
|
126
|
+
const state = createPartBuilder();
|
|
127
|
+
const textPart: TextPart = { type: "text", text: "dispatched" };
|
|
128
|
+
const result = addPart(state, textPart);
|
|
129
|
+
expect(result.accepted).toBe(true);
|
|
130
|
+
expect(result.state.parts).toEqual([{ type: "text", text: "dispatched" }]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("dispatches SurfacePart to addSurface behavior", () => {
|
|
134
|
+
const state = createPartBuilder();
|
|
135
|
+
const result = addPart(state, mockSurface);
|
|
136
|
+
expect(result.accepted).toBe(true);
|
|
137
|
+
expect(result.state.hasSurface).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
140
|
|
|
141
141
|
// =============================================================================
|
|
142
142
|
// Finalization
|
|
143
143
|
// =============================================================================
|
|
144
144
|
|
|
145
|
-
describe(
|
|
146
|
-
it(
|
|
147
|
-
let state = createPartBuilder()
|
|
148
|
-
state = addText(state,
|
|
149
|
-
state = addSurface(state, mockSurface).state
|
|
150
|
-
|
|
151
|
-
const parts = buildParts(state)
|
|
152
|
-
parts.push({ type:
|
|
153
|
-
|
|
154
|
-
expect(buildParts(state)).toHaveLength(2)
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it(
|
|
158
|
-
let state = createPartBuilder()
|
|
159
|
-
state = addText(state,
|
|
160
|
-
state = addSurface(state, mockSurface).state
|
|
161
|
-
|
|
162
|
-
const parts = buildParts(state)
|
|
163
|
-
expect(parts[0].type).toBe(
|
|
164
|
-
expect(parts[1].type).not.toBe(
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
describe(
|
|
169
|
-
it(
|
|
170
|
-
let state = createPartBuilder(false)
|
|
171
|
-
state = addSurface(state, mockSurface).state
|
|
172
|
-
state = addText(state,
|
|
173
|
-
|
|
174
|
-
expect(getDroppedCount(state)).toBe(1)
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it(
|
|
178
|
-
const state = createPartBuilder()
|
|
179
|
-
expect(getDroppedCount(state)).toBe(0)
|
|
180
|
-
})
|
|
181
|
-
})
|
|
145
|
+
describe("buildParts", () => {
|
|
146
|
+
it("returns array copy — mutating result does not affect builder", () => {
|
|
147
|
+
let state = createPartBuilder();
|
|
148
|
+
state = addText(state, "text1").state;
|
|
149
|
+
state = addSurface(state, mockSurface).state;
|
|
150
|
+
|
|
151
|
+
const parts = buildParts(state);
|
|
152
|
+
parts.push({ type: "text", text: "injected" });
|
|
153
|
+
|
|
154
|
+
expect(buildParts(state)).toHaveLength(2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("preserves correct part ordering (text before surfaces)", () => {
|
|
158
|
+
let state = createPartBuilder();
|
|
159
|
+
state = addText(state, "narrative").state;
|
|
160
|
+
state = addSurface(state, mockSurface).state;
|
|
161
|
+
|
|
162
|
+
const parts = buildParts(state);
|
|
163
|
+
expect(parts[0].type).toBe("text");
|
|
164
|
+
expect(parts[1].type).not.toBe("text");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("getDroppedCount", () => {
|
|
169
|
+
it("returns current droppedCount from state", () => {
|
|
170
|
+
let state = createPartBuilder(false);
|
|
171
|
+
state = addSurface(state, mockSurface).state;
|
|
172
|
+
state = addText(state, "dropped").state;
|
|
173
|
+
|
|
174
|
+
expect(getDroppedCount(state)).toBe(1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("returns 0 when no drops have occurred", () => {
|
|
178
|
+
const state = createPartBuilder();
|
|
179
|
+
expect(getDroppedCount(state)).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
});
|