@pie-players/pie-assessment-toolkit 0.3.75 → 1.0.0-beta.1

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 (142) hide show
  1. package/README.md +451 -541
  2. package/custom-elements.json +1315 -0
  3. package/dist/attempt/TestSession.d.ts +0 -1
  4. package/dist/attempt/TestSession.js +10 -5
  5. package/dist/components/ItemScope.custom-element.js +1 -1
  6. package/dist/components/ItemToolBar.custom-element.js +1 -1
  7. package/dist/components/PieAssessmentToolkit.custom-element.js +8 -8
  8. package/dist/components/SectionToolBar.custom-element.js +1 -1
  9. package/dist/components/chunks/{ItemToolBar-7tt7v2xp.js → ItemToolBar-3zrc4p3d.js} +4 -4
  10. package/dist/components/chunks/{ItemToolBar-jx38w99s.js → ItemToolBar-66hcwmfw.js} +2 -2
  11. package/dist/components/chunks/ItemToolBar-asenkvx6.js +40 -0
  12. package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
  13. package/dist/components/chunks/{ItemToolBar-97hx60z4.js → ItemToolBar-kkj69ptr.js} +1 -1
  14. package/dist/components/chunks/sre-engine-bj997w4b.js +1 -0
  15. package/dist/context/assessment-toolkit-context.d.ts +6 -0
  16. package/dist/index.d.ts +7 -7
  17. package/dist/index.js +4 -10
  18. package/dist/policy/core/PolicySource.d.ts +1 -1
  19. package/dist/policy/core/ToolPolicyEngine.d.ts +46 -7
  20. package/dist/policy/core/ToolPolicyEngine.js +73 -16
  21. package/dist/policy/core/compose-decision.d.ts +3 -3
  22. package/dist/policy/core/compose-decision.js +32 -30
  23. package/dist/policy/core/decision-types.d.ts +51 -34
  24. package/dist/policy/core/feature-decision.d.ts +17 -15
  25. package/dist/policy/core/feature-decision.js +11 -13
  26. package/dist/policy/core/pnp-policy-inputs.d.ts +7 -3
  27. package/dist/policy/core/pnp-policy-inputs.js +17 -33
  28. package/dist/policy/core/provenance.d.ts +2 -2
  29. package/dist/policy/core/tool-parameters.d.ts +12 -0
  30. package/dist/policy/core/tool-parameters.js +27 -0
  31. package/dist/policy/engine.d.ts +1 -1
  32. package/dist/policy/sources/PnpPolicySource.d.ts +18 -24
  33. package/dist/policy/sources/PnpPolicySource.js +57 -29
  34. package/dist/runtime/SectionControllerBinding.d.ts +6 -2
  35. package/dist/runtime/SectionControllerBinding.js +21 -15
  36. package/dist/runtime/adapter/subscriber-fanout.d.ts +0 -1
  37. package/dist/runtime/adapter/subscriber-fanout.js +1 -4
  38. package/dist/runtime/composition-emit-scheduler.d.ts +3 -3
  39. package/dist/runtime/composition-emit-scheduler.js +1 -1
  40. package/dist/runtime/content-region.d.ts +0 -4
  41. package/dist/runtime/content-region.js +2 -2
  42. package/dist/runtime/core/SectionEngineCore.d.ts +0 -2
  43. package/dist/runtime/core/SectionEngineCore.js +0 -4
  44. package/dist/runtime/core/cohort.d.ts +0 -5
  45. package/dist/runtime/core/cohort.js +0 -9
  46. package/dist/runtime/core/engine-output.d.ts +3 -2
  47. package/dist/runtime/core/engine-transition.js +3 -2
  48. package/dist/runtime/registration-events.d.ts +11 -14
  49. package/dist/runtime/runtime-id.d.ts +1 -1
  50. package/dist/runtime/runtime-id.js +1 -1
  51. package/dist/runtime/shell-scope.d.ts +4 -4
  52. package/dist/runtime/shell-scope.js +15 -9
  53. package/dist/runtime/tool-host-contract.d.ts +0 -4
  54. package/dist/runtime/tool-host-contract.js +1 -1
  55. package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
  56. package/dist/services/AccessibilityCatalogResolver.js +29 -42
  57. package/dist/services/ElementToolStateStore.d.ts +32 -61
  58. package/dist/services/ElementToolStateStore.js +37 -67
  59. package/dist/services/HighlightCoordinator.d.ts +9 -21
  60. package/dist/services/HighlightCoordinator.js +52 -80
  61. package/dist/services/RangeSerializer.d.ts +4 -35
  62. package/dist/services/RangeSerializer.js +4 -48
  63. package/dist/services/SSMLExtractor.d.ts +8 -31
  64. package/dist/services/SSMLExtractor.js +18 -40
  65. package/dist/services/TTSService.d.ts +51 -57
  66. package/dist/services/TTSService.js +323 -284
  67. package/dist/services/ToolCoordinator.d.ts +6 -49
  68. package/dist/services/ToolCoordinator.js +25 -61
  69. package/dist/services/ToolRegistry.d.ts +83 -80
  70. package/dist/services/ToolRegistry.js +91 -67
  71. package/dist/services/ToolkitCoordinator.d.ts +83 -81
  72. package/dist/services/ToolkitCoordinator.js +186 -174
  73. package/dist/services/framework-error.d.ts +30 -7
  74. package/dist/services/framework-error.js +10 -13
  75. package/dist/services/interfaces.d.ts +51 -71
  76. package/dist/services/section-controller-types.d.ts +7 -7
  77. package/dist/services/section-session-assignment.js +6 -8
  78. package/dist/services/spoken-audio-cards.d.ts +2 -4
  79. package/dist/services/spoken-audio-cards.js +2 -4
  80. package/dist/services/tool-config-validation.d.ts +5 -7
  81. package/dist/services/tool-config-validation.js +11 -26
  82. package/dist/services/tool-context.d.ts +39 -24
  83. package/dist/services/tool-context.js +158 -92
  84. package/dist/services/tool-failure.d.ts +30 -0
  85. package/dist/services/tool-failure.js +11 -0
  86. package/dist/services/tool-icons.d.ts +1 -2
  87. package/dist/services/tool-icons.js +2 -2
  88. package/dist/services/tool-instance-id.d.ts +8 -6
  89. package/dist/services/tool-instance-id.js +12 -18
  90. package/dist/services/tool-providers/TTSToolProvider.d.ts +6 -43
  91. package/dist/services/tool-providers/TTSToolProvider.js +7 -63
  92. package/dist/services/tool-providers/ToolProviderApi.d.ts +0 -7
  93. package/dist/services/tool-providers/ToolProviderRegistry.d.ts +7 -42
  94. package/dist/services/tool-providers/ToolProviderRegistry.js +21 -63
  95. package/dist/services/tool-request.d.ts +4 -0
  96. package/dist/services/tool-request.js +7 -2
  97. package/dist/services/toolbar-decided-tools.d.ts +2 -0
  98. package/dist/services/toolbar-decided-tools.js +2 -0
  99. package/dist/services/toolbar-items.js +2 -5
  100. package/dist/services/tools-config-normalizer.d.ts +18 -9
  101. package/dist/services/tools-config-normalizer.js +25 -19
  102. package/dist/services/tts/browser-provider.d.ts +2 -5
  103. package/dist/services/tts/browser-provider.js +13 -22
  104. package/dist/services/tts/catalog-span-alignment.js +14 -7
  105. package/dist/services/tts/flat-tree.d.ts +0 -7
  106. package/dist/services/tts/flat-tree.js +2 -2
  107. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +2 -2
  108. package/dist/services/tts/generated-speech/index.d.ts +1 -1
  109. package/dist/services/tts/generated-speech/index.js +1 -1
  110. package/dist/services/tts/generated-speech/types.d.ts +1 -1
  111. package/dist/services/tts/generated-speech/types.js +1 -1
  112. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +10 -12
  113. package/dist/services/tts/highlight-target-resolver.d.ts +12 -0
  114. package/dist/services/tts/math-aware-text-processing.js +10 -29
  115. package/dist/services/tts/math-speech.d.ts +1 -1
  116. package/dist/services/tts/math-speech.js +3 -0
  117. package/dist/services/tts/mathml-sanitization.d.ts +7 -0
  118. package/dist/services/tts/mathml-sanitization.js +9 -1
  119. package/dist/services/tts/provider-options.d.ts +12 -1
  120. package/dist/services/tts/ssml/spoken-text.js +1 -1
  121. package/dist/services/tts/start-failure.d.ts +0 -7
  122. package/dist/services/tts/start-failure.js +1 -1
  123. package/dist/services/tts/text-segmentation.d.ts +5 -0
  124. package/dist/services/tts/text-segmentation.js +13 -10
  125. package/dist/services/tts-runtime-config.d.ts +32 -12
  126. package/dist/services/tts-runtime-config.js +14 -20
  127. package/dist/tools/content-capability-resolution.d.ts +2 -2
  128. package/dist/tools/registration.d.ts +7 -6
  129. package/dist/tools/registration.js +10 -9
  130. package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
  131. package/dist/tools/registrations/toolbar-registration-helpers.js +6 -0
  132. package/dist/tools/tool-surface-host.js +1 -8
  133. package/package.json +8 -6
  134. package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +0 -2
  135. package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +0 -40
  136. package/dist/components/chunks/sre-engine-jd8cc08x.js +0 -1
  137. package/dist/services/ContextVariableStore.d.ts +0 -113
  138. package/dist/services/ContextVariableStore.js +0 -206
  139. package/dist/services/tts-control-events.d.ts +0 -13
  140. package/dist/services/tts-control-events.js +0 -7
  141. package/dist/utils/logger.d.ts +0 -6
  142. package/dist/utils/logger.js +0 -10
