@orkestrel/scaffold 0.0.72 → 0.0.74

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.
@@ -1,7 +1,29 @@
1
1
  # The statechart family
2
2
 
3
- Declare one transition table, and give it to the automated run that asserts it and to the
4
- harness a person watches. Never write a second table for the harness.
3
+ Declare one transition table, and give it to the run that asserts it and to the harness a person
4
+ watches. Never write a second table for the harness.
5
+
6
+ ## The vocabulary
7
+
8
+ ```ts
9
+ import type { StateScenario, StateTransition, StatechartStatus } from '@orkestrel/test'
10
+ import {
11
+ STATECHART_ATTRIBUTES,
12
+ STATECHART_STATUSES,
13
+ buildRefusal,
14
+ executeScenario,
15
+ executeScenarios,
16
+ requireValue,
17
+ } from '@orkestrel/test'
18
+ import type { HarnessInterface, HarnessOptions } from '@orkestrel/test/browser'
19
+ import {
20
+ clickAccessible,
21
+ clickDisclosure,
22
+ createHarness,
23
+ readStates,
24
+ render,
25
+ } from '@orkestrel/test/browser'
26
+ ```
5
27
 
6
28
  ## Declare the table
7
29
 
@@ -12,13 +34,134 @@ harness a person watches. Never write a second table for the harness.
12
34
  Design laws bars a literal union that names no real domain state.
13
35
  - Write one `StateScenario` per transition, carrying that `transition` plus `arrange`, `act`, and
14
36
  `assert`. Each phase receives the context and the part of the transition it owns.
15
- - Put the scenarios in the workspace's browser test setup module. Put the table beside them until a
16
- harness page ships; from then on declare it in the application's own constants module, typed on the
17
- entity's unions, and import it from the page and from the setup alike, because a page cannot
18
- import from `tests/` and a second table is what this reference forbids.
37
+ - Put the scenarios and the table in the workspace's browser test setup module, and import them from
38
+ there.
19
39
  - Declare a transition for every event the surface accepts in every state it accepts it, including
20
40
  the event that leaves the state unchanged. A table that lists only the moves the happy path takes
21
41
  proves the happy path.
