@qontinui/shared-types 0.2.5 → 0.3.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 (65) hide show
  1. package/dist/index.cjs.map +1 -1
  2. package/dist/index.d.cts +43 -1
  3. package/dist/index.d.ts +43 -1
  4. package/dist/ui-bridge-ir.cjs +30 -33
  5. package/dist/ui-bridge-ir.cjs.map +1 -1
  6. package/dist/ui-bridge-ir.d.cts +98 -84
  7. package/dist/ui-bridge-ir.d.ts +98 -84
  8. package/dist/ui-bridge-ir.js +30 -33
  9. package/dist/ui-bridge-ir.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/generated/AssertionMiss.d.ts +22 -0
  12. package/src/generated/AssertionOutcome.d.ts +30 -0
  13. package/src/generated/AssertionResult.d.ts +38 -0
  14. package/src/generated/AssertionScope.d.ts +37 -0
  15. package/src/generated/AssertionSeverityCounts.d.ts +28 -0
  16. package/src/generated/BridgeFingerprint.d.ts +40 -0
  17. package/src/generated/CandidateMiss.d.ts +39 -0
  18. package/src/generated/ConjunctEvaluation.d.ts +24 -0
  19. package/src/generated/ConjunctRule.d.ts +55 -0
  20. package/src/generated/FieldDiff.d.ts +33 -0
  21. package/src/generated/IrAssertion.d.ts +26 -0
  22. package/src/generated/IrAssertionTarget.d.ts +28 -0
  23. package/src/generated/IrCrossRef.d.ts +16 -0
  24. package/src/generated/IrElementCriteria.d.ts +29 -0
  25. package/src/generated/IrGroup.d.ts +33 -0
  26. package/src/generated/IrMetadata.d.ts +15 -0
  27. package/src/generated/IrPageSpec.d.ts +51 -0
  28. package/src/generated/IrProvenance.d.ts +28 -0
  29. package/src/generated/IrState.d.ts +36 -0
  30. package/src/generated/IrStateCondition.d.ts +19 -0
  31. package/src/generated/IrTransition.d.ts +34 -0
  32. package/src/generated/IrTransitionAction.d.ts +17 -0
  33. package/src/generated/IrWaitSpec.d.ts +22 -0
  34. package/src/generated/LegacyAssertion.d.ts +22 -0
  35. package/src/generated/LegacyAssertionTarget.d.ts +23 -0
  36. package/src/generated/LegacyGroup.d.ts +19 -0
  37. package/src/generated/LegacyProcessStep.d.ts +16 -0
  38. package/src/generated/LegacySpec.d.ts +30 -0
  39. package/src/generated/LegacyStateMachine.d.ts +17 -0
  40. package/src/generated/LegacyStateMachineState.d.ts +21 -0
  41. package/src/generated/LegacyTransition.d.ts +20 -0
  42. package/src/generated/MatchOutcome.d.ts +11 -0
  43. package/src/generated/MatchedElement.d.ts +28 -0
  44. package/src/generated/MissReason.d.ts +20 -0
  45. package/src/generated/PolicyConjunct.d.ts +22 -0
  46. package/src/generated/PolicyEvaluation.d.ts +22 -0
  47. package/src/generated/PolicyStatus.d.ts +11 -0
  48. package/src/generated/ProposalStatus.d.ts +13 -0
  49. package/src/generated/RecommendedState.d.ts +23 -0
  50. package/src/generated/SpecCheckConfidence.d.ts +15 -0
  51. package/src/generated/SpecCheckPolicy.d.ts +23 -0
  52. package/src/generated/SpecCheckResult.d.ts +66 -0
  53. package/src/generated/SpecCheckStepConfig.d.ts +38 -0
  54. package/src/generated/SpecCheckSummary.d.ts +28 -0
  55. package/src/generated/SpecValidation.d.ts +27 -0
  56. package/src/generated/StateMatchResult.d.ts +36 -0
  57. package/src/generated/UIBridgeElement.d.ts +28 -0
  58. package/src/generated/index.ts +46 -0
  59. package/src/index.ts +5 -0
  60. package/src/ui-bridge-ir/__tests__/projection.test.ts +114 -22
  61. package/src/ui-bridge-ir/adapter.ts +7 -1
  62. package/src/ui-bridge-ir/ir.schema.json +8 -4
  63. package/src/ui-bridge-ir/primitives.ts +8 -0
  64. package/src/ui-bridge-ir/projection.ts +34 -57
  65. package/src/ui-bridge-ir/state.ts +9 -2
