@pie-players/pie-assessment-toolkit 0.3.73 → 0.3.75

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 (274) hide show
  1. package/README.md +469 -318
  2. package/dist/attempt/AssessmentSession.d.ts +1 -5
  3. package/dist/attempt/AssessmentSession.js +0 -29
  4. package/dist/attempt/TestSession.d.ts +1 -20
  5. package/dist/attempt/TestSession.js +8 -99
  6. package/dist/components/ItemScope.custom-element.d.ts +1 -0
  7. package/dist/components/ItemScope.custom-element.js +1 -0
  8. package/dist/components/ItemToolBar.custom-element.d.ts +1 -0
  9. package/dist/components/ItemToolBar.custom-element.js +1 -1
  10. package/dist/components/PieAssessmentToolkit.custom-element.d.ts +1 -0
  11. package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
  12. package/dist/components/SectionToolBar.custom-element.d.ts +1 -0
  13. package/dist/components/SectionToolBar.custom-element.js +1 -1
  14. package/dist/components/chunks/ItemToolBar-7tt7v2xp.js +4 -0
  15. package/dist/components/chunks/ItemToolBar-97hx60z4.js +2 -0
  16. package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +2 -0
  17. package/dist/components/chunks/ItemToolBar-jx38w99s.js +2 -0
  18. package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +40 -0
  19. package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
  20. package/dist/components/chunks/sre-engine-jd8cc08x.js +1 -0
  21. package/dist/components/item-scope-element.d.ts +2 -0
  22. package/dist/components/item-scope-element.js +2 -0
  23. package/dist/context/assessment-toolkit-context.d.ts +32 -6
  24. package/dist/index.d.ts +23 -48
  25. package/dist/index.js +12 -37
  26. package/dist/policy/core/PolicySource.d.ts +1 -1
  27. package/dist/policy/core/ToolPolicyEngine.d.ts +85 -34
  28. package/dist/policy/core/ToolPolicyEngine.js +148 -46
  29. package/dist/policy/core/compose-decision.d.ts +28 -7
  30. package/dist/policy/core/compose-decision.js +75 -54
  31. package/dist/policy/core/decision-types.d.ts +65 -34
  32. package/dist/policy/core/feature-decision.d.ts +16 -9
  33. package/dist/policy/core/feature-decision.js +14 -8
  34. package/dist/policy/core/pnp-policy-inputs.d.ts +24 -33
  35. package/dist/policy/core/pnp-policy-inputs.js +28 -56
  36. package/dist/policy/core/provenance.d.ts +9 -4
  37. package/dist/policy/core/provenance.js +1 -1
  38. package/dist/policy/engine.d.ts +7 -23
  39. package/dist/policy/engine.js +5 -20
  40. package/dist/policy/sources/PnpPolicySource.d.ts +44 -61
  41. package/dist/policy/sources/PnpPolicySource.js +117 -118
  42. package/dist/runtime/SectionControllerBinding.d.ts +279 -0
  43. package/dist/runtime/SectionControllerBinding.js +450 -0
  44. package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
  45. package/dist/runtime/SectionRuntimeEngine.js +30 -372
  46. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
  47. package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
  48. package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
  49. package/dist/runtime/adapter/dom-event-bridge.js +17 -44
  50. package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -2
  51. package/dist/runtime/adapter/subscriber-fanout.js +1 -2
  52. package/dist/runtime/catalog-context.d.ts +19 -0
  53. package/dist/runtime/catalog-context.js +28 -0
  54. package/dist/runtime/composition-emit-scheduler.d.ts +9 -15
  55. package/dist/runtime/composition-emit-scheduler.js +9 -15
  56. package/dist/runtime/content-language.d.ts +32 -0
  57. package/dist/runtime/content-language.js +46 -0
  58. package/dist/runtime/content-region.d.ts +17 -0
  59. package/dist/runtime/content-region.js +29 -0
  60. package/dist/runtime/content-registration.d.ts +13 -0
  61. package/dist/runtime/content-registration.js +25 -0
  62. package/dist/runtime/core/SectionEngineCore.d.ts +2 -2
  63. package/dist/runtime/core/SectionEngineCore.js +2 -2
  64. package/dist/runtime/core/cohort.d.ts +1 -1
  65. package/dist/runtime/core/cohort.js +1 -1
  66. package/dist/runtime/core/engine-input.d.ts +18 -30
  67. package/dist/runtime/core/engine-input.js +11 -12
  68. package/dist/runtime/core/engine-output.d.ts +16 -40
  69. package/dist/runtime/core/engine-output.js +9 -21
  70. package/dist/runtime/core/engine-readiness.d.ts +5 -37
  71. package/dist/runtime/core/engine-readiness.js +8 -41
  72. package/dist/runtime/core/engine-resolver.d.ts +46 -74
  73. package/dist/runtime/core/engine-resolver.js +18 -43
  74. package/dist/runtime/core/engine-state.d.ts +28 -36
  75. package/dist/runtime/core/engine-state.js +18 -21
  76. package/dist/runtime/core/engine-transition.d.ts +14 -19
  77. package/dist/runtime/core/engine-transition.js +71 -51
  78. package/dist/runtime/engine.d.ts +16 -19
  79. package/dist/runtime/engine.js +12 -19
  80. package/dist/runtime/page-warnings.d.ts +13 -0
  81. package/dist/runtime/page-warnings.js +22 -0
  82. package/dist/runtime/policy-input-forwarding.d.ts +25 -0
  83. package/dist/runtime/policy-input-forwarding.js +25 -0
  84. package/dist/runtime/registration-events.d.ts +14 -0
  85. package/dist/runtime/runtime-event-claim.d.ts +8 -0
  86. package/dist/runtime/runtime-event-claim.js +15 -0
  87. package/dist/runtime/shell-event-bridge.d.ts +45 -0
  88. package/dist/runtime/shell-event-bridge.js +127 -0
  89. package/dist/runtime/shell-registration.d.ts +73 -0
  90. package/dist/runtime/shell-registration.js +95 -0
  91. package/dist/runtime/shell-scope.d.ts +68 -0
  92. package/dist/runtime/shell-scope.js +188 -0
  93. package/dist/runtime/tool-host-contract.d.ts +28 -10
  94. package/dist/runtime/tool-host-contract.js +31 -10
  95. package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
  96. package/dist/runtime/unclaimed-registration-watch.js +23 -0
  97. package/dist/services/ElementToolStateStore.d.ts +14 -10
  98. package/dist/services/ElementToolStateStore.js +37 -13
  99. package/dist/services/HighlightCoordinator.d.ts +22 -28
  100. package/dist/services/HighlightCoordinator.js +431 -327
  101. package/dist/services/RangeSerializer.d.ts +10 -1
  102. package/dist/services/RangeSerializer.js +87 -14
  103. package/dist/services/TTSService.d.ts +130 -69
  104. package/dist/services/TTSService.js +713 -570
  105. package/dist/services/ToolCoordinator.d.ts +43 -66
  106. package/dist/services/ToolCoordinator.js +164 -208
  107. package/dist/services/ToolRegistry.d.ts +78 -54
  108. package/dist/services/ToolRegistry.js +107 -105
  109. package/dist/services/ToolkitCoordinator.d.ts +261 -175
  110. package/dist/services/ToolkitCoordinator.js +667 -433
  111. package/dist/services/audio-handoff.d.ts +6 -7
  112. package/dist/services/audio-handoff.js +20 -14
  113. package/dist/services/catalog-owner.d.ts +11 -1
  114. package/dist/services/catalog-owner.js +25 -0
  115. package/dist/services/create-tools-config.d.ts +2 -2
  116. package/dist/services/framework-error-bus.d.ts +3 -6
  117. package/dist/services/framework-error-bus.js +3 -6
  118. package/dist/services/framework-error.d.ts +30 -8
  119. package/dist/services/framework-error.js +9 -7
  120. package/dist/services/interfaces.d.ts +158 -129
  121. package/dist/services/section-controller-types.d.ts +15 -0
  122. package/dist/services/section-session-assignment.d.ts +21 -0
  123. package/dist/services/section-session-assignment.js +82 -0
  124. package/dist/services/spoken-audio-cards.d.ts +0 -2
  125. package/dist/services/spoken-audio-cards.js +1 -3
  126. package/dist/services/tool-config-defaults.d.ts +3 -3
  127. package/dist/services/tool-config-defaults.js +3 -3
  128. package/dist/services/tool-config-validation.d.ts +14 -4
  129. package/dist/services/tool-config-validation.js +63 -71
  130. package/dist/services/tool-context.d.ts +0 -8
  131. package/dist/services/tool-context.js +29 -25
  132. package/dist/services/tool-icons.d.ts +4 -3
  133. package/dist/services/tool-icons.js +4 -3
  134. package/dist/services/tool-instance-id.d.ts +0 -1
  135. package/dist/services/tool-instance-id.js +0 -6
  136. package/dist/services/tool-providers/TTSToolProvider.d.ts +41 -80
  137. package/dist/services/tool-providers/TTSToolProvider.js +81 -40
  138. package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -4
  139. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +32 -28
  140. package/dist/services/tool-providers/ToolProviderRegistry.js +121 -107
  141. package/dist/services/tool-providers/index.d.ts +0 -9
  142. package/dist/services/tool-providers/index.js +2 -4
  143. package/dist/services/tool-request.d.ts +27 -12
  144. package/dist/services/tool-request.js +24 -14
  145. package/dist/services/toolbar-decided-tools.d.ts +15 -0
  146. package/dist/services/toolbar-decided-tools.js +28 -0
  147. package/dist/services/tools-config-normalizer.d.ts +37 -23
  148. package/dist/services/tools-config-normalizer.js +2 -10
  149. package/dist/services/tts/browser-provider.d.ts +38 -2
  150. package/dist/services/tts/browser-provider.js +276 -140
  151. package/dist/services/tts/catalog-span-alignment.d.ts +2 -17
  152. package/dist/services/tts/catalog-span-alignment.js +17 -142
  153. package/dist/services/tts/flat-tree.d.ts +71 -0
  154. package/dist/services/tts/flat-tree.js +269 -0
  155. package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
  156. package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
  157. package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
  158. package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
  159. package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
  160. package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
  161. package/dist/services/tts/generated-speech/types.d.ts +0 -12
  162. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +12 -62
  163. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +38 -5
  164. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +3 -6
  165. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
  166. package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
  167. package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
  168. package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
  169. package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
  170. package/dist/services/tts/math-alignment/index.d.ts +0 -5
  171. package/dist/services/tts/math-alignment/index.js +5 -25
  172. package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +9 -1
  173. package/dist/services/tts/math-alignment/speech-tokenizer.js +38 -36
  174. package/dist/services/tts/math-alignment/types.d.ts +4 -2
  175. package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
  176. package/dist/services/tts/math-aware-text-processing.js +74 -18
  177. package/dist/services/tts/math-control-names.d.ts +21 -0
  178. package/dist/services/tts/math-control-names.js +142 -0
  179. package/dist/services/tts/math-speech.d.ts +16 -1
  180. package/dist/services/tts/math-speech.js +55 -19
  181. package/dist/services/tts/provider-options.d.ts +41 -0
  182. package/dist/services/tts/provider-options.js +1 -0
  183. package/dist/services/tts/sre-engine.d.ts +7 -0
  184. package/dist/services/tts/sre-engine.js +11 -0
  185. package/dist/services/tts/sre-locales.d.ts +14 -0
  186. package/dist/services/tts/sre-locales.js +57 -0
  187. package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
  188. package/dist/services/tts/ssml/spoken-text.js +29 -0
  189. package/dist/services/tts/start-failure.d.ts +11 -0
  190. package/dist/services/tts/start-failure.js +19 -0
  191. package/dist/services/tts/text-processing.d.ts +9 -1
  192. package/dist/services/tts/text-processing.js +46 -34
  193. package/dist/services/tts/text-segmentation.d.ts +7 -0
  194. package/dist/services/tts/text-segmentation.js +47 -0
  195. package/dist/services/tts-runtime-config.d.ts +44 -18
  196. package/dist/services/tts-runtime-config.js +22 -93
  197. package/dist/tools/content-capability-resolution.d.ts +7 -9
  198. package/dist/tools/content-capability-resolution.js +8 -45
  199. package/dist/tools/registration.d.ts +58 -0
  200. package/dist/tools/registration.js +54 -0
  201. package/dist/tools/registrations/toolbar-registration-helpers.js +2 -2
  202. package/dist/tools/tool-surface-host.d.ts +6 -10
  203. package/dist/tools/tool-surface-host.js +44 -42
  204. package/dist/tools/tool-tag-map.d.ts +5 -15
  205. package/dist/tools/tool-tag-map.js +6 -20
  206. package/dist/utils/structural-equality.d.ts +11 -0
  207. package/dist/utils/structural-equality.js +47 -0
  208. package/package.json +24 -59
  209. package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
  210. package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
  211. package/dist/components/chunks/ItemToolBar-byq80z42.js +0 -40
  212. package/dist/components/chunks/ItemToolBar-d36g3aw4.js +0 -49
  213. package/dist/context/runtime-context-consumer.d.ts +0 -13
  214. package/dist/context/runtime-context-consumer.js +0 -51
  215. package/dist/item-loader.d.ts +0 -44
  216. package/dist/item-loader.js +0 -61
  217. package/dist/policy/core/engine-context.d.ts +0 -29
  218. package/dist/policy/core/engine-context.js +0 -27
  219. package/dist/policy/core/policy-source-tag.d.ts +0 -31
  220. package/dist/policy/core/policy-source-tag.js +0 -28
  221. package/dist/policy/internal.d.ts +0 -25
  222. package/dist/policy/internal.js +0 -25
  223. package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
  224. package/dist/runtime/adapter/coordinator-bridge.js +0 -126
  225. package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
  226. package/dist/runtime/adapter/framework-error-bridge.js +0 -46
  227. package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
  228. package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
  229. package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
  230. package/dist/runtime/core/engine-stage-derivation.js +0 -29
  231. package/dist/runtime/engine-context.d.ts +0 -39
  232. package/dist/runtime/engine-context.js +0 -31
  233. package/dist/runtime/internal.d.ts +0 -43
  234. package/dist/runtime/internal.js +0 -45
  235. package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -80
  236. package/dist/runtime/section-runtime-engine-host-context.js +0 -95
  237. package/dist/runtime/stage-emit-gate.d.ts +0 -92
  238. package/dist/runtime/stage-emit-gate.js +0 -82
  239. package/dist/services/I18nService.d.ts +0 -47
  240. package/dist/services/I18nService.js +0 -84
  241. package/dist/services/ThemeProvider.d.ts +0 -95
  242. package/dist/services/ThemeProvider.js +0 -334
  243. package/dist/services/ToolConfigResolver.d.ts +0 -161
  244. package/dist/services/ToolConfigResolver.js +0 -229
  245. package/dist/services/catalog-media.d.ts +0 -56
  246. package/dist/services/catalog-media.js +0 -190
  247. package/dist/services/createDefaultToolRegistry.d.ts +0 -53
  248. package/dist/services/createDefaultToolRegistry.js +0 -45
  249. package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
  250. package/dist/services/defaultPersonalNeedsProfile.js +0 -26
  251. package/dist/services/pnp-standard-features.d.ts +0 -238
  252. package/dist/services/pnp-standard-features.js +0 -338
  253. package/dist/services/tool-providers/CortexToolProvider.d.ts +0 -13
  254. package/dist/services/tool-providers/CortexToolProvider.js +0 -32
  255. package/dist/services/tool-providers/DesmosToolProvider.d.ts +0 -14
  256. package/dist/services/tool-providers/DesmosToolProvider.js +0 -32
  257. package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +0 -16
  258. package/dist/services/tool-providers/GeoGebraToolProvider.js +0 -32
  259. package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +0 -35
  260. package/dist/services/tool-providers/LazyCalculatorToolProvider.js +0 -95
  261. package/dist/services/tts/visual-line-ranges.d.ts +0 -18
  262. package/dist/services/tts/visual-line-ranges.js +0 -226
  263. package/dist/tools/client.d.ts +0 -12
  264. package/dist/tools/client.js +0 -14
  265. package/dist/tools/index.d.ts +0 -8
  266. package/dist/tools/index.js +0 -10
  267. package/dist/tools/internal.d.ts +0 -43
  268. package/dist/tools/internal.js +0 -41
  269. package/dist/tools/response-discovery.d.ts +0 -61
  270. package/dist/tools/response-discovery.js +0 -113
  271. package/dist/tools/types.d.ts +0 -337
  272. package/dist/tools/types.js +0 -12
  273. package/dist/tools/variant-resolver.d.ts +0 -41
  274. package/dist/tools/variant-resolver.js +0 -207
