@pie-players/pie-assessment-toolkit 0.3.29 → 0.3.31

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 (199) hide show
  1. package/README.md +313 -5
  2. package/dist/components/ItemToolBar.custom-element.js +362 -176
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +604 -107
  4. package/dist/components/SectionToolBar.custom-element.js +0 -36
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/policy/core/PolicySource.d.ts +64 -0
  10. package/dist/policy/core/PolicySource.d.ts.map +1 -0
  11. package/dist/policy/core/PolicySource.js +17 -0
  12. package/dist/policy/core/PolicySource.js.map +1 -0
  13. package/dist/policy/core/ToolPolicyEngine.d.ts +123 -0
  14. package/dist/policy/core/ToolPolicyEngine.d.ts.map +1 -0
  15. package/dist/policy/core/ToolPolicyEngine.js +217 -0
  16. package/dist/policy/core/ToolPolicyEngine.js.map +1 -0
  17. package/dist/policy/core/compose-decision.d.ts +37 -0
  18. package/dist/policy/core/compose-decision.d.ts.map +1 -0
  19. package/dist/policy/core/compose-decision.js +332 -0
  20. package/dist/policy/core/compose-decision.js.map +1 -0
  21. package/dist/policy/core/decision-types.d.ts +142 -0
  22. package/dist/policy/core/decision-types.d.ts.map +1 -0
  23. package/dist/policy/core/decision-types.js +13 -0
  24. package/dist/policy/core/decision-types.js.map +1 -0
  25. package/dist/policy/core/engine-context.d.ts +30 -0
  26. package/dist/policy/core/engine-context.d.ts.map +1 -0
  27. package/dist/policy/core/engine-context.js +28 -0
  28. package/dist/policy/core/engine-context.js.map +1 -0
  29. package/dist/policy/core/policy-source-tag.d.ts +32 -0
  30. package/dist/policy/core/policy-source-tag.d.ts.map +1 -0
  31. package/dist/policy/core/policy-source-tag.js +29 -0
  32. package/dist/policy/core/policy-source-tag.js.map +1 -0
  33. package/dist/policy/core/provenance.d.ts +163 -0
  34. package/dist/policy/core/provenance.d.ts.map +1 -0
  35. package/dist/policy/core/provenance.js +232 -0
  36. package/dist/policy/core/provenance.js.map +1 -0
  37. package/dist/policy/core/qti-inputs.d.ts +67 -0
  38. package/dist/policy/core/qti-inputs.d.ts.map +1 -0
  39. package/dist/policy/core/qti-inputs.js +139 -0
  40. package/dist/policy/core/qti-inputs.js.map +1 -0
  41. package/dist/policy/engine.d.ts +28 -0
  42. package/dist/policy/engine.d.ts.map +1 -0
  43. package/dist/policy/engine.js +24 -0
  44. package/dist/policy/engine.js.map +1 -0
  45. package/dist/policy/internal.d.ts +26 -0
  46. package/dist/policy/internal.d.ts.map +1 -0
  47. package/dist/policy/internal.js +26 -0
  48. package/dist/policy/internal.js.map +1 -0
  49. package/dist/policy/sources/QtiPolicySource.d.ts +146 -0
  50. package/dist/policy/sources/QtiPolicySource.d.ts.map +1 -0
  51. package/dist/policy/sources/QtiPolicySource.js +270 -0
  52. package/dist/policy/sources/QtiPolicySource.js.map +1 -0
  53. package/dist/runtime/SectionRuntimeEngine.d.ts +145 -0
  54. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
  55. package/dist/runtime/SectionRuntimeEngine.js +164 -3
  56. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  57. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +144 -0
  58. package/dist/runtime/adapter/SectionEngineAdapter.d.ts.map +1 -0
  59. package/dist/runtime/adapter/SectionEngineAdapter.js +183 -0
  60. package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -0
  61. package/dist/runtime/adapter/coordinator-bridge.d.ts +93 -0
  62. package/dist/runtime/adapter/coordinator-bridge.d.ts.map +1 -0
  63. package/dist/runtime/adapter/coordinator-bridge.js +127 -0
  64. package/dist/runtime/adapter/coordinator-bridge.js.map +1 -0
  65. package/dist/runtime/adapter/dom-event-bridge.d.ts +63 -0
  66. package/dist/runtime/adapter/dom-event-bridge.d.ts.map +1 -0
  67. package/dist/runtime/adapter/dom-event-bridge.js +95 -0
  68. package/dist/runtime/adapter/dom-event-bridge.js.map +1 -0
  69. package/dist/runtime/adapter/framework-error-bridge.d.ts +37 -0
  70. package/dist/runtime/adapter/framework-error-bridge.d.ts.map +1 -0
  71. package/dist/runtime/adapter/framework-error-bridge.js +47 -0
  72. package/dist/runtime/adapter/framework-error-bridge.js.map +1 -0
  73. package/dist/runtime/adapter/instrumentation-bridge.d.ts +32 -0
  74. package/dist/runtime/adapter/instrumentation-bridge.d.ts.map +1 -0
  75. package/dist/runtime/adapter/instrumentation-bridge.js +39 -0
  76. package/dist/runtime/adapter/instrumentation-bridge.js.map +1 -0
  77. package/dist/runtime/adapter/subscriber-fanout.d.ts +35 -0
  78. package/dist/runtime/adapter/subscriber-fanout.d.ts.map +1 -0
  79. package/dist/runtime/adapter/subscriber-fanout.js +60 -0
  80. package/dist/runtime/adapter/subscriber-fanout.js.map +1 -0
  81. package/dist/runtime/core/SectionEngineCore.d.ts +52 -0
  82. package/dist/runtime/core/SectionEngineCore.d.ts.map +1 -0
  83. package/dist/runtime/core/SectionEngineCore.js +81 -0
  84. package/dist/runtime/core/SectionEngineCore.js.map +1 -0
  85. package/dist/runtime/core/cohort.d.ts +40 -0
  86. package/dist/runtime/core/cohort.d.ts.map +1 -0
  87. package/dist/runtime/core/cohort.js +49 -0
  88. package/dist/runtime/core/cohort.js.map +1 -0
  89. package/dist/runtime/core/engine-input.d.ts +114 -0
  90. package/dist/runtime/core/engine-input.d.ts.map +1 -0
  91. package/dist/runtime/core/engine-input.js +19 -0
  92. package/dist/runtime/core/engine-input.js.map +1 -0
  93. package/dist/runtime/core/engine-output.d.ts +62 -0
  94. package/dist/runtime/core/engine-output.d.ts.map +1 -0
  95. package/dist/runtime/core/engine-output.js +27 -0
  96. package/dist/runtime/core/engine-output.js.map +1 -0
  97. package/dist/runtime/core/engine-readiness.d.ts +49 -0
  98. package/dist/runtime/core/engine-readiness.d.ts.map +1 -0
  99. package/dist/runtime/core/engine-readiness.js +47 -0
  100. package/dist/runtime/core/engine-readiness.js.map +1 -0
  101. package/dist/runtime/core/engine-resolver.d.ts +172 -0
  102. package/dist/runtime/core/engine-resolver.d.ts.map +1 -0
  103. package/dist/runtime/core/engine-resolver.js +170 -0
  104. package/dist/runtime/core/engine-resolver.js.map +1 -0
  105. package/dist/runtime/core/engine-stage-derivation.d.ts +19 -0
  106. package/dist/runtime/core/engine-stage-derivation.d.ts.map +1 -0
  107. package/dist/runtime/core/engine-stage-derivation.js +30 -0
  108. package/dist/runtime/core/engine-stage-derivation.js.map +1 -0
  109. package/dist/runtime/core/engine-state.d.ts +83 -0
  110. package/dist/runtime/core/engine-state.d.ts.map +1 -0
  111. package/dist/runtime/core/engine-state.js +49 -0
  112. package/dist/runtime/core/engine-state.js.map +1 -0
  113. package/dist/runtime/core/engine-transition.d.ts +49 -0
  114. package/dist/runtime/core/engine-transition.d.ts.map +1 -0
  115. package/dist/runtime/core/engine-transition.js +261 -0
  116. package/dist/runtime/core/engine-transition.js.map +1 -0
  117. package/dist/runtime/engine-context.d.ts +40 -0
  118. package/dist/runtime/engine-context.d.ts.map +1 -0
  119. package/dist/runtime/engine-context.js +32 -0
  120. package/dist/runtime/engine-context.js.map +1 -0
  121. package/dist/runtime/engine.d.ts +24 -0
  122. package/dist/runtime/engine.d.ts.map +1 -0
  123. package/dist/runtime/engine.js +24 -0
  124. package/dist/runtime/engine.js.map +1 -0
  125. package/dist/runtime/internal.d.ts +44 -0
  126. package/dist/runtime/internal.d.ts.map +1 -0
  127. package/dist/runtime/internal.js +46 -0
  128. package/dist/runtime/internal.js.map +1 -0
  129. package/dist/runtime/runtime-id.d.ts +8 -0
  130. package/dist/runtime/runtime-id.d.ts.map +1 -0
  131. package/dist/runtime/runtime-id.js +10 -0
  132. package/dist/runtime/runtime-id.js.map +1 -0
  133. package/dist/runtime/section-runtime-engine-host-context.d.ts +74 -0
  134. package/dist/runtime/section-runtime-engine-host-context.d.ts.map +1 -0
  135. package/dist/runtime/section-runtime-engine-host-context.js +96 -0
  136. package/dist/runtime/section-runtime-engine-host-context.js.map +1 -0
  137. package/dist/runtime/stage-emit-gate.d.ts +93 -0
  138. package/dist/runtime/stage-emit-gate.d.ts.map +1 -0
  139. package/dist/runtime/stage-emit-gate.js +83 -0
  140. package/dist/runtime/stage-emit-gate.js.map +1 -0
  141. package/dist/services/SSMLExtractor.d.ts.map +1 -1
  142. package/dist/services/SSMLExtractor.js +189 -2
  143. package/dist/services/SSMLExtractor.js.map +1 -1
  144. package/dist/services/TTSService.d.ts +7 -0
  145. package/dist/services/TTSService.d.ts.map +1 -1
  146. package/dist/services/TTSService.js +15 -0
  147. package/dist/services/TTSService.js.map +1 -1
  148. package/dist/services/ToolRegistry.d.ts.map +1 -1
  149. package/dist/services/ToolRegistry.js +30 -0
  150. package/dist/services/ToolRegistry.js.map +1 -1
  151. package/dist/services/ToolkitCoordinator.d.ts +264 -6
  152. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  153. package/dist/services/ToolkitCoordinator.js +350 -32
  154. package/dist/services/ToolkitCoordinator.js.map +1 -1
  155. package/dist/services/framework-error-bus.d.ts +73 -0
  156. package/dist/services/framework-error-bus.d.ts.map +1 -0
  157. package/dist/services/framework-error-bus.js +68 -0
  158. package/dist/services/framework-error-bus.js.map +1 -0
  159. package/dist/services/framework-error.d.ts +36 -1
  160. package/dist/services/framework-error.d.ts.map +1 -1
  161. package/dist/services/framework-error.js +37 -0
  162. package/dist/services/framework-error.js.map +1 -1
  163. package/dist/services/interfaces.d.ts +85 -0
  164. package/dist/services/interfaces.d.ts.map +1 -1
  165. package/dist/services/interfaces.js.map +1 -1
  166. package/dist/services/tool-config-validation.js +1 -1
  167. package/dist/services/tool-config-validation.js.map +1 -1
  168. package/dist/services/tools-config-normalizer.d.ts +24 -1
  169. package/dist/services/tools-config-normalizer.d.ts.map +1 -1
  170. package/dist/services/tools-config-normalizer.js +13 -1
  171. package/dist/services/tools-config-normalizer.js.map +1 -1
  172. package/dist/services/tts-control-events.d.ts +14 -0
  173. package/dist/services/tts-control-events.d.ts.map +1 -0
  174. package/dist/services/tts-control-events.js +8 -0
  175. package/dist/services/tts-control-events.js.map +1 -0
  176. package/dist/tools/calculators/desmos-provider.js +18 -0
  177. package/dist/tools/calculators/desmos-provider.js.map +1 -1
  178. package/dist/tools/client.d.ts +0 -1
  179. package/dist/tools/client.d.ts.map +1 -1
  180. package/dist/tools/client.js +0 -2
  181. package/dist/tools/client.js.map +1 -1
  182. package/dist/tools/default-tool-module-loaders.d.ts.map +1 -1
  183. package/dist/tools/default-tool-module-loaders.js +2 -0
  184. package/dist/tools/default-tool-module-loaders.js.map +1 -1
  185. package/dist/tools/types.d.ts +0 -26
  186. package/dist/tools/types.d.ts.map +1 -1
  187. package/package.json +25 -8
  188. package/dist/runtime/runtime-event-guards.d.ts +0 -2
  189. package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
  190. package/dist/runtime/runtime-event-guards.js +0 -4
  191. package/dist/runtime/runtime-event-guards.js.map +0 -1
  192. package/dist/tools/tool-coordinator.d.ts +0 -72
  193. package/dist/tools/tool-coordinator.d.ts.map +0 -1
  194. package/dist/tools/tool-coordinator.js +0 -194
  195. package/dist/tools/tool-coordinator.js.map +0 -1
  196. package/dist/types/events.d.ts +0 -157
  197. package/dist/types/events.d.ts.map +0 -1
  198. package/dist/types/events.js +0 -11
  199. package/dist/types/events.js.map +0 -1