42
+ - Write the phases as module functions the whole table shares. Each phase reads its subject from its
43
+ own parameters rather than from the row it belongs to, so one set serves a table of any size.
44
+
45
+ ## The worked table
46
+
47
+ Copy the shape of the following table, which is the table this layer's own browser suite runs. Its
48
+ entity is a native disclosure with a second door: the summary toggles it, and a `Dismiss` button
49
+ closes it and does nothing when it is already closed. Give your own table the same row that second
50
+ door produces here — the row whose event leaves the state where it found it.
51
+
52
+ ```ts
53
+ import type { StateScenario } from '@orkestrel/test'
54
+ import { executeScenarios, requireValue } from '@orkestrel/test'
55
+ import { clickAccessible, clickDisclosure, readStates, render } from '@orkestrel/test/browser'
56
+ import { expect, it } from 'vitest'
57
+
58
+ type DisclosureState = 'closed' | 'open'
59
+ type DisclosureEvent = 'toggle' | 'dismiss'
60
+
61
+ interface DisclosureContext {
62
+ readonly summary: HTMLElement
63
+ }
64
+
65
+ // A journey verb resolves its own target by accessible name, so a second mounted disclosure called
66
+ // "Advanced" is an ambiguity rather than a second fixture. Each build takes the previous one out.
67
+ let mounted: HTMLElement | undefined
68
+
69
+ function buildDisclosure(): DisclosureContext {
70
+ mounted?.remove()
71
+ const container = render(
72
+ '<details><summary>Advanced</summary><p>Every setting.</p></details><button type="button">Dismiss</button>',
73
+ )
74
+ const details = requireValue(container.querySelector('details'))
75
+ requireValue(container.querySelector('button')).addEventListener('click', () => {
76
+ details.open = false
77
+ })
78
+ mounted = container
79
+ return { summary: requireValue(container.querySelector('summary')) }
80
+ }
81
+
82
+ function readDisclosure(context: DisclosureContext): DisclosureState {
83
+ return readStates(context.summary).includes('expanded') ? 'open' : 'closed'
84
+ }
85
+
86
+ async function arrangeDisclosure(
87
+ context: DisclosureContext,
88
+ state: DisclosureState,
89
+ ): Promise<void> {
90
+ if (readDisclosure(context) !== state) await clickDisclosure('Advanced')
91
+ }
92
+
93
+ // The context is unused because a journey verb finds what a person reads rather than a node this
94
+ // row was handed.
95
+ async function actOnDisclosure(_context: DisclosureContext, event: DisclosureEvent): Promise<void> {
96
+ if (event === 'toggle') await clickDisclosure('Advanced')
97
+ else await clickAccessible('Dismiss')
98
+ }
99
+
100
+ function assertDisclosure(context: DisclosureContext, state: DisclosureState): void {
101
+ expect(readDisclosure(context)).toBe(state)
102
+ }
103
+
104
+ const SCENARIOS: ReadonlyArray<StateScenario<DisclosureState, DisclosureEvent, DisclosureContext>> =
105
+ [
106
+ {
107
+ transition: {
108
+ name: 'closed opens through the summary',
109
+ from: 'closed',
110
+ event: 'toggle',
111
+ to: 'open',
112
+ },
113
+ arrange: arrangeDisclosure,
114
+ act: actOnDisclosure,
115
+ assert: assertDisclosure,
116
+ },
117
+ {
118
+ transition: {
119
+ name: 'open closes through the summary',
120
+ from: 'open',
121
+ event: 'toggle',
122
+ to: 'closed',
123
+ },
124
+ arrange: arrangeDisclosure,
125
+ act: actOnDisclosure,
126
+ assert: assertDisclosure,
127
+ },
128
+ {
129
+ transition: {
130
+ name: 'open closes through the button',
131
+ from: 'open',
132
+ event: 'dismiss',
133
+ to: 'closed',
134
+ },
135
+ arrange: arrangeDisclosure,
136
+ act: actOnDisclosure,
137
+ assert: assertDisclosure,
138
+ },
139
+ {
140
+ // The row whose event leaves the state where it found it.
141
+ transition: {
142
+ name: 'closed stays closed through the button',
143
+ from: 'closed',
144
+ event: 'dismiss',
145
+ to: 'closed',
146
+ },
147
+ arrange: arrangeDisclosure,
148
+ act: actOnDisclosure,
149
+ assert: assertDisclosure,
150
+ },
151
+ ]
152
+
153
+ it('walks the disclosure statechart', async () => {
154
+ await executeScenarios(SCENARIOS, buildDisclosure)
155
+ })
156
+ ```
157
+
158
+ - Name each row for the door it drove. A table that names only the states reads as if one mechanism
159
+ moved the entity, and the row where the button leaves the disclosure exactly as it found it is the
160
+ one a name has to separate from the toggle rows beside it.
161
+ - Remove the previous fixture inside the builder. Without that removal the next row meets the
162
+ previous disclosure beside its own under one name, and the verb refuses them as ambiguous.
163
+ - Drive a native `<summary>` with `clickDisclosure` and an ARIA disclosure with `clickAccessible`
164
+ settled by `waitForState` ([layer.md](layer.md) → Disclosures).
22
165
 
23
166
  ## Run the table
24
167
 
@@ -28,60 +171,123 @@ harness a person watches. Never write a second table for the harness.
28
171
  - Build the context in `build`, which receives the row it is building for. That is what lets one
29
172
  table mix fixtures.
30
173
  - Let the runner name the failure. It prepends the transition's `name` to whatever the row threw and
31
- carries the original as the `cause`, so a bare assertion message still says which row failed.
174
+ carries the original as the `cause`, so a bare assertion message still says which row failed. A
175
+ phase that throws something other than an `Error` is named by its type, and a builder that refuses
176
+ raises `<name>: build refused` with its own refusal as the `cause`.
177
+ `buildRefusal(name, cause)` from `@orkestrel/test` builds that same error, so an assertion on
178
+ a refused build compares against what it returns rather than against a spelled string.
32
179
  - Never assert the entity's internal state in `assert` where the transition is one a person drives.
33
180
  Assert what the interface renders, through `readPerception`, `readValue`, or `readStates`.