package/dist/index.js CHANGED
@@ -6,55 +6,30 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
- export { assessmentToolkitHostRuntimeContext, assessmentToolkitRegionScopeContext, assessmentToolkitRuntimeContext, assessmentToolkitShellContext, } from "./context/assessment-toolkit-context.js";
10
- export { connectAssessmentToolkitHostRuntimeContext, connectAssessmentToolkitRegionScopeContext, connectAssessmentToolkitRuntimeContext, connectAssessmentToolkitShellContext, } from "./context/runtime-context-consumer.js";
11
9
  export { catalogOwnerContextFor, } from "./services/catalog-owner.js";
12
- export { PIE_INTERNAL_CONTENT_LOADED_EVENT, PIE_INTERNAL_FORMATIVE_ACTION_EVENT, PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT, PIE_INTERNAL_ITEM_PLAYER_ERROR_EVENT, PIE_INTERNAL_MEDIA_TIME_SOURCE_EVENT, PIE_ITEM_SESSION_CHANGED_EVENT, PIE_REGISTER_EVENT, PIE_UNREGISTER_EVENT, } from "./runtime/registration-events.js";
13
- export { connectToolRegionScopeContext, connectToolRuntimeContext, connectToolShellContext, createCrossBoundaryEvent, dispatchCrossBoundaryEvent, isContextValueDefined, } from "./runtime/tool-host-contract.js";
14
- export { AccessibilityCatalogResolver, catalogCardForm, isKnownCatalogType, KNOWN_CATALOG_TYPES, } from "./services/AccessibilityCatalogResolver.js";
15
- // Context Variable Store (QTI 3.0 Context Declarations)
16
- export { ContextVariableStore } from "./services/ContextVariableStore.js";
10
+ export { PIE_INTERNAL_FORMATIVE_ACTION_EVENT, PIE_INTERNAL_ITEM_SESSION_CHANGED_EVENT, PIE_INTERNAL_MEDIA_TIME_SOURCE_EVENT, PIE_ITEM_SESSION_CHANGED_EVENT, } from "./runtime/registration-events.js";
11
+ export { createShellScope, } from "./runtime/shell-scope.js";
12
+ // A shell's translation of its player's events into the runtime's, shared by the
13
+ // section player's passage shell and `<pie-item-scope>`.
14
+ export { createShellEventBridge, } from "./runtime/shell-event-bridge.js";
15
+ export { connectToolRuntimeContext, dispatchCrossBoundaryEvent, } from "./runtime/tool-host-contract.js";
16
+ export { AccessibilityCatalogResolver } from "./services/AccessibilityCatalogResolver.js";
17
17
  // Element Tool State Store (Element-level ephemeral tool state)