package/README.md CHANGED
@@ -88,7 +88,7 @@ provider path is the item-player loader config:
88
88
  - `pie-toolkit-runtime-inherited`
89
89
  - `pie-toolkit-ready`
90
90
  - `pie-toolkit-section-ready`
91
- - `pie-toolkit-runtime-error`
91
+ - `pie-toolkit-framework-error`
92
92
 
93
93
  Toolkit tool/backend operational stream:
94
94
 
@@ -114,6 +114,130 @@ See [ToolkitCoordinator Architecture](../../docs/architecture/TOOLKIT_COORDINATO
114
114
  6. **Element-Level Granularity**: Tool state tracked per PIE element, not per item
115
115
  7. **State Separation**: Tool state (ephemeral) separate from PIE session data (persistent)
116
116
 
117
+ ## Configuration tiers: easy attribute + sophisticated `runtime`
118
+
119
+ This package and `@pie-players/pie-section-player` follow a deliberate
120
+ two-tier configuration model. The same knob can usually be set in either
121
+ tier; the choice is about ergonomics, not capability.
122
+
123
+ ### When to use each tier
124
+
125
+ - **Easy tier — top-level CE attributes / properties.** Use these for the
126
+ common cases that are static for the lifetime of the player or that hosts
127
+ want to set declaratively in HTML / templating frameworks. Example:
128
+
129
+ ```html
130
+ <pie-assessment-toolkit
131
+ assessment-id="my-assessment"
132
+ section-id="s-1"
133
+ tool-config-strictness="warn"
134
+ ></pie-assessment-toolkit>
135
+ ```
136
+
137
+ - **Sophisticated tier — passing a constructed `ToolkitCoordinator` (or a
138
+ `runtime` object on consumer CEs).** Use this for advanced cases: composed
139
+ configuration, dynamic overrides, runtime mutation, fields without a
140
+ tier-1 attribute, or anything that benefits from being a single typed
141
+ object passed by reference. Example:
142
+
143
+ ```ts
144
+ const coordinator = new ToolkitCoordinator({
145
+ assessmentId: "my-assessment",
146
+ toolConfigStrictness: "warn",
147
+ tools: {
148
+ providers: { calculator: { enabled: true } },
149
+ placement: { item: ["calculator", "textToSpeech"] },
150
+ },
151
+ });
152
+ el.coordinator = coordinator;
153
+ ```
154
+
155
+ ### Naming rule
156
+
157
+ The easy-tier attribute name is the kebab-cased version of the runtime /
158
+ config key. `tool-config-strictness` ↔ `toolConfigStrictness`,
159
+ `assessment-id` ↔ `assessmentId`, `player-type` ↔ `playerType`. Hosts can
160
+ move a knob from the easy tier to the configuration object (or back)
161
+ without renaming.
162
+
163
+ ### Precedence rule
164
+
165
+ The configuration object wins. When the same knob is set in both tiers,
166
+ resolution is:
167
+
168
+ 1. The constructed `ToolkitCoordinator` config (or `runtime.<key>` on a
169
+ consumer CE) if set
170
+ 2. Top-level attribute / property if set
171
+ 3. Documented default
172
+
173
+ This applies symmetrically to `pie-assessment-toolkit` itself and to
174
+ section-player CEs that embed it. New knobs MUST follow this precedence;
175
+ do not add ad-hoc fall-throughs.
176
+
177
+ ### Canonical tier-1 attribute set
178
+
179
+ The tier-1 attribute set is the same shape across
180
+ `pie-assessment-toolkit`, `pie-section-player-base`, and the
181
+ `pie-section-player-*` layout elements (locked in M5). Every tier-1
182
+ surface obeys the strict mirror rule:
183
+
184
+ ```
185
+ kebab-attribute ↔ camelCaseProp ↔ runtime.<sameCamelCaseKey>
186
+ ```
187
+
188
+ Common members include:
189
+
190
+ - Identity: `assessment-id`, `section-id`, `attempt-id`
191
+ - Player: `player-type`, `lazy-init`
192
+ - Tools: `tools` (object property), `tool-registry` (object property),
193
+ `enabled-tools` (shorthand for `tools.placement.section`)
194
+ - Coordination: `coordinator` (`createSectionController` is a
195
+ runtime-only key on the section-player layout CEs — see
196
+ "Documented exceptions" below; `<pie-assessment-toolkit>` accepts
197
+ it as a direct JS prop on its own composition surface)
198
+ - Accessibility: `accessibility` (object property; the deprecated
199
+ `accessibility` *attribute* mapping was removed in M5)
200
+ - Diagnostics: `tool-config-strictness`, `debug`. Framework-error
201
+ delivery is via the canonical `onFrameworkError` callback prop and the
202
+ `framework-error` DOM event dispatched on the layout CE host.
203
+
204
+ Documented exceptions to the mirror rule:
205
+
206
+ - Identity (`section-id`, `attempt-id`, `section`): per-attempt host
207
+ state, not configuration.
208
+ - Layout-only shell knobs on the section-player layout CEs
209
+ (`show-toolbar`, `toolbar-position`, `narrow-layout-breakpoint`,
210
+ `split-pane-collapse-strategy`): layout-CE rendering concerns.
211
+ - Per-region toolbar tool placement: hosts populate
212
+ `tools.placement.item` / `tools.placement.passage` (object form) or
213
+ `runtime.tools.placement.{item,passage}` directly. The previously
214
+ deprecated `item-toolbar-tools` / `passage-toolbar-tools` attribute
215
+ aliases were removed in the broad architecture review compat sweep.
216
+ - Runtime-only keys on the section-player layout CEs
217
+ (`createSectionController`, `isolation`): accepted only via
218
+ `runtime.<key>`. The top-level prop aliases were removed in the
219
+ broad architecture review compat sweep. `<pie-assessment-toolkit>`
220
+ itself keeps `createSectionController` and `isolation` as JS-only
221
+ props (no kebab-attribute surface): section-player layouts forward
222
+ `runtime.isolation` and `runtime.createSectionController` to the
223
+ wrapped toolkit via property bindings; standalone hosts that need
224
+ to override coordinator inheritance should pass an explicit
225
+ `coordinator={...}` instead.
226
+
227
+ ### When to add a tier-1 attribute
228
+
229
+ Add a tier-1 attribute only if all of the following hold:
230
+
231
+ - It is a common case that hosts set without composing a `ToolkitCoordinator`
232
+ / `runtime` object.
233
+ - Its value is a primitive or small typed object that round-trips through
234
+ HTML attributes (string, boolean-like, number; structured data passes via
235
+ property assignment).
236
+ - It exists on every CE that conceptually owns the same knob, or has a
237
+ deliberate documented exclusion.
238
+
239
+ Otherwise expose it through the configuration object only.
240
+
117
241
  ## Quick Start
118
242
 
119
243
  ### Option 1: Use ToolkitCoordinator (Recommended)
@@ -562,8 +686,7 @@ The persistence strategy works with the same `SectionControllerSessionState` sha
562
686
 
563
687
  ### ✅ Core Infrastructure
564
688
 
565
- - **TypedEventBus**: Type-safe event bus built on native EventTarget
566
- - **Event Types**: Complete event definitions (player, tools, navigation, state, interaction)
689
+ - **TypedEventBus**: Generic type-safe `EventTarget` wrapper exported as a building block. The toolkit's own production events are emitted via DOM `CustomEvent`s on `<pie-assessment-toolkit>` and via `ToolkitCoordinator.subscribe*` helpers, not through this bus. Hosts and downstream packages may still use it to compose their own typed event maps.
567
690
 
568
691
  ### ✅ Toolkit Services
569
692
 
@@ -945,7 +1068,7 @@ player.section = mySection;
945
1068
  Default behavior is now framework-owned: invalid tools/runtime initialization is handled in `pie-assessment-toolkit` without host try/catch.
946
1069
 
947
1070
  - Framework logs a deterministic console error prefix: `[pie-framework:<kind>:<source>]`
948
- - Framework emits a canonical `framework-error` event (and still emits `runtime-error` for compatibility)
1071
+ - Framework emits a canonical `framework-error` event
949
1072
  - Framework renders a fallback error panel instead of a blank player
950
1073
  - Startup tool-config validation can surface as `kind: "coordinator-init"` when the owned coordinator construction path throws.
951
1074
 
@@ -987,9 +1110,169 @@ Notes:
987
1110
  - `providers.textToSpeech` is the canonical TTS provider key.
988
1111
  - `providers.tts` is rejected by the validation contract.
989
1112
  - Custom tools can provide provider-level `sanitizeConfig` and `validateConfig` hooks.
990
- - Hosts can react to framework errors via `onframework-error` listeners or `onFrameworkError` callback prop.
1113
+ - Hosts can react to framework errors via the `framework-error` DOM event,
1114
+ the `onFrameworkError(model)` callback prop, or by subscribing directly
1115
+ to the package-internal bus via
1116
+ `ToolkitCoordinator.subscribeFrameworkErrors(listener)`. The callback
1117
+ prop fires exactly once per error, regardless of wrapper depth. Filter
1118
+ by `model.kind` (e.g. `"tts-init"`, `"provider-init"`,
1119
+ `"provider-register"`) for tool- or provider-specific handling.
991
1120
  - See `docs/tools-and-accomodations/framework-owned-error-handling.md` for event payload and error-kind mapping details.
992
1121
 
1122
+ ## Section Runtime Engine (advanced)
1123
+
1124
+ The toolkit exposes a layered **section runtime engine** that consolidates
1125
+ runtime resolution, FSM-driven stage progression, framework-error reporting,
1126
+ DOM-event fan-out, and instrumentation into a single object hosts can mount
1127
+ and dispose. The engine is what `<pie-section-player-…>` and
1128
+ `<pie-assessment-toolkit>` use internally, and it is also the surface
1129
+ custom hosts (or alternate layout shells) consume directly.
1130
+
1131
+ ### Two import paths
1132
+
1133
+ The engine ships with two deliberately separate entry points so consumers
1134
+ pick the stability surface that matches their use case:
1135
+
1136
+ - **Stable facade — `@pie-players/pie-assessment-toolkit/runtime/engine`.**
1137
+ Narrow, semver-stable surface for hosts that want to mount, drive, and
1138
+ dispose a section runtime. Re-exports `SectionRuntimeEngine`,
1139
+ `SECTION_RUNTIME_ENGINE_KEY` (Svelte context), the cross-CE host
1140
+ context (`sectionRuntimeEngineHostContext`), and the consumer-side
1141
+ helper for that bridge (`connectSectionRuntimeEngineHostContext`).
1142
+ - **Internal surface — `@pie-players/pie-assessment-toolkit/runtime/internal`.**
1143
+ Wider, evolving surface for advanced hosts that need to construct an
1144
+ engine manually, inspect FSM state, or build alternate fan-out paths.
1145
+ Exposes `SectionEngineCore`, the four adapter bridges
1146
+ (`createDomEventBridge`, `createFrameworkErrorBridge`,
1147
+ `createCoordinatorBridge`, `createInstrumentationBridge`),
1148
+ `FrameworkErrorBus`, cohort helpers,
1149
+ and the `resolveRuntime` / `resolveToolsConfig` /
1150
+ `resolveSectionEngineRuntimeState` helpers. Symbols here may change
1151
+ between minor versions with a changeset note.
1152
+
1153
+ ### Single-engine invariant
1154
+
1155
+ When `<pie-assessment-toolkit>` is nested inside a section-player layout,
1156
+ the layout kernel publishes its engine reference via
1157
+ `sectionRuntimeEngineHostContext`. The toolkit detects that upstream
1158
+ engine and **suppresses its own external lifecycle DOM emits and stage
1159
+ tracker** in favor of the kernel's engine. From the outside, one cohort
1160
+ yields one `pie-stage-change` / `pie-loading-complete` chain on the
1161
+ layout CE host regardless of wrapper depth — even though, during the
1162
+ 0.x line, the toolkit still constructs a local engine instance for its
1163
+ controller-side surface (`register`, `handleContent*`, `initialize`).
1164
+ A future release collapses the toolkit's controller-side surface onto
1165
+ the upstream engine; until then the externally observable invariant —
1166
+ **one cohort, one canonical event chain** — is what hosts should rely
1167
+ on. A standalone `<pie-assessment-toolkit>` (no upstream context)
1168
+ emits from its own engine.
1169
+
1170
+ **Detection.** If a custom layout shell emits two `pie-stage-change`
1171
+ events per stage transition (or two `pie-loading-complete` per cohort)
1172
+ on the same layout CE — typically with two distinct `detail.runtimeId`
1173
+ values — the shell has not published its engine via
1174
+ `sectionRuntimeEngineHostContext`, so the wrapped
1175
+ `<pie-assessment-toolkit>` falls back to its standalone path and
1176
+ constructs a second engine. Wire the bridge as shown below.
1177
+
1178
+ ### Common-host wiring example
1179
+
1180
+ Most hosts never construct the engine directly — the section-player
1181
+ layout CE and the toolkit CE handle it. Use the facade only when
1182
+ building an alternate layout shell (e.g. a custom kernel host). The
1183
+ shape mirrors what the section-player kernel does internally:
1184
+
1185
+ ```ts
1186
+ import { ContextProvider } from "@pie-players/pie-context";
1187
+ import {
1188
+ SectionRuntimeEngine,
1189
+ sectionRuntimeEngineHostContext,
1190
+ } from "@pie-players/pie-assessment-toolkit/runtime/engine";
1191
+ import {
1192
+ FrameworkErrorBus,
1193
+ makeCohort,
1194
+ } from "@pie-players/pie-assessment-toolkit/runtime/internal";
1195
+
1196
+ const bus = new FrameworkErrorBus();
1197
+ const engine = new SectionRuntimeEngine();
1198
+
1199
+ // 1. Attach to the layout CE host. `sourceCe` is stamped onto every
1200
+ // DOM event the engine dispatches and is required.
1201
+ engine.attachHost({
1202
+ host: layoutHostElement,
1203
+ sourceCe: "my-custom-layout",
1204
+ frameworkErrorBus: bus,
1205
+ coordinator: toolkitCoordinator,
1206
+ });
1207
+
1208
+ // 2. Publish the engine reference on the layout CE host so any
1209
+ // wrapped <pie-assessment-toolkit> consumes it instead of
1210
+ // constructing its own (single-engine invariant).
1211
+ const engineProvider = new ContextProvider(layoutHostElement, {
1212
+ context: sectionRuntimeEngineHostContext,
1213
+ initialValue: { engine },
1214
+ });
1215
+ engineProvider.connect();
1216
+
1217
+ // 3. (Optional) Subscribe to the structured output stream — same set
1218
+ // of outputs the DOM-event bridge fans out to the host element.
1219
+ engine.subscribe((output) => {
1220
+ // tap stage transitions, readiness updates, framework errors,
1221
+ // instrumentation events
1222
+ });
1223
+
1224
+ // 4. Drive the engine. Use real `SectionEngineInput` shapes:
1225
+ const cohort = makeCohort({ sectionId, attemptId });
1226
+ engine.dispatchInput({
1227
+ kind: "initialize",
1228
+ cohort,
1229
+ effectiveRuntime,
1230
+ effectiveToolsConfig,
1231
+ itemCount,
1232
+ });
1233
+
1234
+ // On loading-progress / readiness signal updates:
1235
+ engine.dispatchInput({
1236
+ kind: "update-readiness-signals",
1237
+ signals: {
1238
+ sectionReady,
1239
+ interactionReady,
1240
+ allLoadingComplete,
1241
+ runtimeError,
1242
+ },
1243
+ loadedCount,
1244
+ itemCount,
1245
+ mode: "progressive",
1246
+ });
1247
+
1248
+ // On unmount:
1249
+ engineProvider.disconnect();
1250
+ engine.dispose();
1251
+ ```
1252
+
1253
+ The DOM events `pie-stage-change`, `pie-loading-complete`, and
1254
+ `framework-error` are dispatched on `host` automatically by the
1255
+ adapter's `dom-event-bridge`. The canonical `onFrameworkError` callback
1256
+ prop and the package-internal `FrameworkErrorBus` deliver each error
1257
+ exactly once regardless of wrapper depth. The `framework-error` DOM
1258
+ event on the layout CE host also delivers each error exactly once: the
1259
+ section-player kernel intercepts the toolkit's bubbled emit at
1260
+ `<pie-section-player-base>` and calls `event.stopPropagation()`, so the
1261
+ layout host sees only the canonical engine-bridge emit. Direct
1262
+ listeners on `<pie-assessment-toolkit>` itself still see the toolkit's
1263
+ own emit (the toolkit dispatch reaches them before the kernel listener
1264
+ runs). The single-emit contract is pinned by
1265
+ `packages/section-player/tests/section-player-framework-error-dual-emit.test.ts`.
1266
+ The previous dual-emit on the layout host was removed in the broad
1267
+ architecture review compat sweep.
1268
+
1269
+ The deprecated readiness aliases (`readiness-change`,
1270
+ `interaction-ready`, `ready`) and their `legacy-event-bridge` were
1271
+ removed in the broad architecture review compat sweep. Hosts that
1272
+ listened for them migrate to `pie-stage-change` (with the readiness
1273
+ detail also available via the kernel's `selectReadiness()`) and
1274
+ `pie-loading-complete`.
1275
+
993
1276
  ## State Separation: Tool State vs Session Data
994
1277
 
995
1278
  The toolkit enforces a clear separation between ephemeral tool state and persistent session data:
@@ -1058,12 +1341,37 @@ import type {
1058
1341
  } from '@pie-players/pie-assessment-toolkit';
1059
1342
  ```
1060
1343
 
1344
+ ## Content trust boundary
1345
+
1346
+ The toolkit embeds item content via the underlying `pie-item-player`
1347
+ custom element and renders tool icons / SSML fragments that originate
1348
+ from tool configuration. Two sanitization layers apply:
1349
+
1350
+ - **Item / passage markup** - sanitized by default in
1351
+ `pie-item-player`. See
1352
+ [pie-item-player README](./README.md#content-trust-boundary)
1353
+ for the `trust-markup` opt-out and the `sanitizeMarkup` override.
1354
+ As a post-sanitization step, every authored `<img>` outside a `pie-*`
1355
+ custom element is wrapped in `<span class="pie-image-scroll">` so
1356
+ overwide images surface a horizontal scrollbar instead of being
1357
+ clipped by the section layout's `overflow-x: hidden` ancestors
1358
+ (PIE-94 / WCAG 1.4.10 Reflow at 400% zoom). The wrapper is
1359
+ keyboard-scrollable (`tabindex="0"`, `role="region"`) and carries
1360
+ the image's `alt` text in its `aria-label`; matching CSS lives in
1361
+ `@pie-players/pie-theme` (`components.css`).
1362
+ - **Tool icons and SSML** - tool-registered icon markup is parsed and
1363
+ DOMPurified inside the toolbar at render time; SSML payloads are
1364
+ restricted to an allow-listed subset of SSML tags/attributes before
1365
+ being forwarded to TTS providers. Do not ship tools that rely on raw
1366
+ `<script>` or event-handler attributes in their icon strings.
1367
+
1061
1368
  ## Related Documentation
1062
1369
 
1063
1370
  - **[Tool Registry Architecture](docs/TOOL_REGISTRY.md)** - ⭐ NEW - Registry-based tool management and QTI 3.0 PNP support
1064
1371
  - **[PNP Configuration Guide](docs/PNP_CONFIGURATION.md)** - ⭐ NEW - How to configure student profiles, district policies, and governance rules
1065
1372
  - [ToolkitCoordinator Architecture](../../docs/architecture/TOOLKIT_COORDINATOR.md) - Design decisions and patterns
1066
1373
  - [Section Player README](../section-player/README.md) - Section player integration
1374
+ - [Section Player Architecture](../section-player/ARCHITECTURE.md#layered-runtime-engine-post-m7) - Layered runtime engine, kernel/toolkit wiring, single-engine invariant
1067
1375
  - [Framework-Owned Error Handling](../../docs/tools-and-accomodations/framework-owned-error-handling.md) - Canonical framework error model/events and fallback behavior
1068
1376
  - [Safe Custom Tool Configuration](../../docs/tools-and-accomodations/safe-custom-tool-config.md) - Host-side config patterns and validation guidance
1069
1377
  - [Architecture Overview](../../docs/architecture/architecture.md) - Complete system architecture