34
181
 
35
182
  ## Drive the act the way the transition happens
36
183
 
37
184
  - Drive `act` through the journey verbs — `clickAccessible`, `clickDisclosure`, `typeAccessible`,
38
- `traverseAccessible` — and through `userEvent.keyboard` from `vitest/browser` where the transition
39
- is a key on an already-focused control, for every transition a person can cause.
185
+ `traverseAccessible`, `pressKeys` — for every transition a person can cause.
40
186
  - Drive `act` through the entity's own API only where the transition is the entity's rather than the
41
187
  person's: a lifecycle event, a transport reply, a timer the surface owns.
42
188
  - Say which door each row used, in the row's `name`. A table that mixes the doors silently reads as
43
189
  a set of user transitions and proves something else.
44
190
 
45
- ## Build the harness a person watches
46
-
47
- Mount the harness on the same table. It stays in the repository that owns the surface: only
48
- `StateTransition`, `StateScenario`, `executeScenario`, `executeScenarios`, `STATECHART_ATTRIBUTES`,
49
- and `STATECHART_STATUSES` are published, and a workspace that spells a `data-statechart-*` string of
50
- its own has left the contract.
51
-
52
- - Render one play control per transition and one play-all control, each disabled while a run is in
53
- flight.
54
- - Render a state badge carrying the entity's current state, an event log of what the entity emitted,
55
- and a `role="status"` announcer that narrates each step in a sentence, so a screen reader and a
56
- vision model both read the run without visual chrome.
57
- - Publish `STATECHART_ATTRIBUTES.status` on the harness root, and cycle its value through
58
- `STATECHART_STATUSES`: `pending` before a run has a result for every row, `idle` standing ready,
59
- `running` in flight, and `passed` or `failed` as the terminal reading. Publish
60
- `STATECHART_ATTRIBUTES.passed`, `.failed`, and `.total` on the same element, so a gate finds the
61
- harness and reads the tally from one node.
62
- - Publish `STATECHART_ATTRIBUTES.scenario` and `STATECHART_ATTRIBUTES.result` on each row, and
63
- `STATECHART_ATTRIBUTES.state` on the element rendering the entity's current state.
64
- - Write every attribute from the map rather than from a literal. A harness that sets an attribute
65
- the gate does not read fails silently as a run that never completes.
66
- - Deep-link one transition and the play-all run from the route, so a decision round names the exact
67
- link it wants looked at ([decide.md](decide.md) → The rendered artifact).
68
- - Give the harness a demo step that leaves the widget in its most legible state after the run, for a
69
- person or a vision model deciding on a look.
70
- - Pace the harness for a person to watch. The gate inherits that wall time, so budget the gate from
71
- the row count and the pause rather than from a fixed timeout.
191
+ ## Mount the harness
192
+
193
+ `createHarness(options)` renders the table in the browser and drives it row by row. It takes the
194
+ table as `scenarios`, the fixture builder as `build`, the reader that reports the entity's state as
195
+ `state`, and an optional `pause` between rows for a table worth watching.
196
+
197
+ ```ts
198
+ import { STATECHART_ATTRIBUTES } from '@orkestrel/test'
199
+ import { createHarness } from '@orkestrel/test/browser'
200
+
201
+ const harness = createHarness({
202
+ scenarios: SCENARIOS,
203
+ build: buildDisclosure,
204
+ state: readDisclosure,
205
+ })
206
+
207
+ harness.status // 'idle' — mounted, nothing run yet
208
+ harness.total // 4
209
+
210
+ await harness.execute()
211
+
212
+ harness.status // 'passed'
213
+ harness.passed // 4
214
+ harness.failed // 0
215
+ harness.failures // []
216
+
217
+ // The object reads its own markup, so a gate polling the page and a test asserting on the object
218
+ // cannot disagree.
219
+ harness.root.getAttribute(STATECHART_ATTRIBUTES.status) // 'passed'
220
+ harness.root.getAttribute(STATECHART_ATTRIBUTES.total) // '4'
221
+
222
+ harness.destroy()
223
+ ```
224
+
225
+ - Hand it the same table the run asserts. A second table for the harness is what this reference
226
+ forbids.
227
+ - Let it render its own markup. It writes `status`, `passed`, `failed`, and `total` on its root,
228
+ `scenario` and `result` on each row, and `state` on the element rendering the entity's current
229
+ state, every one of them from `STATECHART_ATTRIBUTES` rather than from a literal. A workspace that
230
+ spells a `data-statechart-*` string of its own has left the contract.
231
+ - Read the announcer beside the attributes. A `role="status"` element narrates each step in a
232
+ sentence, so a screen reader and a vision model both read the run without visual chrome.
233
+ - Take `execute` as reporting on the whole table. It carries on past a failing row, where
234
+ `executeScenarios` stops at the first, and a builder that refuses fails its own row under the
235
+ sentence `buildRefusal` builds rather than ending the run.
236
+ - Call `execute` again to re-run the same table from a fresh tally and a cleared rendered state.
237
+ - Call `destroy` in the test's own cleanup. It removes the mounted root and does nothing when the
238
+ root is already gone.
239
+ - Pace a table a person watches with `pause`, and budget the gate from the row count and that pause
240
+ rather than from a fixed timeout.
241
+
242
+ ## The observable statuses
243
+
244
+ `STATECHART_STATUSES` publishes the run states in the order a run passes through them, and
245
+ `StatechartStatus` is the same set as a named union.
246
+
247
+ | Status | The reading it names |
248
+ | --------- | ---------------------------------------------------------------------------------------- |
249
+ | `pending` | Construction, until every declared row has rendered and `total` carries the row count |
250
+ | `idle` | Mounted and standing ready, with `passed` and `failed` at zero and nothing in flight |
251
+ | `running` | A run in flight |
252
+ | `passed` | Terminal: the run finished and no row's result reads failed |
253
+ | `failed` | Terminal: the run finished with a failing row, or it ended on the `state` reader's throw |
254
+
255
+ - Read a `pending` status as a harness whose rows never mounted. A gate that finds it has found a
256
+ defect rather than a run to wait for.
257
+ - Read `total` as what the table declares and `passed` plus `failed` as what the last run finished.
258
+ - Take the terminal pair as the pair a gate waits for. Every exit writes one of them: a run the
259
+ `state` reader ends writes `failed` and then rejects with that reader's value by identity, and the
260
+ row that reader was called for is not counted as failed.
261
+ - Read the state element as carrying no reading before the first row produces a context. A state is
262
+ read from an entity, and no entity exists until a row builds one.
72
263
 