@@ -27,6 +27,11 @@ export type { ApiAssertionType } from './ApiAssertionType';
27
27
  export type { ApiContentType } from './ApiContentType';
28
28
  export type { ApiVariableExtraction } from './ApiVariableExtraction';
29
29
  export type { AppEvent } from './AppEvent';
30
+ export type { AssertionMiss } from './AssertionMiss';
31
+ export type { AssertionOutcome } from './AssertionOutcome';
32
+ export type { AssertionResult } from './AssertionResult';
33
+ export type { AssertionScope } from './AssertionScope';
34
+ export type { AssertionSeverityCounts } from './AssertionSeverityCounts';
30
35
  export type { AvailableTransitionsResult } from './AvailableTransitionsResult';
31
36
  export type { BaseStepFields } from './BaseStepFields';
32
37
  export type { BatchComputeEmbeddingRequest } from './BatchComputeEmbeddingRequest';
@@ -34,8 +39,10 @@ export type { BatchComputeEmbeddingResponse } from './BatchComputeEmbeddingRespo
34
39
  export type { BatchEmbeddingResult } from './BatchEmbeddingResult';
35
40
  export type { BetweenIterations } from './BetweenIterations';
36
41
  export type { BoundingBox } from './BoundingBox';
42
+ export type { BridgeFingerprint } from './BridgeFingerprint';
37
43
  export type { BuildPhaseConfig } from './BuildPhaseConfig';
38
44
  export type { BuiltinOverrideProposal } from './BuiltinOverrideProposal';
45
+ export type { CandidateMiss } from './CandidateMiss';
39
46
  export type { CanonicalStep } from './CanonicalStep';
40
47
  export type { CanvasPanel } from './CanvasPanel';
41
48
  export type { CatchUpPolicy } from './CatchUpPolicy';
@@ -56,6 +63,8 @@ export type { ConditionScheduleConfig } from './ConditionScheduleConfig';
56
63
  export type { ConditionStatus } from './ConditionStatus';
57
64
  export type { Confidence } from './Confidence';
58
65
  export type { ConfidenceLevel } from './ConfidenceLevel';
66
+ export type { ConjunctEvaluation } from './ConjunctEvaluation';
67
+ export type { ConjunctRule } from './ConjunctRule';
59
68
  export type { Constraint } from './Constraint';
60
69
  export type { ConstraintCheck } from './ConstraintCheck';
61
70
  export type { ConstraintProposal } from './ConstraintProposal';
@@ -137,6 +146,7 @@ export type { ExitCheckResult } from './ExitCheckResult';
137
146
  export type { ExitStrategy } from './ExitStrategy';
138
147
  export type { ExportResult } from './ExportResult';
139
148
  export type { ExtendIterationsRequest } from './ExtendIterationsRequest';
149
+ export type { FieldDiff } from './FieldDiff';
140
150
  export type { FileChange } from './FileChange';
141
151
  export type { Finding } from './Finding';
142
152
  export type { FindingActionType } from './FindingActionType';
@@ -167,6 +177,19 @@ export type { ImplementFixesConfig } from './ImplementFixesConfig';
167
177
  export type { IndividualCheckResult } from './IndividualCheckResult';
168
178
  export type { InitialStateRef } from './InitialStateRef';
169
179
  export type { InitialStatesSource } from './InitialStatesSource';
180
+ export type { IrAssertion } from './IrAssertion';
181
+ export type { IrAssertionTarget } from './IrAssertionTarget';
182
+ export type { IrCrossRef } from './IrCrossRef';
183
+ export type { IrElementCriteria } from './IrElementCriteria';
184
+ export type { IrGroup } from './IrGroup';
185
+ export type { IrMetadata } from './IrMetadata';
186
+ export type { IrPageSpec } from './IrPageSpec';
187
+ export type { IrProvenance } from './IrProvenance';
188
+ export type { IrState } from './IrState';
189
+ export type { IrStateCondition } from './IrStateCondition';
190
+ export type { IrTransition } from './IrTransition';
191
+ export type { IrTransitionAction } from './IrTransitionAction';
192
+ export type { IrWaitSpec } from './IrWaitSpec';
170
193
  export type { IssueSeverity } from './IssueSeverity';
171
194
  export type { IssueSource } from './IssueSource';
172
195
  export type { IssueStatus } from './IssueStatus';
@@ -179,12 +202,22 @@ export type { JobStatus } from './JobStatus';
179
202
  export type { JobSummary } from './JobSummary';
180
203
  export type { LLMCostSummary } from './LLMCostSummary';
181
204
  export type { LLMMetrics } from './LLMMetrics';