18
18
  export { ElementToolStateStore } from "./services/ElementToolStateStore.js";
19
19
  export { HighlightColor, HighlightCoordinator, HighlightType, } from "./services/HighlightCoordinator.js";
20
- export { RangeSerializer } from "./services/RangeSerializer.js";
21
- export { I18nService } from "./services/I18nService.js";
22
20
  export { ToolRegistry } from "./services/ToolRegistry.js";
23
- export { DEFAULT_TOOL_REQUEST_LEVEL } from "./services/tool-request.js";
24
- export { extractTextContent, hasChoiceInteraction, hasMathContent, hasReadableText, isAssessmentContext, isElementContext, isItemContext, isPassageContext, isRubricContext, isSectionContext, } from "./services/tool-context.js";
25
- export { DEFAULT_TOOL_PLACEMENT } from "./services/createDefaultToolRegistry.js";
21
+ export { hasReadableText } from "./services/tool-context.js";
26
22
  export { createToolsConfig } from "./services/create-tools-config.js";
27
- export { createEmptyPersonalNeedsProfile } from "./services/defaultPersonalNeedsProfile.js";
28
- export { createToolElement, resolveToolTag, toToolIdFromTag, } from "./tools/tool-tag-map.js";
29
- // QTI 3.0 Standard Access Features
30
- export { ALL_STANDARD_ACCESS_FEATURES, EXAMPLE_PNP_CONFIGURATIONS, getFeatureCategory, getFeaturesInCategory, isStandardAccessFeature, QTI_STANDARD_ACCESS_FEATURES, } from "./services/pnp-standard-features.js";
31
- // Media-bearing catalog cards: the generic half, shared by every card form that
32
- // references a recording rather than carrying text. `applyMediaFragment` reached
33
- // the public surface through `sign-language-cards.js` until signing moved to
34
- // `@pie-players/pie-tool-sign-language`; it is exported from its own module now,
35
- // alongside the validators a capability package needs to read a media payload.
36
- export { applyMediaFragment, enforceMediaFragment, isSafeMediaSrc, isUnsupportedMediaAssetVersion, normalizeMediaFragment, normalizeMediaSources, SUPPORTED_MEDIA_ASSET_VERSION, trimmedOrUndefined, } from "./services/catalog-media.js";
37
- export { resolveSpokenAudioMedia, SPOKEN_CATALOG_TYPE, } from "./services/spoken-audio-cards.js";
38
23
  export { SSMLExtractor } from "./services/SSMLExtractor.js";