73
264
  ## Gate the harness
74
265
 
75
- Prove the harness from the browser project, through the interface:
76
-
77
- - Mount the harness page and clear the route's query first, so a leftover deep link cannot start the
78
- walk before the gate does.
79
- - Press the play-all control through `clickAccessible`, never through a constructed event and never
80
- by setting the deep link.
81
- - Poll `STATECHART_ATTRIBUTES.status` until it reads `passed` or `failed`. Never assert it from one
82
- read after the click.
83
- - Assert the status reads `passed`, the failed tally reads zero, and the passed tally equals the
84
- total. Name the failing rows from `STATECHART_ATTRIBUTES.scenario` in the failure message, so a red
85
- gate says which transition broke.
86
- - Assert the harness inventory before the rows: a page that mounted no transition passes every tally
87
- assertion.
266
+ Prove the harness from the browser project, through the object and the markup together:
267
+
268
+ - Mount the harness, `execute` it, and assert the status reads `passed`, the failed tally reads
269
+ zero, and the passed tally equals the total.
270
+ - Assert the inventory before the tally. A harness that mounted no row would pass every tally
271
+ assertion, and `createHarness` refuses an empty table with `Statechart harness mounted no
272
+ transition`.
273
+ - Name the failing rows from `failures`, which lists each row whose rendered `result` reads failed,
274
+ in table order. A red gate says which transition broke.
275
+ - Read the tally off `harness.root` as well as off the object, so the attribute contract a gate
276
+ outside the page depends on is asserted rather than assumed.
277
+ - Poll the root's `status` attribute until it reads a terminal value where the gate runs outside
278
+ this layer's environment, under a budget derived from the row count and the declared pause. Never
279
+ assert it from one read after the start.
280
+
281
+ ## A harness page is product
282
+
283
+ A deep-linked page hosting a harness is optional product the workspace ships on its own account.
284
+ This layer's browser entry imports `vitest/browser` at module scope, so an application page cannot
285
+ import it.
286
+
287
+ - Name only the attribute contract such a page must honour: the names in `STATECHART_ATTRIBUTES`,
288
+ the readings in `STATECHART_STATUSES`, and the tally on one root node.
289
+ - Report the page itself as the repository owner's decision — which transitions a surface owes,
290
+ where the page is linked, and whether it ships at all.
291
+ - Route a person who must watch the widget move to the harness run's own frames and its written
292
+ artifact ([decide.md](decide.md) → The harness run), and name a deep link only where the workspace
293
+ already ships such a page.
@@ -2,37 +2,93 @@
2
2
 