205
+ export type { LegacyAssertion } from './LegacyAssertion';
206
+ export type { LegacyAssertionTarget } from './LegacyAssertionTarget';
207
+ export type { LegacyGroup } from './LegacyGroup';
208
+ export type { LegacyProcessStep } from './LegacyProcessStep';
209
+ export type { LegacySpec } from './LegacySpec';
210
+ export type { LegacyStateMachine } from './LegacyStateMachine';
211
+ export type { LegacyStateMachineState } from './LegacyStateMachineState';
212
+ export type { LegacyTransition } from './LegacyTransition';
182
213
  export type { LogSourceSelection } from './LogSourceSelection';
183
214
  export type { LoopInstanceStatus } from './LoopInstanceStatus';
184
215
  export type { LoopPhase } from './LoopPhase';
185
216
  export type { MatchAdjustment } from './MatchAdjustment';
186
217
  export type { MatchLocation } from './MatchLocation';
187
218
  export type { MatchMethod } from './MatchMethod';
219
+ export type { MatchOutcome } from './MatchOutcome';
220
+ export type { MatchedElement } from './MatchedElement';
188
221
  export type { McpCallRecord } from './McpCallRecord';
189
222
  export type { McpCallsResult } from './McpCallsResult';
190
223
  export type { McpConnectionRef } from './McpConnectionRef';
@@ -194,6 +227,7 @@ export type { McpToolCallResult } from './McpToolCallResult';
194
227
  export type { McpToolInfo } from './McpToolInfo';
195
228
  export type { McpToolInputSchema } from './McpToolInputSchema';
196
229
  export type { McpTransport } from './McpTransport';
230
+ export type { MissReason } from './MissReason';
197
231
  export type { ModelCostBreakdown } from './ModelCostBreakdown';
198
232
  export type { ModelOverrideConfig } from './ModelOverrideConfig';
199
233
  export type { Monitor } from './Monitor';
@@ -228,18 +262,23 @@ export type { PipelineConfig } from './PipelineConfig';
228
262
  export type { PlaybackFrameRequest } from './PlaybackFrameRequest';
229
263
  export type { PlaywrightExecutionMode } from './PlaywrightExecutionMode';
230
264
  export type { Point } from './Point';
265
+ export type { PolicyConjunct } from './PolicyConjunct';
266
+ export type { PolicyEvaluation } from './PolicyEvaluation';
267
+ export type { PolicyStatus } from './PolicyStatus';
231
268
  export type { PollingConfig } from './PollingConfig';
232
269
  export type { ProcessConfig } from './ProcessConfig';
233
270
  export type { ProcessState } from './ProcessState';
234
271
  export type { ProcessStatus } from './ProcessStatus';
235
272
  export type { PromptStep } from './PromptStep';
236
273
  export type { PromptStepPhase } from './PromptStepPhase';
274
+ export type { ProposalStatus } from './ProposalStatus';
237
275
  export type { RAGDashboardStats } from './RAGDashboardStats';
238
276
  export type { RagCompletionEvent } from './RagCompletionEvent';
239
277
  export type { RagProcessingStatus } from './RagProcessingStatus';
240
278
  export type { RagProgressEvent } from './RagProgressEvent';
241
279
  export type { ReadConfigResponse } from './ReadConfigResponse';
242
280
  export type { RecommendationReviewDecisionPayload } from './RecommendationReviewDecisionPayload';
281
+ export type { RecommendedState } from './RecommendedState';
243
282
  export type { ReflectionFixOutput } from './ReflectionFixOutput';
244
283
  export type { Region } from './Region';
245
284
  export type { RepositoryInactiveCondition } from './RepositoryInactiveCondition';
@@ -287,6 +326,12 @@ export type { SearchResultItem } from './SearchResultItem';
287
326
  export type { SearchStrategy } from './SearchStrategy';
288
327
  export type { SemanticSearchRequest } from './SemanticSearchRequest';
289
328
  export type { SemanticSearchResponse } from './SemanticSearchResponse';
329
+ export type { SpecCheckConfidence } from './SpecCheckConfidence';
330
+ export type { SpecCheckPolicy } from './SpecCheckPolicy';
331
+ export type { SpecCheckResult } from './SpecCheckResult';
332
+ export type { SpecCheckStepConfig } from './SpecCheckStepConfig';
333
+ export type { SpecCheckSummary } from './SpecCheckSummary';
334
+ export type { SpecValidation } from './SpecValidation';
290
335
  export type { StageCondition } from './StageCondition';
291
336
  export type { StageInput } from './StageInput';
292
337
  export type { StageOutput } from './StageOutput';