39
- export { ThemeProvider } from "./services/ThemeProvider.js";
40
24
  // Tool Coordinator
41
25
  export { ToolCoordinator, ZIndexLayer } from "./services/ToolCoordinator.js";
42
26
  export { ToolkitCoordinator } from "./services/ToolkitCoordinator.js";
43
- export { formatFrameworkErrorForConsole, frameworkErrorFromToolConfigDiagnostics, frameworkErrorFromUnknown, toFrameworkErrorModel, } from "./services/framework-error.js";
44
- export { isExternalIconUrl, isInlineSvgIcon, isToolbarLinkItem, isValidToolbarItemShape, } from "./services/toolbar-items.js";
45
- export { normalizeToolsConfig, normalizeToolAlias, normalizeToolList, parseToolList, } from "./services/tools-config-normalizer.js";
46
- export { isHostDeniedFeature } from "./policy/core/feature-decision.js";
47
- export { frameworkErrorFromToolConfigValidation, normalizeAndValidateToolsConfig, } from "./services/tool-config-validation.js";
48
- export { createScopedToolId, parseScopedToolId, toOverlayToolId, } from "./services/tool-instance-id.js";
27
+ export { frameworkErrorFromUnknown, toFrameworkErrorModel, } from "./services/framework-error.js";
28
+ export { createScopedToolId } from "./services/tool-instance-id.js";
49
29
  export { PlaybackState, TTSService } from "./services/TTSService.js";
50
- export { PIE_TTS_CONTROL_HANDOFF_EVENT, } from "./services/tts-control-events.js";
51
- export { bindTtsAudioHandoff, pauseTtsForMediaAudio, } from "./services/audio-handoff.js";
52
30
  export { BrowserTTSProvider } from "./services/tts/browser-provider.js";
53
- export { DEFAULT_TTS_SPEED_OPTIONS, formatTTSSpeedOptionsAsText, normalizeTTSLayoutMode, normalizeTTSSpeedControlOptions, normalizeTTSSpeedOptionConfigs, normalizeTTSSpeedOptions, parseTTSSpeedOptionsFromText, resolveTTSHostToolbarLayout, resolveTTSLayoutMode, resolveTTSRuntimeSettings, } from "./services/tts-runtime-config.js";
54
- export { createFetchItemLoader, createLoadItem, ItemLoadError, } from "./item-loader.js";
55
- export { createMemoryStorage, createTestAttemptSessionIdentifier, createNewTestAttemptSession, getBrowserLocalStorage, getOrCreateAnonymousDeviceId, getTestAttemptSessionStorageKey, loadTestAttemptSession, saveTestAttemptSession, setCurrentPosition, upsertItemSessionFromPieSessionChange, upsertVisitedItem, } from "./attempt/TestSession.js";
56
- export { createNewAssessmentSession, getAssessmentSessionStorageKey, loadAssessmentSession, saveAssessmentSession, setCurrentSectionPosition, upsertSectionSession, } from "./attempt/AssessmentSession.js";
57
- export { buildActivitySessionItemUpdate, buildActivitySessionPatchFromTestAttemptSession, mapActivityToTestAttemptSession, toItemSessionsRecord, } from "./attempt/adapters/activity-to-test-attempt-session.js";
31
+ export { createNewTestAttemptSession, setCurrentPosition, toItemSessionsRecord, upsertItemSessionFromPieSessionChange, upsertVisitedItem, } from "./attempt/TestSession.js";
32
+ export { createNewAssessmentSession, setCurrentSectionPosition, upsertSectionSession, } from "./attempt/AssessmentSession.js";
58
33
  // Section Player - Use @pie-players/pie-section-player web component
59
34
  // ============================================================================
60
35
  // Shared Components
@@ -57,7 +57,7 @@ export interface PolicySourceResult {
57
57
  decisions?: PolicySourceProvenanceEntry[];
58
58
  }
59
59
  export interface PolicySource {
60
- /** Stable identifier — used for `custom.${id}` source tags. */
60
+ /** Stable identifier, recorded as `customSourceId` on the decisions and diagnostics it causes. */
61
61
  readonly id: string;
62
62
  refine(context: PolicySourceDecisionContext): PolicySourceResult;
63
63
  }
@@ -3,43 +3,40 @@
3
3
  * `docs/tools-and-accomodations/architecture.md`.
4
4
  *
5
5
  * Wraps `composeDecision(...)` with engine-instance state:
6
- * - bound inputs (`tools`, `assessment`, `currentItemRef`, ...)
6
+ * - bound inputs (`tools`, `assessment`, `pnpEnforcement`) and the
7
+ * settings of the mounted items
7
8
  * - registered custom `PolicySource`s
8
9
  * - `onPolicyChange` subscriber bus
9
10
  * - `dispose()` clean-up
10
11
  *
11
12
  * The class has no DOM dependency and no Svelte runes. It is safe to
12
13
  * instantiate inside a worker, in a Node test, or inside a CE
13
- * connectedCallback. The Svelte context glue (`TOOL_POLICY_ENGINE_KEY`)
14
- * lives in `engine-context.ts`.
15
- *
16
- * PR 1 ships the engine *without callers*. PR 2 wires it into
17
- * `ToolkitCoordinator`. PR 3 switches `<pie-item-toolbar>` over.
14
+ * connectedCallback. `ToolkitCoordinator` owns the instance, and
15
+ * `<pie-item-toolbar>` reads its decisions through the coordinator.
18
16
  */
19
- import type { AssessmentEntity, AssessmentItemRef } from "@pie-players/pie-players-shared/types";
17
+ import type { AssessmentEntity, ItemSettings } from "@pie-players/pie-players-shared/types";
20
18
  import type { CanonicalToolsConfig, ToolPlacementLevel } from "../../services/tools-config-normalizer.js";