3
3
  Prove a style from what the browser resolved on the mounted surface. The `enterprise-bootstrap`
4
4
  skill's [instruments reference](../../enterprise-bootstrap/references/inspection.md) names each
5
- instrument's property, its population, and the negative controls that must fail. Take those from
6
- there and the reading from here.
5
+ instrument's property, its population, and its coverage. Take those from there, the reading from
6
+ here, and the control from the builder this layer publishes for it.
7
+
8
+ ## The vocabulary
9
+
10
+ ```ts
11
+ import type {
12
+ CaptureVariant,
13
+ CensusFixture,
14
+ CensusReading,
15
+ Color,
16
+ ContrastFixture,
17
+ EscapeFixture,
18
+ } from '@orkestrel/test/browser'
19
+ import {
20
+ CANVAS_COLOR,
21
+ blendColor,
22
+ buildCensus,
23
+ buildContrast,
24
+ buildEscapes,
25
+ extractOrphans,
26
+ extractStyles,
27
+ findKeyframes,
28
+ findRule,
29
+ matchesColor,
30
+ measureContrast,
31
+ measureLuminance,
32
+ parseCSSColor,
33
+ pressKeys,
34
+ readBackdrop,
35
+ readCascade,
36
+ readCensus,
37
+ readClasses,
38
+ readContrast,
39
+ readLayers,
40
+ readPixels,
41
+ readRing,
42
+ readRootToken,
43
+ readRows,
44
+ readStyle,
45
+ readToken,
46
+ traverseAccessible,
47
+ waitForAnimations,
48
+ } from '@orkestrel/test/browser'
49
+ import { inject } from 'vitest'
50
+ ```
7
51
 
8
52
  ## Assert the resolved value
9
53
 
10
54
  - Read one property with `readStyle(element, property)` and a length with `readPixels(element, property)`. A
11
55
  class present in the markup and absent from the cascade resolves to nothing, and an assertion on
12
56
  the class list passes on it.
57
+ - Read `readPixels` as a measured contribution rather than a parsed length. A resolved value
58
+ carrying no leading number reads as `0`, so read the text with `readStyle` where an unparsable
59
+ value and a genuine zero are different findings.
13
60
  - Never substitute `findRule` for a resolved read. It proves a declaration exists, and another rule
14
- may still win; reach for it where the stylesheet itself is the subject.
61
+ may still win; reach for it where the stylesheet itself is the subject, and for `findKeyframes`
62
+ where the animation's declaration is.
15
63
  - Compare a color through `matchesColor` or `parseCSSColor` rather than by string. A browser normalizes a color
16
64
  expression, so a literal comparison fails on a value that resolved correctly.
65
+ - Take every reading after the paint settles. Await `waitForAnimations` on the element whose
66
+ transition was running ([layer.md](layer.md) → The waits); a reading taken mid-transition reports
67
+ an interpolated frame no state of the interface paints.
17
68
 
18
69
  ## Run per variant
19
70
 
20
71
  The run axis is fixed in [SKILL.md](../SKILL.md) → Read the variant once. The matrix family's own
21
72
  readings follow.
22
73
 