@@ -313,6 +358,7 @@ export type { StateMachineStateUpdate } from './StateMachineStateUpdate';
313
358
  export type { StateMachineTransition } from './StateMachineTransition';
314
359
  export type { StateMachineTransitionCreate } from './StateMachineTransitionCreate';
315
360
  export type { StateMachineTransitionUpdate } from './StateMachineTransitionUpdate';
361
+ export type { StateMatchResult } from './StateMatchResult';
316
362
  export type { StateNodeData } from './StateNodeData';
317
363
  export type { StatesResponse } from './StatesResponse';
318
364
  export type { StdioConfig } from './StdioConfig';
package/src/index.ts CHANGED
@@ -7,6 +7,11 @@
7
7
  // Render logging types
8
8
  export * from "./render-log";
9
9
 
10
+ // Spec-check distinctness validation (Plan 04). Surfaced from the
11
+ // auto-generated Rust mirror — see qontinui-schemas/rust/src/spec_check.rs.
12
+ export type { SpecValidation } from "./generated/SpecValidation";
13
+ export type { ProposalStatus } from "./generated/ProposalStatus";
14
+
10
15
  // State discovery types
11
16
  export * from "./discovery";
12
17
 
@@ -13,6 +13,8 @@
13
13
  import { describe, expect, it } from "vitest";
14
14
 
15
15
  import type { IRDocument } from "../document";