21
19
  import type { ToolRegistry } from "../../services/ToolRegistry.js";
22
- import type { ToolPolicyDecision, ToolPolicyDecisionRequest } from "./decision-types.js";
20
+ import type { ToolPolicyDecision, ToolPolicyDecisionRequest, ToolScope } from "./decision-types.js";
23
21
  import type { PolicySource } from "./PolicySource.js";
24
22
  import type { FeaturePolicyDecision } from "./feature-decision.js";
25
23
  export type PnpEnforcementMode = "on" | "off";
26
24
  export interface ToolPolicyEngineInputs {
27
25
  tools?: CanonicalToolsConfig | null;
28
26
  assessment?: AssessmentEntity | null;
29
- currentItemRef?: AssessmentItemRef | null;
30
27
  /**
31
- * Explicit PNP/profile override. When omitted (or `undefined`), the engine
32
- * defaults to {@link resolveDefaultPnpEnforcement} over the bound
33
- * `assessment` and `currentItemRef`: `"on"` if they carry any
34
- * PNP/profile policy material (PNP, district policy, test
35
- * administration, item-level required/restricted/parameters),
28
+ * Explicit PNP/profile override; `null` or omitted is auto-mode. Auto-mode
29
+ * resolves per decision through {@link resolveDefaultPnpEnforcement}: `"on"`
30
+ * when the bound `assessment` carries PNP/profile policy material (PNP,
31
+ * district policy, test administration), and for a decision scoped to an item
32
+ * also when that item's settings carry required/restricted/parameters;
36
33
  * `"off"` otherwise. `tests/policy/pnp-default-on.test.ts` locks the rule.
37
34
  *
38
35
  * Hosts that want to force a mode pass `"on"` or `"off"`
39
36
  * explicitly. Embedded under `<pie-section-player-*>` the preferred
40
37
  * override flows through `runtime.tools.pnpEnforcement`.
41
38
  */
42
- pnpEnforcement?: PnpEnforcementMode;
39
+ pnpEnforcement?: PnpEnforcementMode | null;
43
40
  }