23
- - Apply each variant's `apply` and its `width` and `height` before the readings, and take every
24
- reading for that variant before moving to the next.
74
+ - Read `inject('variants')` for the declared list, and walk every entry inside one run. This family
75
+ reads the whole matrix, where the capture family renders one variant per run.
76
+ - Compose each variant's `apply` in the test, and apply it with the variant's `width` and `height`
77
+ before the readings. Take every reading for that variant before moving to the next.
25
78
  - Name the attribute the surface actually reads in `apply`; a Bootstrap surface switches on
26
79
  `data-bs-theme`. An `apply` that sets another attribute leaves the run in the default theme, where
27
80
  every reading passes.
81
+ - Reach for the application's own theme control where the surface ships one, and assert the state it
82
+ announces. Setting the attribute directly proves the stylesheet; driving the control proves the
83
+ surface.
28
84
  - Assert that the run read every declared variant. A matrix that silently walked one variant reports
29
85
  a pass for the theme nobody exercised.
30
86
  - Report which variants a result covers beside it. A pairing that appears only in a state the run
31
87
  never entered is unmeasured.
32
88
 
33
- Vitest `provide` carries serializable values only. Where a workspace fans one project out per
34
- variant, the project's provided variant carries `name`, `width`, and `height`; `apply` does not
35
- cross that channel. Run `apply` inside the test from the variant the project provides.
89
+ Vitest `provide` carries serializable values only, so the provided variant carries `name`, `width`,
90
+ and `height`; `apply` does not cross that channel. Compose it in the test from the name the project
91
+ provided.
36
92
 
37
93
  ## Contrast and focus chrome
38
94
 
@@ -43,39 +99,62 @@ cross that channel. Run `apply` inside the test from the variant the project pro
43
99
  are all translucent; take that refusal as the reading, because an assumed white canvas turns "this
44
100
  surface declares no background" into a number that reads like a measurement.
45
101
  - Read focus chrome with `readRing(control)`, after focus arrived through `traverseAccessible`,
46
- `userEvent.keyboard` from `vitest/browser`, or a real click. Pass `worn` where the chrome is painted onto a second element such
102
+ `pressKeys`, or a real click. Pass `worn` where the chrome is painted onto a second element such
47
103
  as a label. It reports `undefined` for a control not matching `:focus-visible`, for the browser's
48
104
  own automatic ring, and for a focus style that only repaints the fill — treat each as a finding
49
105
  about the surface rather than as a pass.
50
- - Carry the negative controls the composited-contrast instrument names, in the same run and composed
51
- in the harness rather than taken from the surface. An instrument whose negative control passes is
52
- broken, and its readings are not evidence.
53
106
  - Reach for `measureContrast`, `measureLuminance`, `blendColor`, `readLayers`, and `readBackdrop`
54
107
  only where the composite itself is the subject. Never re-derive `readContrast` from them.
55
108
 
109
+ ## The published controls
110
+
111
+ Take each reading's control from the builder this layer publishes for it.
112
+ `.claude/rules/quality.md` § Instruments owns the law that control satisfies.
113
+
114
+ | Reading | Control | What it carries |
115
+ | --------------- | ------------------------- | -------------------------------------------------------------------------------------- |
116
+ | `readContrast` | `buildContrast(bar)` | A translucent tint over an opaque floor, with a `refused` and an `accepted` foreground |
117
+ | `extractStyles` | `buildEscapes(permitted)` | An inline declaration, an embedded `<style>` element, and the sheet the id exempts |
118
+ | `readCensus` | `buildCensus()` | An HTML token and an SVG token, neither declared by any loaded stylesheet |
119
+
120
+ - Append each control's `root` to the same surface root the reading walks, take the reading, and
121
+ remove it afterwards. Every builder returns detached nodes and mounts nothing, so where the
122
+ control is read is the caller's decision.
123
+ - Assert on the fields the builder returns rather than on a token or a selector written down in the
124
+ test. `buildCensus` hands back its own tokens, and `buildContrast` hands back `refused` and
125
+ `accepted` by name.
126
+ - Require the `refused` foreground to read under the bar and the `accepted` one to reach it, in the
127
+ same run as the production readings. `buildContrast` refuses a bar its own stack cannot straddle,
128
+ and that refusal is the reading: no pair it can compose settles that bar.
129
+ - Pass the exempt id the policy owns to `buildEscapes`, so the reader must leave the permitted sheet
130
+ alone rather than passing by rejecting every `<style>` element.
131
+
56
132
  ## The authored-class census