16
+ import type { IRElementCriteria } from "../element-criteria";
17
+ import type { IRAssertion } from "../group";
16
18
  import {
17
19
  projectIRToBundledPage,
18
20
  projectLegacyToIR,
@@ -24,6 +26,74 @@ import {
24
26
  // Fixtures
25
27
  // ---------------------------------------------------------------------------
26
28
 
29
+ /**
30
+ * Convert IR-shaped element criteria into the legacy criteria record stored on
31
+ * `IRAssertion.target.criteria`. Mirrors `projection.ts:convertCriteria`'s
32
+ * rename map (text -> textContent, ariaLabel -> accessibleName when no
33
+ * accessibleName is set, attributes -> dataAttributes). The forward projection
34
+ * passes `target.criteria` through verbatim into the legacy group output, so
35
+ * fixtures store criteria in their legacy on-disk shape.
36
+ */
37
+ function toLegacyCriteria(c: IRElementCriteria): Record<string, unknown> {
38
+ const out: Record<string, unknown> = {};
39
+ if (c.role !== undefined) out.role = c.role;
40
+ if (c.tagName !== undefined) out.tagName = c.tagName;
41
+ if (c.text !== undefined) out.textContent = c.text;
42
+ if (c.textContains !== undefined) out.textContains = c.textContains;
43
+ if (c.accessibleName !== undefined) {
44
+ out.accessibleName = c.accessibleName;
45
+ } else if (c.ariaLabel !== undefined) {
46
+ out.accessibleName = c.ariaLabel;
47
+ }
48
+ if (c.id !== undefined) out.id = c.id;
49
+ if (c.attributes !== undefined) out.dataAttributes = c.attributes;
50
+ return out;
51
+ }
52
+
53
+ /**
54
+ * Build the canonical IR-side assertion list for a state from a flat array of
55
+ * IR element criteria. Mirrors the shape the projection emits for state-derived
56
+ * groups so the fixtures stay terse — one criteria per assertion, with all the
57
+ * legacy required fields (id/category/severity/...) defaulted the same way the
58
+ * forward projection used to defaulted them when synthesizing assertions from
59
+ * `requiredElements`. Criteria are stored in legacy on-disk form (`textContent`,
60
+ * `accessibleName`, `dataAttributes`) because `IRAssertion.target.criteria` is
61
+ * already the legacy criteria record — the forward projection passes it through
62
+ * verbatim into `group.assertions[].target.criteria`.
63
+ */
64
+ function assertionsFromCriteria(
65
+ stateId: string,
66
+ stateName: string,
67
+ criteria: IRElementCriteria[],
68
+ ): IRAssertion[] {
69
+ return criteria.map((c, index) => ({
70
+ id: `${stateId}-elem-${index}`,
71
+ description: `Required element ${index} for state ${stateName}`,
72
+ category: "element-presence",
73
+ severity: "critical",
74
+ assertionType: "exists",
75
+ target: {
76
+ type: "search",
77
+ criteria: toLegacyCriteria(c),
78
+ label: `Required element for ${stateName}`,
79
+ },
80
+ source: "ai-generated",
81
+ reviewed: false,
82
+ enabled: true,
83
+ }));
84
+ }
85
+
86
+ /**
87
+ * Pluck the legacy-criteria record off an assertion produced by the inverse
88
+ * projection. The IR side stores criteria in IR form post-inversion (the
89
+ * projection's `invertCriteria` renames `textContent -> text`,
90
+ * `dataAttributes -> attributes`), so the returned object is shaped like
91
+ * `IRElementCriteria`.
92
+ */
93
+ function criteriaOf(assertion: IRAssertion): IRElementCriteria {
94
+ return assertion.target.criteria as unknown as IRElementCriteria;
95
+ }
96
+
27
97
  /**
28
98
  * Slice of `qontinui-runner/src/specs/active.spec.uibridge.json` mapped back
29
99
  * to IR. Three to four states, transitions between them, a few criteria
@@ -44,21 +114,27 @@ const ACTIVE_PAGE_FIXTURE: IRDocument = {
44
114
  id: "idle",
45
115
  name: "Idle",
46
116
  description: "No workflow is running.",
47
- requiredElements: [{ text: "No Active Workflow" }],
117
+ assertions: assertionsFromCriteria("idle", "Idle", [
118
+ { text: "No Active Workflow" },
119
+ ]),
48
120
  isInitial: true,
49
121
  },
50
122
  {
51
123
  id: "running",
52
124
  name: "Running",
53
125
  description: "A workflow is actively executing.",
54
- requiredElements: [{ role: "button", text: "Stop" }],
126
+ assertions: assertionsFromCriteria("running", "Running", [
127
+ { role: "button", text: "Stop" },
128
+ ]),
55
129
  isInitial: false,
56
130
  },
57
131
  {
58
132
  id: "paused",
59
133
  name: "Paused",
60
134
  description: "The workflow is temporarily paused.",
61
- requiredElements: [{ role: "button", ariaLabel: "Play" }],
135
+ assertions: assertionsFromCriteria("paused", "Paused", [
136
+ { role: "button", ariaLabel: "Play" },
137
+ ]),
62
138
  isInitial: false,
63
139
  },
64
140
  {
@@ -66,10 +142,10 @@ const ACTIVE_PAGE_FIXTURE: IRDocument = {
66
142
  name: "Breakpoint Paused",
67
143
  description:
68
144
  "A workflow step with breakpoint: true has completed; execution is suspended.",
69
- requiredElements: [
145
+ assertions: assertionsFromCriteria("breakpoint-paused", "Breakpoint Paused", [
70
146
  { text: "Breakpoint" },
71
147
  { attributes: { "content-label": "breakpoint step" } },
72
- ],
148
+ ]),
73
149
  isInitial: false,
74
150
  },
75
151
  ],
@@ -268,12 +344,12 @@ describe("projectIRToBundledPage — round-trip against active.spec", () => {
268
344
  {
269
345
  id: "first",
270
346
  name: "First",
271
- requiredElements: [{ role: "heading" }],
347
+ assertions: assertionsFromCriteria("first", "First", [{ role: "heading" }]),
272
348
  },
273
349
  {
274
350
  id: "second",
275
351
  name: "Second",
276
- requiredElements: [{ role: "button" }],
352
+ assertions: assertionsFromCriteria("second", "Second", [{ role: "button" }]),
277
353
  },
278
354
  ],
279
355
  transitions: [],
@@ -388,7 +464,7 @@ describe("projectIRToBundledPage — empty cases", () => {
388
464
  {
389
465
  id: "no-req",
390
466
  name: "No Required Elements",
391
- requiredElements: [],
467
+ assertions: [],
392
468
  },
393
469
  ],
394
470
  transitions: [],
@@ -499,7 +575,7 @@ describe("projectIRToBundledPage — empty cases", () => {
499
575
  {
500
576
  id: "empty-text",
501
577
  name: "Empty Text",
502
- requiredElements: [{ text: "" }],
578
+ assertions: assertionsFromCriteria("empty-text", "Empty Text", [{ text: "" }]),
503
579
  },
504
580
  ],
505
581
  transitions: [],
@@ -515,8 +591,16 @@ describe("projectIRToBundledPage — empty cases", () => {
515
591
  id: "p",
516
592
  name: "P",
517
593
  states: [
518
- { id: "a", name: "A", requiredElements: [{ role: "heading" }] },
519
- { id: "b", name: "B", requiredElements: [{ role: "heading" }] },
594
+ {
595
+ id: "a",
596
+ name: "A",
597
+ assertions: assertionsFromCriteria("a", "A", [{ role: "heading" }]),
598
+ },
599
+ {
600
+ id: "b",
601
+ name: "B",
602
+ assertions: assertionsFromCriteria("b", "B", [{ role: "heading" }]),
603
+ },
520
604
  ],
521
605
  transitions: [
522
606
  {
@@ -541,8 +625,16 @@ describe("projectIRToBundledPage — empty cases", () => {
541
625
  id: "p",
542
626
  name: "P",
543
627
  states: [
544
- { id: "base", name: "Base", requiredElements: [{ role: "heading" }] },
545
- { id: "modal", name: "Modal", requiredElements: [{ role: "dialog" }] },
628
+ {
629
+ id: "base",
630
+ name: "Base",
631
+ assertions: assertionsFromCriteria("base", "Base", [{ role: "heading" }]),
632
+ },
633
+ {
634
+ id: "modal",
635
+ name: "Modal",
636
+ assertions: assertionsFromCriteria("modal", "Modal", [{ role: "dialog" }]),
637
+ },
546
638
  ],
547
639
  transitions: [
548
640
  {
@@ -576,7 +668,7 @@ describe("projectIRToBundledPage — provenance + state metadata", () => {
576
668
  {
577
669
  id: "s",
578
670
  name: "S",
579
- requiredElements: [{ role: "heading" }],
671
+ assertions: assertionsFromCriteria("s", "S", [{ role: "heading" }]),
580
672
  provenance: { source: "hand-authored", file: "p.tsx" },
581
673
  },
582
674
  ],
@@ -595,7 +687,7 @@ describe("projectIRToBundledPage — provenance + state metadata", () => {
595
687
  {
596
688
  id: "s",
597
689
  name: "S",
598
- requiredElements: [{ role: "heading" }],
690
+ assertions: assertionsFromCriteria("s", "S", [{ role: "heading" }]),
599
691
  },
600
692
  ],
601
693
  transitions: [],
@@ -613,7 +705,7 @@ describe("projectIRToBundledPage — provenance + state metadata", () => {
613
705
  {
614
706
  id: "s",
615
707
  name: "S",
616
- requiredElements: [{ role: "heading" }],
708
+ assertions: assertionsFromCriteria("s", "S", [{ role: "heading" }]),
617
709
  metadata: { description: "From metadata" },
618
710
  },
619
711
  ],
@@ -632,7 +724,7 @@ describe("projectIRToBundledPage — provenance + state metadata", () => {
632
724
  {
633
725
  id: "s",
634
726
  name: "S",
635
- requiredElements: [{ role: "heading" }],
727
+ assertions: assertionsFromCriteria("s", "S", [{ role: "heading" }]),
636
728
  },
637
729
  ],
638
730
  transitions: [],
@@ -834,21 +926,21 @@ describe("projectLegacyToIR — basic shape", () => {
834
926
  expect(alpha.description).toBe("First group for the round-trip test.");
835
927
  });
836
928
 
837
- it("sources requiredElements from group.assertions[].target.criteria", () => {
929
+ it("sources state.assertions[].target.criteria from group.assertions[].target.criteria (inverted)", () => {
838
930
  const ir = projectLegacyToIR(LEGACY_FIXTURE);
839
931
  const alpha = ir.states.find((s) => s.id === "alpha")!;
840
- expect(alpha.requiredElements).toHaveLength(2);
841
- expect(alpha.requiredElements[0]).toEqual({
932
+ expect(alpha.assertions).toHaveLength(2);
933
+ expect(criteriaOf(alpha.assertions[0]!)).toEqual({
842
934
  role: "heading",
843
935
  text: "Alpha",
844
936
  });
845
- expect(alpha.requiredElements[1]).toEqual({ text: "Welcome" });
937
+ expect(criteriaOf(alpha.assertions[1]!)).toEqual({ text: "Welcome" });
846
938
  });
847
939
 
848
940
  it("inverts dataAttributes -> attributes and accessibleName passthrough", () => {
849
941
  const ir = projectLegacyToIR(LEGACY_FIXTURE);
850
942
  const beta = ir.states.find((s) => s.id === "beta")!;
851
- expect(beta.requiredElements[0]).toEqual({
943
+ expect(criteriaOf(beta.assertions[0]!)).toEqual({
852
944
  role: "dialog",
853
945
  accessibleName: "Confirm",
854
946
  });
@@ -114,10 +114,16 @@ const SUPPORTED_IR_VERSIONS = new Set<string>(["1.0"]);
114
114
  * incomingTransitions, crossRefs, visualRefs).
115
115
  */
116
116
  export function adaptIRState(state: IRState): AdaptedState {
117
+ // Project rich IR assertions down to the runtime's flat element-criteria
118
+ // list. The runtime engine (ui-bridge-auto's WorkflowConfig.StateConfig)
119
+ // doesn't carry per-element severity/category — those metadata fields stay
120
+ // IR-side, consumed by Spec-Check policies.
117
121
  const out: AdaptedState = {
118
122
  id: state.id,
119
123
  name: state.name,
120
- requiredElements: state.requiredElements,
124
+ requiredElements: state.assertions.map(
125
+ (a) => a.target.criteria as unknown as IRElementCriteria,
126
+ ),
121
127
  };
122
128
  if (state.description !== undefined) out.description = state.description;
123
129
  if (state.excludedElements !== undefined) out.excludedElements = state.excludedElements;
@@ -95,7 +95,11 @@
95
95
  "file": { "type": "string" },
96
96
  "line": { "type": "integer", "minimum": 1 },
97
97
  "column": { "type": "integer", "minimum": 1 },
98
- "pluginVersion": { "type": "string" }
98
+ "pluginVersion": { "type": "string" },
99
+ "status": {
100
+ "type": "string",
101
+ "enum": ["proposed", "pending", "promoted"]
102
+ }
99
103
  }
100
104
  },
101
105
  "IRCrossRef": {
@@ -146,15 +150,15 @@
146
150
  },
147
151
  "IRState": {
148
152
  "type": "object",
149
- "required": ["id", "name", "requiredElements"],
153
+ "required": ["id", "name", "assertions"],
150
154
  "additionalProperties": false,
151
155
  "properties": {
152
156
  "id": { "type": "string" },
153
157
  "name": { "type": "string" },
154
158
  "description": { "type": "string" },
155
- "requiredElements": {
159
+ "assertions": {
156
160
  "type": "array",
157
- "items": { "$ref": "#/$defs/IRElementCriteria" }
161
+ "items": { "$ref": "#/$defs/IRAssertion" }
158
162
  },
159
163
  "excludedElements": {
160
164
  "type": "array",
@@ -27,6 +27,14 @@ export interface IRProvenance {
27
27
  column?: number;
28
28
  /** Build-plugin version that produced this node, if applicable. */
29
29
  pluginVersion?: string;
30
+ /**
31
+ * Lifecycle status for the Stream E coverage-growth flywheel. Absent =
32
+ * implicitly `"promoted"` (legacy + on-disk specs that predate the field).
33
+ * Set to `"proposed"` by `spec_authoring`, `"pending"` when staged in
34
+ * `_pending/`, `"promoted"` after the 2-green sweep moves the file to
35
+ * `pages/<id>/`.
36
+ */
37
+ status?: "proposed" | "pending" | "promoted";
30
38
  }
31
39
 
32
40
  // ---------------------------------------------------------------------------
@@ -371,58 +371,13 @@ function convertCriteria(criteria: IRElementCriteria): LegacyCriteria {
371
371
  }
372
372
 
373
373
  /**
374
- * Build a single legacy assertion from a state + index + criteria.
375
- *
376
- * Always produces one assertion per requiredElement; the caller is responsible
377
- * for iterating `state.requiredElements`. Empty `requiredElements` produces an
378
- * empty `assertions[]` — legitimate legacy specs (e.g. architecture-only
379
- * groups in `graphql-infrastructure`, `activity-timeline`, `opik-integration`,
380
- * `watchers`) ship with empty `assertions[]`, and the inverse projection
381
- * round-trip must preserve that emptiness or `check-spec-pairing` flags an
382
- * assertion-count drift.
383
- */
384
- function buildAssertion(
385
- state: IRState,
386
- index: number,
387
- criteria: IRElementCriteria,
388
- ): LegacyAssertion {
389
- const description =
390
- state.metadata?.description ?? `Required element ${index} for state ${state.name}`;
391
- const targetCriteria: LegacyCriteria = convertCriteria(criteria);
392
- const assertion: LegacyAssertion = {
393
- id: `${state.id}-elem-${index}`,
394
- description,
395
- category: "element-presence",
396
- severity: "critical",
397
- assertionType: "exists",
398
- target: {
399
- type: "search",
400
- criteria: targetCriteria,
401
- label: `Required element for ${state.name}`,
402
- },
403
- source: "ai-generated",
404
- reviewed: false,
405
- enabled: true,
406
- };
407
- if (state.precondition !== undefined) {
408
- assertion.precondition = state.precondition;
409
- }
410
- return assertion;
411
- }
412
-
413
- /**
414
- * Build the legacy `groups[]` entry for one IR state. Emits exactly one
415
- * assertion per `requiredElement`; states with zero requiredElements project
416
- * to a group with an empty `assertions[]` array. Legacy consumers (runner
417
- * spec drift / verify, error monitor curator, AI session) tolerate empty
418
- * groups — multiple shipping legacy specs already use them for
419
- * architecture-only / documentation-only groups.
374
+ * Build the legacy `groups[]` entry for one IR state. With IrState.assertions
375
+ * carrying first-class assertion metadata (id/description/severity/category/
376
+ * target), this is now a direct shape-conversion per assertion — no more
377
+ * synthesizing IDs or defaulting severity.
420
378
  */
421
379
  function buildGroup(state: IRState): LegacyGroup {
422
- const elems = state.requiredElements ?? [];
423
- const assertions: LegacyAssertion[] = elems.map((c, i) =>
424
- buildAssertion(state, i, c),
425
- );
380
+ const assertions: LegacyAssertion[] = state.assertions.map(synthesizedAssertionToLegacy);
426
381
  return {
427
382
  id: state.id,
428
383
  name: state.name,
@@ -535,7 +490,10 @@ function buildStateMachineState(
535
490
  id: state.id,
536
491
  name: state.name,
537
492
  description: state.description ?? "",
538
- elements: (state.requiredElements ?? []).map(convertCriteria),
493
+ // Project legacy `elements[]` from each assertion's target.criteria.
494
+ elements: state.assertions.map((a) =>
495
+ convertCriteria(a.target.criteria as IRElementCriteria),
496
+ ),
539
497
  isInitial,
540
498
  transitions: outgoing,
541
499
  };
@@ -680,11 +638,30 @@ function buildIRState(
680
638
  group: LegacyGroup,
681
639
  smState: LegacyStateMachineState | undefined,
682
640
  ): IRState {
683
- // Always one requiredElement per assertion so the forward projection emits
684
- // the same number of assertions on the round-trip.
685
- const requiredElements: IRElementCriteria[] = group.assertions.map((a) =>
686
- invertCriteria(a.target?.criteria),
687
- );
641
+ // Map each LegacyAssertion to an IRAssertion, preserving id/description/
642
+ // severity/category. target.criteria is normalized via invertCriteria so
643
+ // the criteria shape matches what the IR side stores.
644
+ const assertions: IRAssertion[] = group.assertions.map((a) => {
645
+ const ir: IRAssertion = {
646
+ id: a.id,
647
+ description: a.description,
648
+ category: a.category,
649
+ severity: a.severity,
650
+ assertionType: a.assertionType,
651
+ target: {
652
+ type: "search",
653
+ criteria: invertCriteria(a.target?.criteria) as unknown as Record<string, unknown>,
654
+ label: a.target?.label ?? a.description,
655
+ },
656
+ source: a.source,
657
+ reviewed: a.reviewed,
658
+ enabled: a.enabled,
659
+ };
660
+ if (a.precondition !== undefined) {
661
+ ir.precondition = a.precondition;
662
+ }
663
+ return ir;
664
+ });
688
665
 
689
666
  const description =
690
667
  group.description !== undefined && group.description.length > 0
@@ -695,7 +672,7 @@ function buildIRState(
695
672
  const state: IRState = {
696
673
  id: group.id,
697
674
  name: smState?.name ?? group.name,
698
- requiredElements,
675
+ assertions,
699
676
  };
700
677
  if (description !== undefined && description.length > 0) {
701
678
  state.description = description;
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { IRElementCriteria } from "./element-criteria";
10
+ import type { IRAssertion } from "./group";
10
11
  import type { IRCrossRef, IRMetadata, IRProvenance, IRVisualRef } from "./primitives";
11
12
 
12
13
  /**
@@ -42,8 +43,14 @@ export interface IRState {
42
43
  description?: string;
43
44
 
44
45
  // Predicates (canonical) -----------------------------------------------
45
- /** Element criteria that must ALL be satisfied for this state to be active. */
46
- requiredElements: IRElementCriteria[];
46
+ /**
47
+ * State-scoped assertions. Each carries its own assertion_id, description,
48
+ * severity, category, and an IrElementCriteria target via target.criteria.
49
+ * Spec-Check (Plan 02) evaluates these against UI Bridge snapshots; policy
50
+ * filtering via `severity`/`category` is meaningful because each assertion
51
+ * carries its own metadata (no synthesized stubs).
52
+ */
53
+ assertions: IRAssertion[];
47
54
  /** Element criteria where NONE may be satisfied (any match disqualifies). */
48
55
  excludedElements?: IRElementCriteria[];
49
56
  /** Additional property checks on matched elements. */