44
41
  export interface ToolPolicyEngineArgs {
45
42
  toolRegistry: ToolRegistry;
@@ -49,28 +46,41 @@ export interface ToolPolicyEngineArgs {
49
46
  contextId?: string;
50
47
  }
51
48
  export interface ToolPolicyChangeEvent {
52
- /** What changed in this update. */
53
- reason: "inputs" | "policy-source-added" | "policy-source-removed" | "pnp-enforcement" | "disposed";
49
+ /**
50
+ * What changed in this update. `"item-settings"` is a mounted item's
51
+ * settings changing: it can change which tools that item's own toolbar
52
+ * shows, and nothing else a toolbar renders.
53
+ */
54
+ reason: "inputs" | "item-settings" | "policy-source-added" | "policy-source-removed" | "pnp-enforcement" | "disposed";
54
55
  /** Snapshot of the engine's inputs after the change. */
55
56
  inputs: Readonly<ResolvedEngineInputs>;
56
57
  }
57
58
  export interface ResolvedEngineInputs {
58
59
  tools: CanonicalToolsConfig;
59
60
  assessment: AssessmentEntity | null;
60
- currentItemRef: AssessmentItemRef | null;
61
+ /**
62
+ * The mode a decision not scoped to an item applies. In auto-mode an
63
+ * item-scoped decision also turns on for its item's settings.
64
+ */
61
65
  pnpEnforcement: PnpEnforcementMode;
66
+ /** The host's override of auto-mode, or `null` in auto-mode. */
67
+ pnpEnforcementOverride: PnpEnforcementMode | null;
62
68
  }
63
69
  export type ToolPolicyChangeListener = (event: ToolPolicyChangeEvent) => void;
64
70
  export declare class ToolPolicyEngine {
65
- private readonly toolRegistry;
71
+ private toolRegistry;
66
72
  private readonly contextId;
67
- private readonly pnpPolicySource;
73
+ private pnpPolicySource;
68
74
  private readonly customSources;
69
75
  private readonly listeners;
70
76
  private tools;
71
77
  private assessment;
72
- private currentItemRef;
73
- private pnpEnforcement;
78
+ private pnpEnforcementOverride;
79
+ /**
80
+ * Mounted items' settings by canonical item id, newest registration last;
81
+ * see {@link registerItemSettings}.
82
+ */
83
+ private readonly itemSettings;
74
84
  private disposed;
75
85
  constructor(args: ToolPolicyEngineArgs);
76
86
  /**
@@ -87,10 +97,33 @@ export declare class ToolPolicyEngine {
87
97
  * values even though the policy decision itself is identical.
88
98
  * Hosts that want timestamp-stable provenance should reuse a
89
99
  * cached `ToolPolicyDecision` rather than re-call `decide(...)`.
100
+ *
101
+ * An item's registered settings apply to the item's own toolbar: a request
102
+ * at item level whose scope is the item. Every other request — a section,
103
+ * assessment or passage toolbar — is shared by the items it sits beside, so
104
+ * it leaves every item's settings out and reports each tool on its toolbar
105
+ * that one restricts or requires as `tool-policy.itemSettingNotApplied`.
90
106
  */
91
107
  decide(request: ToolPolicyDecisionRequest): ToolPolicyDecision;
92
108
  /**
93
- * Resolve eligibility for one PNP/AfA feature id through the six-level
109
+ * Whether some surface is granted `featureId`: a decision with no item scope,
110
+ * or one scoped to a registered item. A provider serves every surface, so its
111
+ * failure denies the accommodation wherever one surface is granted it.
112
+ */
113
+ grantsFeatureAnywhere(featureId: string): boolean;
114
+ /**
115
+ * File a mounted item's settings under its canonical id. They govern the
116
+ * decisions scoped to that item: its own toolbar, and a feature its content
117
+ * asks about with the item's scope. See {@link decide}.
118
+ *
119
+ * The same item mounted twice registers twice; the newest registration
120
+ * applies, and its cleanup hands the item back to the one before. Emits an
121
+ * `"item-settings"` change whenever the settings that apply to the item
122
+ * change.
123
+ */
124
+ registerItemSettings(itemId: string, settings: ItemSettings): () => void;
125
+ /**
126
+ * Resolve eligibility for one PNP/AfA feature id through the eight-level
94
127
  * precedence, independent of toolbar placement.
95
128
  *
96
129
  * For capabilities that render as their own surface rather than a toolbar
@@ -111,8 +144,11 @@ export declare class ToolPolicyEngine {
111
144
  * The decision reports whether an assessment was bound, which is the engine's
112
145
  * to answer rather than the policy source's — see
113
146
  * {@link FeaturePolicyDecision.assessmentBound}.
147
+ *
148
+ * `scope` is the surface asking. An item's scope brings in the item's
149
+ * registered settings, as {@link decide} does for the item's toolbar.
114
150
  */
115
- decideFeature(featureId: string): FeaturePolicyDecision;
151
+ decideFeature(featureId: string, scope?: ToolScope): FeaturePolicyDecision;
116
152
  /**
117
153
  * The host gates that hold for a feature id, or `null` when none fires.
118
154
  *
@@ -133,24 +169,39 @@ export declare class ToolPolicyEngine {
133
169
  /**
134
170
  * Apply a partial input update.
135
171
  *
136
- * Each key is value-diffed via `Object.is` before storing, so callers
137
- * may safely re-push the same reference (e.g. on Svelte effect
138
- * re-runs caused by unrelated tracked reads): no-op patches do not
139
- * fire `onPolicyChange` listeners. This contract is relied on by
140
- * `PieAssessmentToolkit.svelte`'s prop-forwarding effect and by the
141
- * coordinator's `decideToolPolicy()` tests.
172
+ * A key that did not change does not fire `onPolicyChange` listeners.
173
+ * `tools` is diffed with `Object.is`. `assessment` is diffed structurally,
174
+ * because hosts rebuild it from their own state on every render; the latest
175
+ * reference is stored either way.
142
176
  *
143
- * `pnpEnforcement` is stored on its own field and always emits with
144
- * a distinct `"pnp-enforcement"` reason when no other key changed,
145
- * so PR 4's auto-on flip can target that reason without a separate
146
- * event channel.
177
+ * `pnpEnforcement` sets the override, `null` or `undefined` returning to
178
+ * auto-mode, and emits with a distinct `"pnp-enforcement"` reason when no
179
+ * other key changed.
147
180
  */
148
181
  updateInputs(patch: Partial<ToolPolicyEngineInputs>): void;
182
+ /**
183
+ * Swap the registry PNP support ids resolve against, together with the tools
184
+ * config validated against it, and emit an `"inputs"` change. For a
185
+ * coordinator adopting its toolkit's registry.
186
+ */
187
+ replaceToolRegistry(toolRegistry: ToolRegistry, tools: CanonicalToolsConfig): void;
149
188
  registerPolicySource(source: PolicySource): () => void;
150
189
  onPolicyChange(listener: ToolPolicyChangeListener): () => void;
151
190
  getInputs(): Readonly<ResolvedEngineInputs>;
152
191
  dispose(): void;
153
192
  private snapshotInputs;
193
+ /** The override, else auto-mode over the assessment and `item`'s settings. */
194
+ private enforcementFor;
195
+ /** The settings that apply to `itemId`: its newest registration's. */
196
+ private itemSettingsFor;
197
+ private emitIfItemSettingsChanged;
198
+ /**
199
+ * The registered item an item scope names, by its canonical id. A scope
200
+ * of any other level names none.
201
+ */
202
+ private itemForScope;
203
+ /** The registered items whose settings their own toolbar enforces. */
204
+ private enforcedItems;
154
205
  private emit;
155
206
  private assertNotDisposed;
156
207
  }
@@ -3,24 +3,23 @@
3
3
  * `docs/tools-and-accomodations/architecture.md`.
4
4
  *
5
5
  * Wraps `composeDecision(...)` with engine-instance state:
6
- * - bound inputs (`tools`, `assessment`, `currentItemRef`, ...)
6
+ * - bound inputs (`tools`, `assessment`, `pnpEnforcement`) and the
7
+ * settings of the mounted items
7
8
  * - registered custom `PolicySource`s
8
9
  * - `onPolicyChange` subscriber bus
9
10
  * - `dispose()` clean-up
10
11
  *
11
12
  * The class has no DOM dependency and no Svelte runes. It is safe to
12
13
  * instantiate inside a worker, in a Node test, or inside a CE
13
- * connectedCallback. The Svelte context glue (`TOOL_POLICY_ENGINE_KEY`)
14
- * lives in `engine-context.ts`.
15
- *
16
- * PR 1 ships the engine *without callers*. PR 2 wires it into
17
- * `ToolkitCoordinator`. PR 3 switches `<pie-item-toolbar>` over.
14
+ * connectedCallback. `ToolkitCoordinator` owns the instance, and
15
+ * `<pie-item-toolbar>` reads its decisions through the coordinator.
18
16
  */
19
17
  import { normalizeToolList, normalizeToolsConfig, } from "../../services/tools-config-normalizer.js";
20
18
  import { hostFeatureDenial, interpretFeatureResult, } from "./feature-decision.js";
21
19
  import { composeDecision } from "./compose-decision.js";
22
20
  import { resolveDefaultPnpEnforcement } from "./pnp-policy-inputs.js";
23
- import { PnpPolicySource } from "../sources/PnpPolicySource.js";
21
+ import { structurallyEqual } from "../../utils/structural-equality.js";
22
+ import { PnpPolicySource, } from "../sources/PnpPolicySource.js";
24
23
  const DEFAULT_TOOLS = normalizeToolsConfig({
25
24
  policy: { allowed: [], blocked: [] },
26
25
  placement: { section: [], item: [], passage: [] },
@@ -34,8 +33,12 @@ export class ToolPolicyEngine {
34
33
  listeners = new Set();
35
34
  tools;
36
35
  assessment;
37
- currentItemRef;
38
- pnpEnforcement;
36
+ pnpEnforcementOverride;
37
+ /**
38
+ * Mounted items' settings by canonical item id, newest registration last;
39
+ * see {@link registerItemSettings}.
40
+ */
41
+ itemSettings = new Map();
39
42
  disposed = false;
40
43
  constructor(args) {
41
44
  this.toolRegistry = args.toolRegistry;
@@ -45,13 +48,7 @@ export class ToolPolicyEngine {
45
48
  const inputs = args.inputs ?? {};
46
49
  this.tools = inputs.tools ?? DEFAULT_TOOLS;
47
50
  this.assessment = inputs.assessment ?? null;
48
- this.currentItemRef = inputs.currentItemRef ?? null;
49
- this.pnpEnforcement =
50
- inputs.pnpEnforcement ??
51
- resolveDefaultPnpEnforcement({
52
- assessment: this.assessment,
53
- currentItemRef: this.currentItemRef,
54
- });
51
+ this.pnpEnforcementOverride = inputs.pnpEnforcement ?? null;
55
52
  }
56
53
  /**
57
54
  * Resolve the visible tool set for a given placement level and
@@ -67,27 +64,85 @@ export class ToolPolicyEngine {
67
64
  * values even though the policy decision itself is identical.
68
65
  * Hosts that want timestamp-stable provenance should reuse a
69
66
  * cached `ToolPolicyDecision` rather than re-call `decide(...)`.
67
+ *
68
+ * An item's registered settings apply to the item's own toolbar: a request
69
+ * at item level whose scope is the item. Every other request — a section,
70
+ * assessment or passage toolbar — is shared by the items it sits beside, so
71
+ * it leaves every item's settings out and reports each tool on its toolbar
72
+ * that one restricts or requires as `tool-policy.itemSettingNotApplied`.
70
73
  */
71
74
  decide(request) {
72
75
  this.assertNotDisposed();
73
76
  const requestContextId = request.scope.scopeId
74
77
  ? `${this.contextId}:${request.level}:${request.scope.scopeId}`
75
78
  : `${this.contextId}:${request.level}`;
79
+ const item = request.level === "item" ? this.itemForScope(request.scope) : undefined;
80
+ const shared = request.level !== "item" ||
81
+ request.scope.level === "section" ||
82
+ request.scope.level === "assessment";
76
83
  return composeDecision({
77
84
  request,
78
85
  tools: this.tools,
79
86
  pnpPolicy: {
80
87
  source: this.pnpPolicySource,
81
88
  assessment: this.assessment ?? undefined,
82
- currentItemRef: this.currentItemRef ?? undefined,
83
- enforcement: this.pnpEnforcement,
89
+ item,
90
+ enforcement: this.enforcementFor(item),
84
91
  },
92
+ unappliedItems: shared ? this.enforcedItems() : undefined,
85
93
  customSources: this.customSources,
86
94
  contextId: requestContextId,
87
95
  });
88
96
  }
89
97
  /**
90
- * Resolve eligibility for one PNP/AfA feature id through the six-level
98
+ * Whether some surface is granted `featureId`: a decision with no item scope,
99
+ * or one scoped to a registered item. A provider serves every surface, so its
100
+ * failure denies the accommodation wherever one surface is granted it.
101
+ */
102
+ grantsFeatureAnywhere(featureId) {
103
+ if (this.decideFeature(featureId).granted)
104
+ return true;
105
+ for (const id of this.itemSettings.keys()) {
106
+ if (this.decideFeature(featureId, { level: "item", scopeId: id }).granted) {
107
+ return true;
108
+ }
109
+ }
110
+ return false;
111
+ }
112
+ /**
113
+ * File a mounted item's settings under its canonical id. They govern the
114
+ * decisions scoped to that item: its own toolbar, and a feature its content
115
+ * asks about with the item's scope. See {@link decide}.
116
+ *
117
+ * The same item mounted twice registers twice; the newest registration
118
+ * applies, and its cleanup hands the item back to the one before. Emits an
119
+ * `"item-settings"` change whenever the settings that apply to the item
120
+ * change.
121
+ */
122
+ registerItemSettings(itemId, settings) {
123
+ this.assertNotDisposed();
124
+ const entry = { settings };
125
+ const before = this.itemSettingsFor(itemId);
126
+ const entries = this.itemSettings.get(itemId) ?? [];
127
+ entries.push(entry);
128
+ this.itemSettings.set(itemId, entries);
129
+ this.emitIfItemSettingsChanged(itemId, before);
130
+ return () => {
131
+ if (this.disposed)
132
+ return;
133
+ const current = this.itemSettings.get(itemId);
134
+ const index = current?.indexOf(entry) ?? -1;
135
+ if (!current || index < 0)
136
+ return;
137
+ const previous = this.itemSettingsFor(itemId);
138
+ current.splice(index, 1);
139
+ if (current.length === 0)
140
+ this.itemSettings.delete(itemId);
141
+ this.emitIfItemSettingsChanged(itemId, previous);
142
+ };
143
+ }
144
+ /**
145
+ * Resolve eligibility for one PNP/AfA feature id through the eight-level
91
146
  * precedence, independent of toolbar placement.
92
147
  *
93
148
  * For capabilities that render as their own surface rather than a toolbar
@@ -108,15 +163,18 @@ export class ToolPolicyEngine {
108
163
  * The decision reports whether an assessment was bound, which is the engine's
109
164
  * to answer rather than the policy source's — see
110
165
  * {@link FeaturePolicyDecision.assessmentBound}.
166
+ *
167
+ * `scope` is the surface asking. An item's scope brings in the item's
168
+ * registered settings, as {@link decide} does for the item's toolbar.
111
169
  */
112
- decideFeature(featureId) {
170
+ decideFeature(featureId, scope) {
113
171
  this.assertNotDisposed();
114
172
  const hostDenial = this.hostFeatureGate(featureId);
115
173
  if (hostDenial)
116
174
  return hostDenial;
117
175
  return interpretFeatureResult(featureId, this.pnpPolicySource.resolveFeature(featureId, {
118
176
  assessment: this.assessment ?? undefined,
119
- currentItemRef: this.currentItemRef ?? undefined,
177
+ item: this.itemForScope(scope),
120
178
  }),
121
179
  // `resolveFeature` takes the assessment as `undefined` either way, so the
122
180
  // source cannot tell an unbound host from one whose profile is silent.
@@ -159,17 +217,14 @@ export class ToolPolicyEngine {
159
217
  /**
160
218
  * Apply a partial input update.
161
219
  *
162
- * Each key is value-diffed via `Object.is` before storing, so callers
163
- * may safely re-push the same reference (e.g. on Svelte effect
164
- * re-runs caused by unrelated tracked reads): no-op patches do not
165
- * fire `onPolicyChange` listeners. This contract is relied on by
166
- * `PieAssessmentToolkit.svelte`'s prop-forwarding effect and by the
167
- * coordinator's `decideToolPolicy()` tests.
220
+ * A key that did not change does not fire `onPolicyChange` listeners.
221
+ * `tools` is diffed with `Object.is`. `assessment` is diffed structurally,
222
+ * because hosts rebuild it from their own state on every render; the latest
223
+ * reference is stored either way.
168
224
  *
169
- * `pnpEnforcement` is stored on its own field and always emits with
170
- * a distinct `"pnp-enforcement"` reason when no other key changed,
171
- * so PR 4's auto-on flip can target that reason without a separate
172
- * event channel.
225
+ * `pnpEnforcement` sets the override, `null` or `undefined` returning to
226
+ * auto-mode, and emits with a distinct `"pnp-enforcement"` reason when no
227
+ * other key changed.
173
228
  */
174
229
  updateInputs(patch) {
175
230
  this.assertNotDisposed();
@@ -184,21 +239,14 @@ export class ToolPolicyEngine {
184
239
  }
185
240
  if ("assessment" in patch) {
186
241
  const next = patch.assessment ?? null;
187
- if (!Object.is(this.assessment, next)) {
188
- this.assessment = next;
242
+ if (!structurallyEqual(this.assessment, next))
189
243
  changed = true;
190
- }
244
+ this.assessment = next;
191
245
  }
192
- if ("currentItemRef" in patch) {
193
- const next = patch.currentItemRef ?? null;
194
- if (!Object.is(this.currentItemRef, next)) {
195
- this.currentItemRef = next;
196
- changed = true;
197
- }
198
- }
199
- if ("pnpEnforcement" in patch && patch.pnpEnforcement) {
200
- if (this.pnpEnforcement !== patch.pnpEnforcement) {
201
- this.pnpEnforcement = patch.pnpEnforcement;
246
+ if ("pnpEnforcement" in patch) {
247
+ const next = patch.pnpEnforcement ?? null;
248
+ if (this.pnpEnforcementOverride !== next) {
249
+ this.pnpEnforcementOverride = next;
202
250
  pnpChanged = true;
203
251
  }
204
252
  }
@@ -209,6 +257,18 @@ export class ToolPolicyEngine {
209
257
  });
210
258
  }
211
259
  }
260
+ /**
261
+ * Swap the registry PNP support ids resolve against, together with the tools
262
+ * config validated against it, and emit an `"inputs"` change. For a
263
+ * coordinator adopting its toolkit's registry.
264
+ */
265
+ replaceToolRegistry(toolRegistry, tools) {
266
+ this.assertNotDisposed();
267
+ this.toolRegistry = toolRegistry;
268
+ this.pnpPolicySource = new PnpPolicySource(toolRegistry);
269
+ this.tools = tools;
270
+ this.emit({ reason: "inputs", inputs: this.snapshotInputs() });
271
+ }
212
272
  registerPolicySource(source) {
213
273
  this.assertNotDisposed();
214
274
  this.customSources.push(source);
@@ -247,16 +307,58 @@ export class ToolPolicyEngine {
247
307
  inputs: this.snapshotInputs(),
248
308
  });
249
309
  this.listeners.clear();
310
+ this.itemSettings.clear();
250
311
  this.customSources.length = 0;
251
312
  }
252
313
  snapshotInputs() {
253
314
  return Object.freeze({
254
315
  tools: this.tools,
255
316
  assessment: this.assessment,
256
- currentItemRef: this.currentItemRef,
257
- pnpEnforcement: this.pnpEnforcement,
317
+ pnpEnforcement: this.enforcementFor(),
318
+ pnpEnforcementOverride: this.pnpEnforcementOverride,
258
319
  });
259
320
  }
321
+ /** The override, else auto-mode over the assessment and `item`'s settings. */
322
+ enforcementFor(item) {
323
+ return (this.pnpEnforcementOverride ??
324
+ resolveDefaultPnpEnforcement({
325
+ assessment: this.assessment,
326
+ itemSettings: item?.settings,
327
+ }));
328
+ }
329
+ /** The settings that apply to `itemId`: its newest registration's. */
330
+ itemSettingsFor(itemId) {
331
+ return this.itemSettings.get(itemId)?.at(-1)?.settings;
332
+ }
333
+ emitIfItemSettingsChanged(itemId, before) {
334
+ if (structurallyEqual(before, this.itemSettingsFor(itemId)))
335
+ return;
336
+ this.emit({ reason: "item-settings", inputs: this.snapshotInputs() });
337
+ }
338
+ /**
339
+ * The registered item an item scope names, by its canonical id. A scope
340
+ * of any other level names none.
341
+ */
342
+ itemForScope(scope) {
343
+ if (scope?.level !== "item")
344
+ return undefined;
345
+ const id = scope.canonicalItemId || scope.itemId || scope.scopeId;
346
+ const settings = id ? this.itemSettingsFor(id) : undefined;
347
+ return settings ? { id, settings } : undefined;
348
+ }
349
+ /** The registered items whose settings their own toolbar enforces. */
350
+ enforcedItems() {
351
+ const items = [];
352
+ for (const id of this.itemSettings.keys()) {
353
+ const settings = this.itemSettingsFor(id);
354
+ if (!settings)
355
+ continue;
356
+ const item = { id, settings };
357
+ if (this.enforcementFor(item) === "on")
358
+ items.push(item);
359
+ }
360
+ return items;
361
+ }
260
362
  emit(event) {
261
363
  for (const listener of Array.from(this.listeners)) {
262
364
  try {
@@ -11,27 +11,48 @@
11
11
  * The tests in
12
12
  * `tests/policy/compose-decision.test.ts` lock the orchestration in.
13
13
  */
14
- import type { AssessmentEntity, AssessmentItemRef } from "@pie-players/pie-players-shared/types";
15
- import { type CanonicalToolsConfig } from "../../services/tools-config-normalizer.js";
16
- import type { ToolPolicyDecision, ToolPolicyDecisionRequest } from "./decision-types.js";
14
+ import type { AssessmentEntity } from "@pie-players/pie-players-shared/types";
15
+ import { type CanonicalToolsConfig, type ToolPlacementLevel } from "../../services/tools-config-normalizer.js";
16
+ import type { OverrideBlockedDetails, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolPolicyDiagnostic } from "./decision-types.js";
17
17
  import type { PolicySource } from "./PolicySource.js";
18
- import type { PnpPolicySource } from "../sources/PnpPolicySource.js";
18
+ import { type PnpPolicySourceRule } from "./provenance.js";
19
+ import type { PnpPolicyItem, PnpPolicySource } from "../sources/PnpPolicySource.js";
19
20
  export interface ComposeDecisionInputs {
20
21
  request: ToolPolicyDecisionRequest;
21
22
  tools: CanonicalToolsConfig;
22
23
  pnpPolicy: {
23
24
  source: PnpPolicySource | null;
24
25
  assessment?: AssessmentEntity;
25
- currentItemRef?: AssessmentItemRef;
26
+ /** The item an item-scoped decision is for; see {@link PnpPolicyItem}. */
27
+ item?: PnpPolicyItem;
26
28
  /**
27
29
  * "on" applies the PNP/profile precedence rules; "off" skips step 5
28
- * entirely. Defaults to "on" when an assessment is present
29
- * and the source is non-null (see M8 design Q5).
30
+ * entirely. The engine resolves it from the enforcement override, or
31
+ * from whether the assessment or item carries PNP/profile material.
30
32
  */
31
33
  enforcement: "on" | "off";
32
34
  };
35
+ /**
36
+ * Mounted items whose settings this decision leaves out: set for a section-
37
+ * or assessment-level toolbar. A tool on that toolbar that one of them
38
+ * restricts or requires gets a `tool-policy.itemSettingNotApplied`
39
+ * diagnostic.
40
+ */
41
+ unappliedItems?: readonly PnpPolicyItem[];
33
42
  customSources: readonly PolicySource[];
34
43
  /** Stable identifier used as the provenance `contextId`. */
35
44
  contextId: string;
36
45
  }
37
46
  export declare function composeDecision(inputs: ComposeDecisionInputs): ToolPolicyDecision;
47
+ /**
48
+ * A `tool-policy.overrideBlocked` diagnostic for a tool a `true` test
49
+ * administration override grants and `rule` withdraws. `level` is absent on a
50
+ * feature decision, which has no toolbar level.
51
+ */
52
+ export declare function overrideBlockedDiagnostic(toolId: string, rule: OverrideBlockedDetails["rule"], level?: ToolPlacementLevel): ToolPolicyDiagnostic;
53
+ /**
54
+ * A `tool-policy.unknownSupportId` diagnostic for an id the registry lacks,
55
+ * attributed to the highest-precedence rule naming it. `level` is absent on a
56
+ * feature decision, which has no toolbar level.
57
+ */
58
+ export declare function unknownSupportIdDiagnostic(supportId: string, origins: readonly PnpPolicySourceRule[], level?: ToolPlacementLevel): ToolPolicyDiagnostic;