57
133
 
58
- Take the property, the population, and the negative controls from the instruments reference →
59
- Authored class in the shipped cascade. This is the reading.
134
+ Take the property, the population, and the coverage from the instruments reference → Authored class
135
+ in the shipped cascade. This is the reading.
60
136
 
61
- - Read the census as the difference between `readClasses(root)` of the mounted surface and
62
- `readCascade()`. A token in the difference is a class the markup uses and no loaded stylesheet
63
- declares.
137
+ - Read the census with `readCensus(root)`, which walks the mounted surface, reports `elements` as
138
+ the population it read, lists every `tokens` value the markup carries, and lists as `undeclared`
139
+ the tokens no loaded stylesheet declares. It refuses a walk that read no element.
140
+ - Assert on `elements` as well as on `undeclared`. An empty walk reports no undeclared token, and so
141
+ does markup whose every class the cascade declares.
64
142
  - Take `root` from the mounted surface, so the census covers what rendered rather than what a
65
143
  template file spells.
66
- - Report what `readClasses` walked as the population, and fail a run that walked none.
67
- - Append the extraction-door negative control to that same `root`, so it reaches the difference
68
- through `readClasses` rather than beside it.
144
+ - Read `readClasses` and `readCascade` directly only where one side of the difference is the
145
+ subject. `readCensus` is the reading, and re-deriving it drops the population it reports.
146
+ - Append `buildCensus().root` to that same `root`, so the control reaches the difference through the
147
+ same walk rather than beside it.
69
148
 
70
149
  ## Style escapes
71
150
 
72
- Take the property, the population, the named exemptions, and the negative control from the
73
- instruments reference → Style escapes. This is the reading.
151
+ Take the property, the population, the named exemptions, and the coverage from the instruments
152
+ reference → Style escapes. This is the reading.
74
153
 
75
154
  - Read escapes with `extractStyles(root)`, which returns the markup of every hit it found.
76
155
  - Take the reading before any journey drives the surface, because the population is the undriven
77
156
  tree.
78
- - Append the harness-built negative control element to that same `root`, so it reaches the reading
157
+ - Append `buildEscapes(permitted).root` to that same `root`, so the control reaches the reading
79
158
  through `extractStyles`.
80
159
  - Reach for `extractOrphans` where the finding is a child element rendered outside its required
81
160
  parent, and `readRows` where the subject is a repeated row's rendered text.
@@ -89,3 +168,12 @@ instruments reference → Style escapes. This is the reading.
89
168
  assertion on presence passes on a token nobody declared.
90
169
  - Read each token once per variant and assert the values differ where the design says the variants
91
170
  differ. A pair of variants that resolves a token identically is a theme that did not switch.
171
+
172
+ ## The engine bound
173
+
174
+ Every reading here comes from the one engine the gate renders.
175
+
176
+ - State that bound with the result. A claim about another engine's resolved value is unproven until
177
+ a reading taken on that engine records it.
178
+ - Read the limit and the condition that reopens it from the emitted `configs/browsers.ts` doc block,
179
+ which the generated workspace ships, rather than from a copy in the suite.
@@ -128,6 +128,13 @@ The Orchestrator verifies the finished exec with direct evidence — git status,
128
128
  scoped validation — and carries touched files, diffstat, and deviation state into
129
129
  integration and review.
130
130
 
131
+ On a Windows host a shell write that decodes and re-encodes text can replace a code point the active
132
+ code page cannot represent, so when a bench unit must edit a line carrying a code point above
133
+ `0x7F`, the brief tells it to make that edit through the exec's own patch tool, never through
134
+ `Get-Content`, `Set-Content`, `Out-File`, or a `>` redirection, and to report every such line it
135
+ touched. The Orchestrator's review sweep compares the set of code points above `0x7F` on each
136
+ touched line before and after the edit, and flags a line that lost any of them.
137
+
131
138
  ## Routing exclusion — defensive negative-test units
132
139
 
133
140
  The provider applies a content-safety filter that terminates a turn mid-run when the work