@@ -50,8 +50,10 @@ export interface ToolPolicyDecisionRequest {
50
50
  /**
51
51
  * Diagnostic codes the engine may emit alongside a decision.
52
52
  * `tool-policy.requiredToolBlocked` fires when a host policy gate
53
- * (`policy.blocked`, missing-from-`placement`, provider veto) removed a tool
54
- * that item or district `requiredTools` mandates.
53
+ * (`policy.blocked`, the allowlist, a provider veto, or placement at no level)
54
+ * removed a tool that item or district `requiredTools` mandates. A tool placed
55
+ * at another level is served there, so its absence from this level's placement
56
+ * is no conflict. `details` is {@link RequiredToolBlockedDetails}.
55
57
  *
56
58
  * `tool-policy.placementMissing` fires when a custom `PolicySource`
57
59
  * references a tool ID that is not present in `tools.placement[level]`
@@ -62,7 +64,9 @@ export interface ToolPolicyDecisionRequest {
62
64
  *
63
65
  * `tool-policy.unknownSupportId` fires for each id a profile, district
64
66
  * policy, test administration or item names that no tool is registered under.
65
- * `details` is {@link UnknownSupportIdDetails}.
67
+ * It describes the inputs, so the engine reports it on its resolved inputs'
68
+ * `diagnostics` rather than on a decision. `details` is
69
+ * {@link UnknownSupportIdDetails}.
66
70
  *
67
71
  * `tool-policy.itemSettingNotApplied` fires on a section-, assessment- or
68
72
  * passage-level decision for each tool on that toolbar a mounted item's
@@ -75,9 +79,10 @@ export interface ToolPolicyDecisionRequest {
75
79
  * `restrictedTools` or the profile's `prohibitedSupports` withdraws instead.
76
80
  * `details` is {@link OverrideBlockedDetails}.
77
81
  *
78
- * The toolkit coordinator logs each diagnostic once per code, tool and item.
82
+ * The toolkit coordinator logs each diagnostic once per code, tool and item,
83
+ * and hands the same ones to its `onPolicyDiagnostic` listeners.
79
84
  */
80
- export type ToolPolicyDiagnosticCode = "tool-policy.requiredToolBlocked" | "tool-policy.placementMissing" | "tool-policy.unknownSupportId" | "tool-policy.itemSettingNotApplied" | "tool-policy.overrideBlocked";
85
+ export type ToolPolicyDiagnosticCode = keyof ToolPolicyDiagnosticDetails;
81
86
  /**
82
87
  * Which host gate removed a profile-mandated tool. Surfaced inside
83
88
  * `ToolPolicyDiagnostic.details` for `tool-policy.requiredToolBlocked`
@@ -87,44 +92,38 @@ export type ToolPolicyDiagnosticCode = "tool-policy.requiredToolBlocked" | "tool
87
92
  * `provenance.features[toolId].allDecisions`.
88
93
  *
89
94
  * The four values mirror steps 1–4 of `composeDecision`:
90
- * - `placement-missing` — tool is not in `tools.placement[level]`
95
+ * - `placement-missing` — no level of `tools.placement` lists the tool
91
96
  * - `provider-disabled` — `tools.providers[id].enabled === false`
92
97
  * - `host-allowlist` — non-empty `tools.policy.allowed` excludes the id
93
98
  * - `host-blocked` — `tools.policy.blocked` lists the id
94
99
  */
95
100
  export type ToolPolicyHostGate = "placement-missing" | "provider-disabled" | "host-allowlist" | "host-blocked";
96
- /**
97
- * Strongly-typed payload for `tool-policy.requiredToolBlocked`
98
- * diagnostics. The diagnostic's `details` field still types as
99
- * `Record<string, unknown>` for forward compatibility, but engines
100
- * always populate this exact shape today and consumers can safely
101
- * cast.
102
- */
103
- export interface RequiredToolBlockedDetails extends Record<string, unknown> {
104
- /** The profile policy rule that mandated the tool (e.g. `district-requirement`). */
105
- rule: string;
101
+ /** Payload of a `tool-policy.requiredToolBlocked` diagnostic. */
102
+ export interface RequiredToolBlockedDetails {
103
+ /** The requirement that mandates the tool. */
104
+ rule: "item-requirement" | "district-requirement";
106
105
  /** Which host gate removed the tool. */
107
106
  hostRule: ToolPolicyHostGate;
108
107
  /** The host configuration value that triggered the gate (best-effort, may be omitted). */
109
108
  hostValue?: unknown;
110
109
  }
111
110
  /** Payload of a `tool-policy.unknownSupportId` diagnostic. */
112
- export interface UnknownSupportIdDetails extends Record<string, unknown> {
111
+ export interface UnknownSupportIdDetails {
113
112
  /** The rules whose lists name the id, in precedence order. */
114
113
  origins: PnpPolicySourceRule[];
115
114
  }
116
115
  /** Payload of a `tool-policy.placementMissing` diagnostic. */
117
- export interface PlacementMissingDetails extends Record<string, unknown> {
116
+ export interface PlacementMissingDetails {
118
117
  /** The custom `PolicySource` that named the tool. */
119
118
  customSourceId: string;
120
119
  }
121
120
  /** Payload of a `tool-policy.overrideBlocked` diagnostic. */
122
- export interface OverrideBlockedDetails extends Record<string, unknown> {
121
+ export interface OverrideBlockedDetails {
123
122
  /** The rule that withdrew the granted tool. */
124
123
  rule: "item-restriction" | "pnp-prohibited";
125
124
  }
126
125
  /** Payload of a `tool-policy.itemSettingNotApplied` diagnostic. */
127
- export interface ItemSettingNotAppliedDetails extends Record<string, unknown> {
126
+ export interface ItemSettingNotAppliedDetails {
128
127
  /** Canonical id of the item whose setting names the tool. */
129
128
  itemId: string;
130
129
  /** The item settings that name it. */
@@ -132,14 +131,28 @@ export interface ItemSettingNotAppliedDetails extends Record<string, unknown> {
132
131
  /** The level of the toolbar the tool is on. */
133
132
  toolbarLevel: ToolLevel;
134
133
  }
135
- export interface ToolPolicyDiagnostic {
136
- code: ToolPolicyDiagnosticCode;
137
- /** The toolbar level decided; absent on a feature decision. */
138
- level?: ToolPlacementLevel;
139
- toolId: string;
140
- message: string;
141
- details?: Record<string, unknown>;
134
+ /** The payload each diagnostic code carries. */
135
+ export interface ToolPolicyDiagnosticDetails {
136
+ "tool-policy.requiredToolBlocked": RequiredToolBlockedDetails;
137
+ "tool-policy.placementMissing": PlacementMissingDetails;
138
+ "tool-policy.unknownSupportId": UnknownSupportIdDetails;
139
+ "tool-policy.itemSettingNotApplied": ItemSettingNotAppliedDetails;
140
+ "tool-policy.overrideBlocked": OverrideBlockedDetails;
142
141
  }
142
+ /** A policy diagnostic, its `details` typed by its `code`. */
143
+ export type ToolPolicyDiagnostic = {
144
+ [C in ToolPolicyDiagnosticCode]: {
145
+ code: C;
146
+ /**
147
+ * The toolbar level decided; absent on a feature decision and on an input
148
+ * diagnostic.
149
+ */
150
+ level?: ToolPlacementLevel;
151
+ toolId: string;
152
+ message: string;
153
+ details: ToolPolicyDiagnosticDetails[C];
154
+ };
155
+ }[ToolPolicyDiagnosticCode];
143
156
  export interface ToolPolicyEntry {
144
157
  toolId: string;
145
158
  /**
@@ -150,20 +163,24 @@ export interface ToolPolicyEntry {
150
163
  */
151
164
  required: boolean;
152
165
  /**
153
- * `true` for a tool the student's PNP `supports` or a
154
- * `testAdministration.toolOverrides` entry grants — UI-level signal that
166
+ * `true` for a tool the student's PNP `supports` or `activateAtInit`, or a
167
+ * `testAdministration.toolOverrides` entry, grants — UI-level signal that
155
168
  * the host cannot toggle this tool off in user preferences. Does NOT
156
169
  * override host blocks (they would have removed the entry before this
157
170
  * flag is read).
158
171
  */
159
172
  alwaysAvailable: boolean;
160
173
  /**
161
- * Tool-specific settings resolved from the assessment / item
162
- * settings via the PNP policy source. Hosts that want to read these
163
- * directly should use `entry.settings` rather than peeking into
164
- * the assessment entity from above the engine.
174
+ * `true` for a tool the student's PNP `activateAtInit` lists that policy
175
+ * admits: the toolbar turns it on when it first shows it.
176
+ */
177
+ activateAtInit: boolean;
178
+ /**
179
+ * The tool's parameters: the profile's, assessment's and, on the item's own
180
+ * toolbar, item's `toolParameters` entries merged key by key, later ones
181
+ * winning. Set whether or not a grant admitted the tool.
165
182
  */
166
- settings?: unknown;
183
+ parameters?: Record<string, unknown>;
167
184
  }
168
185
  export interface ToolPolicyDecision {
169
186
  visibleTools: ToolPolicyEntry[];
@@ -27,10 +27,10 @@ import type { PnpPolicySourceRule, ToolPolicyResolutionDecision, ToolPolicySourc
27
27
  * A feature verdict comes from one of the eight PNP precedence levels, or from a
28
28
  * host gate that never reaches them: `tools.policy.blocked` and a non-empty
29
29
  * `tools.policy.allowed` are absolute for the id they name, exactly as they are
30
- * on the placement-scoped path.
30
+ * on the placement-scoped path. `"none"` is a feature no rule names.
31
31
  */
32
- export type FeaturePolicyRule = PnpPolicySourceRule | "host-allowlist" | "host-blocked";
33
- export interface FeaturePolicyDecision {
32
+ export type FeaturePolicyRule = PnpPolicySourceRule | "host-allowlist" | "host-blocked" | "none";
33
+ export interface FeaturePolicyDecision<P = Record<string, unknown>> {
34
34
  /** The PNP/AfA support id that was evaluated (e.g. `"signLanguage"`). */
35
35
  featureId: string;
36
36
  /**
@@ -42,7 +42,8 @@ export interface FeaturePolicyDecision {
42
42
  action: ToolPolicyResolutionDecision["action"];
43
43
  /** Which precedence rule produced the verdict. */
44
44
  rule: FeaturePolicyRule;
45
- precedence: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
45
+ /** The rule's precedence level; `null` for `rule: "none"`. */
46
+ precedence: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | null;
46
47
  sourceType: ToolPolicySourceType;
47
48
  /** Human-readable explanation, suitable for a policy debugger. */
48
49
  reason: string;
@@ -66,20 +67,21 @@ export interface FeaturePolicyDecision {
66
67
  */
67
68
  assessmentBound: boolean;
68
69
  /**
69
- * `true` when the grant is a mandate (item or district `requiredTools`)
70
- * rather than a student-profile support or a test-administration override.
70
+ * `true` when item or district `requiredTools` mandates the feature, including
71
+ * under a `true` test-administration override.
71
72
  */
72
73
  required: boolean;
73
74
  /**
74
- * Feature parameters resolved from item `toolParameters` then assessment
75
- * `toolConfigs`, keyed by the feature id. The seam a later configurable
76
- * presentation would hang on; no vocabulary is defined yet.
75
+ * The feature's parameters: the profile's, assessment's and, for an item's
76
+ * scope, item's `toolParameters` entries for the feature id, merged key by key,
77
+ * later ones winning. Set on a denial too; a host denial leaves it out.
77
78
  */
78
- parameters?: unknown;
79
+ parameters?: P;
79
80
  /**
80
- * Conflicts found while deciding: a `tool-policy.unknownSupportId` for each
81
- * id the bound inputs name that no tool is registered under. Empty on a host
82
- * denial, which consults no policy source.
81
+ * Conflicts found while deciding: a `tool-policy.overrideBlocked` for a
82
+ * granting override the decision withdrew. Empty on a host denial, which
83
+ * consults no policy source. An id no tool is registered under describes the
84
+ * inputs, so it is reported on the engine inputs' `diagnostics`.
83
85
  */
84
86
  diagnostics: ToolPolicyDiagnostic[];
85
87
  }
@@ -97,7 +99,7 @@ export interface FeatureDecisionContext {
97
99
  * blocklist is a verdict whether or not an assessment was bound, and the flag
98
100
  * only ever qualifies a *policy* denial.
99
101
  */
100
- export declare function hostFeatureDenial(featureId: string, rule: "host-allowlist" | "host-blocked", hostValue: readonly string[], context: FeatureDecisionContext): FeaturePolicyDecision;
102
+ export declare function hostFeatureDenial(featureId: string, rule: "host-allowlist" | "host-blocked", hostValue: readonly string[], context: FeatureDecisionContext): FeaturePolicyDecision<never>;
101
103
  /**
102
104
  * Whether a host gate produced the verdict, rather than a policy source.
103
105
  *
@@ -114,4 +116,4 @@ export declare function isHostDeniedFeature(decision: Pick<FeaturePolicyDecision
114
116
  * exactly one decision and at most one flags entry — no mapped-tool-id
115
117
  * bookkeeping is needed to read it back out.
116
118
  */
117
- export declare function interpretFeatureResult(featureId: string, result: PnpPolicyResult, context: FeatureDecisionContext): FeaturePolicyDecision;
119
+ export declare function interpretFeatureResult<P>(featureId: string, result: PnpPolicyResult, context: FeatureDecisionContext, parameters: P | undefined): FeaturePolicyDecision<P>;
@@ -20,14 +20,12 @@
20
20
  * "nothing was bound to ask against" are the same verdict for different
21
21
  * reasons, and only the second is a defect.
22
22
  */
23
- import { overrideBlockedDiagnostic, unknownSupportIdDiagnostic, } from "./compose-decision.js";
23
+ import { overrideBlockedDiagnostic } from "./compose-decision.js";
24
24
  /**
25
25
  * Reason text for a denial that had no assessment to decide against.
26
26
  *
27
- * Used in place of the `pnp-support` skip's "not configured at any level",
28
- * which is true but reads as a completed evaluation. `rule` and `precedence`
29
- * stay as the source reported them: nothing fired, so naming a ninth rule
30
- * would describe a precedence level that does not exist.
27
+ * Used in place of "not configured", which is true but reads as a completed
28
+ * evaluation.
31
29
  */
32
30
  const unboundAssessmentReason = (featureId) => `No assessment is bound, so no policy source could grant "${featureId}"`;
33
31
  /**
@@ -73,17 +71,20 @@ export function isHostDeniedFeature(decision) {
73
71
  * exactly one decision and at most one flags entry — no mapped-tool-id
74
72
  * bookkeeping is needed to read it back out.
75
73
  */
76
- export function interpretFeatureResult(featureId, result, context) {
74
+ export function interpretFeatureResult(featureId, result, context, parameters) {
77
75
  const decision = result.decisions[0];
78
76
  const flags = Array.from(result.perToolFlags.values())[0];
79
77
  const granted = decision?.action === "enable";
80
78
  const reason = decision?.reason ?? `Feature "${featureId}" not configured`;
79
+ // The source logs "no rule fired" as a skip at the last level so the trail
80
+ // stays ordered; the verdict names no rule for it.
81
+ const fired = decision !== undefined && decision.action !== "skip";
81
82
  return {
82
83
  featureId,
83
84
  granted,
84
85
  action: decision?.action ?? "skip",
85
- rule: decision?.rule ?? "pnp-support",
86
- precedence: decision?.precedence ?? 8,
86
+ rule: fired ? decision.rule : "none",
87
+ precedence: fired ? decision.precedence : null,
87
88
  sourceType: decision?.sourceType ?? "system",
88
89
  // Only a denial is re-worded. An unbound host can still be granted the
89
90
  // feature — an item's registered settings carrying `requiredTools` mandate
@@ -93,11 +94,8 @@ export function interpretFeatureResult(featureId, result, context) {
93
94
  ? reason
94
95
  : unboundAssessmentReason(featureId),
95
96
  required: Boolean(flags?.required),
96
- parameters: flags?.settings,
97
+ parameters,
97
98
  assessmentBound: context.assessmentBound,
98
- diagnostics: [
99
- ...Array.from(result.unmappedSupportIds, ([supportId, origins]) => unknownSupportIdDiagnostic(supportId, origins)),
100
- ...Array.from(result.blockedOverrides, ([toolId, rule]) => overrideBlockedDiagnostic(toolId, rule)),
101
- ],
99
+ diagnostics: Array.from(result.blockedOverrides, ([toolId, rule]) => overrideBlockedDiagnostic(toolId, rule)),
102
100
  };
103
101
  }
@@ -11,11 +11,15 @@
11
11
  * record (only `id` / `name`, no PNP and no settings) do not engage
12
12
  * PNP/profile gates. The flip happens the moment the assessment carries any of:
13
13
  *
14
- * - `personalNeedsProfile` (any `supports` or `prohibitedSupports`),
14
+ * - `personalNeedsProfile` (any `supports`, `prohibitedSupports` or
15
+ * `activateAtInit`),
15
16
  * - `settings.districtPolicy` (any `blockedTools` or `requiredTools`),
16
17
  * - `settings.testAdministration` (any `toolOverrides` entry),
17
18
  * - or, for an item-scoped decision, the item's settings carry
18
- * `requiredTools` / `restrictedTools` / `toolParameters`.
19
+ * `requiredTools` / `restrictedTools`.
20
+ *
21
+ * Tool parameters are no policy material: the engine resolves them whatever the
22
+ * enforcement state.
19
23
  *
20
24
  * Hosts opt out of the auto-on behavior by passing
21
25
  * `pnpEnforcement: "off"` explicitly (engine input or
@@ -32,7 +36,7 @@ import type { AssessmentEntity, ItemSettings } from "@pie-players/pie-players-sh
32
36
  export declare function assessmentHasPnpPolicyInputs(assessment: AssessmentEntity | null | undefined): boolean;
33
37
  /**
34
38
  * Return `true` when an item's settings carry item-level profile policy
35
- * material (`requiredTools`, `restrictedTools`, or `toolParameters`).
39
+ * material (`requiredTools` or `restrictedTools`).
36
40
  *
37
41
  * Used in addition to {@link assessmentHasPnpPolicyInputs} for a decision scoped
38
42
  * to the item, so an item with profile-relevant settings engages PNP/profile
@@ -11,16 +11,21 @@
11
11
  * record (only `id` / `name`, no PNP and no settings) do not engage
12
12
  * PNP/profile gates. The flip happens the moment the assessment carries any of:
13
13
  *
14
- * - `personalNeedsProfile` (any `supports` or `prohibitedSupports`),
14
+ * - `personalNeedsProfile` (any `supports`, `prohibitedSupports` or
15
+ * `activateAtInit`),
15
16
  * - `settings.districtPolicy` (any `blockedTools` or `requiredTools`),
16
17
  * - `settings.testAdministration` (any `toolOverrides` entry),
17
18
  * - or, for an item-scoped decision, the item's settings carry
18
- * `requiredTools` / `restrictedTools` / `toolParameters`.
19
+ * `requiredTools` / `restrictedTools`.
20
+ *
21
+ * Tool parameters are no policy material: the engine resolves them whatever the
22
+ * enforcement state.
19
23
  *
20
24
  * Hosts opt out of the auto-on behavior by passing
21
25
  * `pnpEnforcement: "off"` explicitly (engine input or
22
26
  * `ToolkitCoordinator.setPnpEnforcement("off")`).
23
27
  */
28
+ const hasEntries = (value) => Array.isArray(value) && value.length > 0;
24
29
  /**
25
30
  * Return `true` when the assessment carries any PNP/profile policy
26
31
  * material that the engine's `PnpPolicySource` would consume.
@@ -32,26 +37,18 @@ export function assessmentHasPnpPolicyInputs(assessment) {
32
37
  if (!assessment)
33
38
  return false;
34
39
  const pnp = assessment.personalNeedsProfile;
35
- if (pnp) {
36
- if (Array.isArray(pnp.supports) && pnp.supports.length > 0)
37
- return true;
38
- if (Array.isArray(pnp.prohibitedSupports) &&
39
- pnp.prohibitedSupports.length > 0) {
40
- return true;
41
- }
40
+ if (pnp &&
41
+ (hasEntries(pnp.supports) ||
42
+ hasEntries(pnp.prohibitedSupports) ||
43
+ hasEntries(pnp.activateAtInit))) {
44
+ return true;
42
45
  }
43
46
  const settings = assessment.settings;
44
47
  if (settings) {
45
48
  const district = settings.districtPolicy;
46
- if (district) {
47
- if (Array.isArray(district.blockedTools) &&
48
- district.blockedTools.length > 0) {
49
- return true;
50
- }
51
- if (Array.isArray(district.requiredTools) &&
52
- district.requiredTools.length > 0) {
53
- return true;
54
- }
49
+ if (district &&
50
+ (hasEntries(district.blockedTools) || hasEntries(district.requiredTools))) {
51
+ return true;
55
52
  }
56
53
  const overrides = settings.testAdministration?.toolOverrides;
57
54
  if (overrides &&
@@ -64,7 +61,7 @@ export function assessmentHasPnpPolicyInputs(assessment) {
64
61
  }
65
62
  /**
66
63
  * Return `true` when an item's settings carry item-level profile policy
67
- * material (`requiredTools`, `restrictedTools`, or `toolParameters`).
64
+ * material (`requiredTools` or `restrictedTools`).
68
65
  *
69
66
  * Used in addition to {@link assessmentHasPnpPolicyInputs} for a decision scoped
70
67
  * to the item, so an item with profile-relevant settings engages PNP/profile
@@ -74,20 +71,7 @@ export function assessmentHasPnpPolicyInputs(assessment) {
74
71
  export function itemSettingsHavePnpPolicyInputs(settings) {
75
72
  if (!settings)
76
73
  return false;
77
- if (Array.isArray(settings.requiredTools) &&
78
- settings.requiredTools.length > 0) {
79
- return true;
80
- }
81
- if (Array.isArray(settings.restrictedTools) &&
82
- settings.restrictedTools.length > 0) {
83
- return true;
84
- }
85
- if (settings.toolParameters &&
86
- typeof settings.toolParameters === "object" &&
87
- Object.keys(settings.toolParameters).length > 0) {
88
- return true;
89
- }
90
- return false;
74
+ return hasEntries(settings.requiredTools) || hasEntries(settings.restrictedTools);
91
75
  }
92
76
  /**
93
77
  * Resolve the auto-mode `pnpEnforcement` for one decision.
@@ -8,7 +8,7 @@
8
8
  * provider veto, PNP/profile gates, and custom policy sources.
9
9
  */
10
10
  /** The PNP/profile precedence rules, highest first. */
11
- export type PnpPolicySourceRule = "district-block" | "test-admin-override" | "item-restriction" | "pnp-prohibited" | "item-requirement" | "district-requirement" | "pnp-support";
11
+ export type PnpPolicySourceRule = "district-block" | "test-admin-override" | "item-restriction" | "pnp-prohibited" | "item-requirement" | "district-requirement" | "pnp-support" | "pnp-activate-at-init";
12
12
  /**
13
13
  * Rule vocabulary for tool policy decisions. A decision's `rule` is the one
14
14
  * attribution channel: it names the step that enabled, blocked or advised on a
@@ -20,7 +20,7 @@ export interface ToolPolicyResolutionDecision {
20
20
  /** Monotonic step index across the entire decision log. */
21
21
  step: number;
22
22
  /**
23
- * Precedence level. PNP/profile rules use `1`–`6`; non-profile rules use `0`
23
+ * Precedence level. PNP/profile rules use `1`–`8`; non-profile rules use `0`
24
24
  * for host-side gates, and a custom source, which runs after PNP/profile
25
25
  * policy, uses its own or `100 + i` for the `i`th registered source.
26
26
  */
@@ -0,0 +1,12 @@
1
+ import type { AssessmentEntity, ToolParametersFor } from "@pie-players/pie-players-shared/types";
2
+ import type { PnpPolicyItem } from "../sources/PnpPolicySource.js";
3
+ /**
4
+ * A tool's parameters for one decision, merged key by key: the profile's
5
+ * `toolParameters` entry, overridden by the assessment's, overridden by the
6
+ * item's.
7
+ *
8
+ * Parameters say how a tool behaves wherever policy shows it, so they reach a
9
+ * tool placed without a grant, and PNP enforcement does not gate them. `item` is
10
+ * set only for a decision about the item's own toolbar or content.
11
+ */
12
+ export declare function resolveToolParameters<K extends string>(toolId: K, assessment: AssessmentEntity | null | undefined, item: PnpPolicyItem | undefined): ToolParametersFor<K> | undefined;
@@ -0,0 +1,27 @@
1
+ function asParameters(value) {
2
+ return value && typeof value === "object" && !Array.isArray(value)
3
+ ? value
4
+ : undefined;
5
+ }
6
+ /**
7
+ * A tool's parameters for one decision, merged key by key: the profile's
8
+ * `toolParameters` entry, overridden by the assessment's, overridden by the
9
+ * item's.
10
+ *
11
+ * Parameters say how a tool behaves wherever policy shows it, so they reach a
12
+ * tool placed without a grant, and PNP enforcement does not gate them. `item` is
13
+ * set only for a decision about the item's own toolbar or content.
14
+ */
15
+ export function resolveToolParameters(toolId, assessment, item) {
16
+ const settings = assessment?.settings;
17
+ const layers = [
18
+ assessment?.personalNeedsProfile?.toolParameters?.[toolId],
19
+ settings?.toolParameters?.[toolId],
20
+ item?.settings.toolParameters?.[toolId],
21
+ ]
22
+ .map(asParameters)
23
+ .filter((layer) => layer !== undefined);
24
+ return layers.length > 0
25
+ ? Object.assign({}, ...layers)
26
+ : undefined;
27
+ }
@@ -7,6 +7,6 @@
7
7
  */
8
8
  export { ToolPolicyEngine, type PnpEnforcementMode, type ResolvedEngineInputs, type ToolPolicyChangeEvent, type ToolPolicyChangeListener, type ToolPolicyEngineArgs, type ToolPolicyEngineInputs, } from "./core/ToolPolicyEngine.js";
9
9
  export { isHostDeniedFeature, type FeaturePolicyDecision, type FeaturePolicyRule, } from "./core/feature-decision.js";
10
- export type { ItemSettingNotAppliedDetails, OverrideBlockedDetails, PlacementMissingDetails, RequiredToolBlockedDetails, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolPolicyDiagnostic, ToolPolicyDiagnosticCode, ToolPolicyEntry, ToolPolicyHostGate, ToolScope, UnknownSupportIdDetails, } from "./core/decision-types.js";
10
+ export type { ItemSettingNotAppliedDetails, OverrideBlockedDetails, PlacementMissingDetails, RequiredToolBlockedDetails, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolPolicyDiagnostic, ToolPolicyDiagnosticCode, ToolPolicyDiagnosticDetails, ToolPolicyEntry, ToolPolicyHostGate, ToolScope, UnknownSupportIdDetails, } from "./core/decision-types.js";
11
11
  export type { PolicySource, PolicySourceDecisionContext, PolicySourceProvenanceEntry, PolicySourceResult, } from "./core/PolicySource.js";
12
12
  export type { PnpPolicySourceRule, ToolPolicyDecisionRule, ToolPolicyFeatureTrail, ToolPolicyProvenance, ToolPolicyResolutionDecision, ToolPolicySourceType, } from "./core/provenance.js";
@@ -16,12 +16,12 @@ export interface PnpPolicyToolFlags {
16
16
  /** PNP/profile policy mandates this tool (item or district `requiredTools`). */
17
17
  required: boolean;
18
18
  /**
19
- * The student's profile supports this tool, or a test-administration
20
- * override grants it (host UI cannot toggle off).
19
+ * The student's profile supports this tool or activates it at start, or a
20
+ * test-administration override grants it (host UI cannot toggle off).
21
21
  */
22
22
  alwaysAvailable: boolean;
23
- /** Tool-specific settings derived from item / assessment settings. */
24
- settings?: unknown;
23
+ /** The student's profile lists this tool in `activateAtInit`. */
24
+ activateAtInit: boolean;
25
25
  /** Which PNP/profile rule contributed the surviving verdict. */
26
26
  rule: PnpPolicySourceRule;
27
27
  }
@@ -60,11 +60,12 @@ export interface PnpPolicyResult {
60
60
  */
61
61
  blockedToolIds: Set<string>;
62
62
  /**
63
- * Tool IDs PNP/profile policy mandates (item or district `requiredTools`). Used
64
- * by the engine to detect `tool-policy.requiredToolBlocked`
63
+ * Tools PNP/profile policy mandates (item or district `requiredTools`), each
64
+ * with the requirement that names it, including a tool a `true` override
65
+ * decides. Used by the engine to detect `tool-policy.requiredToolBlocked`
65
66
  * diagnostics for tools removed by host policy.
66
67
  */
67
- mandatedToolIds: Set<string>;
68
+ mandates: Map<string, "item-requirement" | "district-requirement">;
68
69
  /**
69
70
  * Per-tool flags merged into surviving `ToolPolicyEntry`s.
70
71
  */
@@ -75,15 +76,6 @@ export interface PnpPolicyResult {
75
76
  * support id).
76
77
  */
77
78
  decisions: PnpPolicyDecisionEvent[];
78
- /**
79
- * Ids named by a profile, district policy, test administration or item that
80
- * no tool is registered under, each with the rules whose lists name it, in
81
- * precedence order. Nothing resolves them; the engine turns each into a
82
- * `tool-policy.unknownSupportId` diagnostic. Empty when the registry is: an
83
- * empty registry has nothing to check an id against, and
84
- * `tool-config-validation` already reports it once.
85
- */
86
- unmappedSupportIds: Map<string, PnpPolicySourceRule[]>;
87
79
  /**
88
80
  * Ids a `true` test-administration override names that an item restriction
89
81
  * or a PNP prohibition withdrew instead, each with the rule that did. The
@@ -125,8 +117,8 @@ export declare class PnpPolicySource {
125
117
  * feature: the returned result carries exactly one decision, and
126
118
  * `decisions[0].action` is the verdict.
127
119
  *
128
- * Reusing `resolveSupport(...)` rather than re-walking the precedence rules
129
- * is the point — a second copy of the eight levels would drift.
120
+ * It reuses `resolveSupport(...)` because a second copy of the eight levels
121
+ * would drift.
130
122
  */
131
123
  resolveFeature(featureId: string, args: PnpPolicyApplyArgs): PnpPolicyResult;
132
124
  /**
@@ -136,11 +128,13 @@ export declare class PnpPolicySource {
136
128
  private prepare;
137
129
  private resolveSupport;
138
130
  /**
139
- * The named ids no tool is registered under, with the rules naming each in
140
- * precedence order. A support id is the id of the tool it grants, so an id
141
- * missing from the registry matches nothing placed.
131
+ * Ids the assessment or any of `items` names that no tool is registered
132
+ * under, each with the rules whose lists name it, in precedence order. A
133
+ * support id is the id of the tool it grants, so an id missing from the
134
+ * registry matches nothing placed; the engine turns each into a
135
+ * `tool-policy.unknownSupportId` diagnostic. Empty when the registry is: an
136
+ * empty registry has nothing to check an id against, and
137
+ * `tool-config-validation` already reports it once.
142
138
  */
143
- private unmappedIds;
144
- /** Tool parameters: the item's entry, else the assessment's. */
145
- private resolveToolSettings;
139
+ unknownSupportIds(assessment: AssessmentEntity | null | undefined, items: readonly ItemSettings[]): Map<string, PnpPolicySourceRule[]>;
146
140
  }