@pie-players/pie-assessment-toolkit 0.3.30 → 0.3.32

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 (179) hide show
  1. package/README.md +289 -5
  2. package/dist/components/ItemToolBar.custom-element.js +165 -92
  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 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/policy/core/PolicySource.d.ts +64 -0
  9. package/dist/policy/core/PolicySource.d.ts.map +1 -0
  10. package/dist/policy/core/PolicySource.js +17 -0
  11. package/dist/policy/core/PolicySource.js.map +1 -0
  12. package/dist/policy/core/ToolPolicyEngine.d.ts +123 -0
  13. package/dist/policy/core/ToolPolicyEngine.d.ts.map +1 -0
  14. package/dist/policy/core/ToolPolicyEngine.js +217 -0
  15. package/dist/policy/core/ToolPolicyEngine.js.map +1 -0
  16. package/dist/policy/core/compose-decision.d.ts +37 -0
  17. package/dist/policy/core/compose-decision.d.ts.map +1 -0
  18. package/dist/policy/core/compose-decision.js +332 -0
  19. package/dist/policy/core/compose-decision.js.map +1 -0
  20. package/dist/policy/core/decision-types.d.ts +142 -0
  21. package/dist/policy/core/decision-types.d.ts.map +1 -0
  22. package/dist/policy/core/decision-types.js +13 -0
  23. package/dist/policy/core/decision-types.js.map +1 -0
  24. package/dist/policy/core/engine-context.d.ts +30 -0
  25. package/dist/policy/core/engine-context.d.ts.map +1 -0
  26. package/dist/policy/core/engine-context.js +28 -0
  27. package/dist/policy/core/engine-context.js.map +1 -0
  28. package/dist/policy/core/policy-source-tag.d.ts +32 -0
  29. package/dist/policy/core/policy-source-tag.d.ts.map +1 -0
  30. package/dist/policy/core/policy-source-tag.js +29 -0
  31. package/dist/policy/core/policy-source-tag.js.map +1 -0
  32. package/dist/policy/core/provenance.d.ts +163 -0
  33. package/dist/policy/core/provenance.d.ts.map +1 -0
  34. package/dist/policy/core/provenance.js +232 -0
  35. package/dist/policy/core/provenance.js.map +1 -0
  36. package/dist/policy/core/qti-inputs.d.ts +67 -0
  37. package/dist/policy/core/qti-inputs.d.ts.map +1 -0
  38. package/dist/policy/core/qti-inputs.js +139 -0
  39. package/dist/policy/core/qti-inputs.js.map +1 -0
  40. package/dist/policy/engine.d.ts +28 -0
  41. package/dist/policy/engine.d.ts.map +1 -0
  42. package/dist/policy/engine.js +24 -0
  43. package/dist/policy/engine.js.map +1 -0
  44. package/dist/policy/internal.d.ts +26 -0
  45. package/dist/policy/internal.d.ts.map +1 -0
  46. package/dist/policy/internal.js +26 -0
  47. package/dist/policy/internal.js.map +1 -0
  48. package/dist/policy/sources/QtiPolicySource.d.ts +146 -0
  49. package/dist/policy/sources/QtiPolicySource.d.ts.map +1 -0
  50. package/dist/policy/sources/QtiPolicySource.js +270 -0
  51. package/dist/policy/sources/QtiPolicySource.js.map +1 -0
  52. package/dist/runtime/SectionRuntimeEngine.d.ts +145 -0
  53. package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
  54. package/dist/runtime/SectionRuntimeEngine.js +164 -3
  55. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  56. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +144 -0
  57. package/dist/runtime/adapter/SectionEngineAdapter.d.ts.map +1 -0
  58. package/dist/runtime/adapter/SectionEngineAdapter.js +183 -0
  59. package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -0
  60. package/dist/runtime/adapter/coordinator-bridge.d.ts +93 -0
  61. package/dist/runtime/adapter/coordinator-bridge.d.ts.map +1 -0
  62. package/dist/runtime/adapter/coordinator-bridge.js +127 -0
  63. package/dist/runtime/adapter/coordinator-bridge.js.map +1 -0
  64. package/dist/runtime/adapter/dom-event-bridge.d.ts +63 -0
  65. package/dist/runtime/adapter/dom-event-bridge.d.ts.map +1 -0
  66. package/dist/runtime/adapter/dom-event-bridge.js +95 -0
  67. package/dist/runtime/adapter/dom-event-bridge.js.map +1 -0
  68. package/dist/runtime/adapter/framework-error-bridge.d.ts +37 -0
  69. package/dist/runtime/adapter/framework-error-bridge.d.ts.map +1 -0
  70. package/dist/runtime/adapter/framework-error-bridge.js +47 -0
  71. package/dist/runtime/adapter/framework-error-bridge.js.map +1 -0
  72. package/dist/runtime/adapter/instrumentation-bridge.d.ts +32 -0
  73. package/dist/runtime/adapter/instrumentation-bridge.d.ts.map +1 -0
  74. package/dist/runtime/adapter/instrumentation-bridge.js +39 -0
  75. package/dist/runtime/adapter/instrumentation-bridge.js.map +1 -0
  76. package/dist/runtime/adapter/subscriber-fanout.d.ts +35 -0
  77. package/dist/runtime/adapter/subscriber-fanout.d.ts.map +1 -0
  78. package/dist/runtime/adapter/subscriber-fanout.js +60 -0
  79. package/dist/runtime/adapter/subscriber-fanout.js.map +1 -0
  80. package/dist/runtime/core/SectionEngineCore.d.ts +52 -0
  81. package/dist/runtime/core/SectionEngineCore.d.ts.map +1 -0
  82. package/dist/runtime/core/SectionEngineCore.js +81 -0
  83. package/dist/runtime/core/SectionEngineCore.js.map +1 -0
  84. package/dist/runtime/core/cohort.d.ts +40 -0
  85. package/dist/runtime/core/cohort.d.ts.map +1 -0
  86. package/dist/runtime/core/cohort.js +49 -0
  87. package/dist/runtime/core/cohort.js.map +1 -0
  88. package/dist/runtime/core/engine-input.d.ts +114 -0
  89. package/dist/runtime/core/engine-input.d.ts.map +1 -0
  90. package/dist/runtime/core/engine-input.js +19 -0
  91. package/dist/runtime/core/engine-input.js.map +1 -0
  92. package/dist/runtime/core/engine-output.d.ts +62 -0
  93. package/dist/runtime/core/engine-output.d.ts.map +1 -0
  94. package/dist/runtime/core/engine-output.js +27 -0
  95. package/dist/runtime/core/engine-output.js.map +1 -0
  96. package/dist/runtime/core/engine-readiness.d.ts +49 -0
  97. package/dist/runtime/core/engine-readiness.d.ts.map +1 -0
  98. package/dist/runtime/core/engine-readiness.js +47 -0
  99. package/dist/runtime/core/engine-readiness.js.map +1 -0
  100. package/dist/runtime/core/engine-resolver.d.ts +172 -0
  101. package/dist/runtime/core/engine-resolver.d.ts.map +1 -0
  102. package/dist/runtime/core/engine-resolver.js +170 -0
  103. package/dist/runtime/core/engine-resolver.js.map +1 -0
  104. package/dist/runtime/core/engine-stage-derivation.d.ts +19 -0
  105. package/dist/runtime/core/engine-stage-derivation.d.ts.map +1 -0
  106. package/dist/runtime/core/engine-stage-derivation.js +30 -0
  107. package/dist/runtime/core/engine-stage-derivation.js.map +1 -0
  108. package/dist/runtime/core/engine-state.d.ts +83 -0
  109. package/dist/runtime/core/engine-state.d.ts.map +1 -0
  110. package/dist/runtime/core/engine-state.js +49 -0
  111. package/dist/runtime/core/engine-state.js.map +1 -0
  112. package/dist/runtime/core/engine-transition.d.ts +49 -0
  113. package/dist/runtime/core/engine-transition.d.ts.map +1 -0
  114. package/dist/runtime/core/engine-transition.js +261 -0
  115. package/dist/runtime/core/engine-transition.js.map +1 -0
  116. package/dist/runtime/engine-context.d.ts +40 -0
  117. package/dist/runtime/engine-context.d.ts.map +1 -0
  118. package/dist/runtime/engine-context.js +32 -0
  119. package/dist/runtime/engine-context.js.map +1 -0
  120. package/dist/runtime/engine.d.ts +24 -0
  121. package/dist/runtime/engine.d.ts.map +1 -0
  122. package/dist/runtime/engine.js +24 -0
  123. package/dist/runtime/engine.js.map +1 -0
  124. package/dist/runtime/internal.d.ts +44 -0
  125. package/dist/runtime/internal.d.ts.map +1 -0
  126. package/dist/runtime/internal.js +46 -0
  127. package/dist/runtime/internal.js.map +1 -0
  128. package/dist/runtime/runtime-id.d.ts +8 -0
  129. package/dist/runtime/runtime-id.d.ts.map +1 -0
  130. package/dist/runtime/runtime-id.js +10 -0
  131. package/dist/runtime/runtime-id.js.map +1 -0
  132. package/dist/runtime/section-runtime-engine-host-context.d.ts +74 -0
  133. package/dist/runtime/section-runtime-engine-host-context.d.ts.map +1 -0
  134. package/dist/runtime/section-runtime-engine-host-context.js +96 -0
  135. package/dist/runtime/section-runtime-engine-host-context.js.map +1 -0
  136. package/dist/runtime/stage-emit-gate.d.ts +93 -0
  137. package/dist/runtime/stage-emit-gate.d.ts.map +1 -0
  138. package/dist/runtime/stage-emit-gate.js +83 -0
  139. package/dist/runtime/stage-emit-gate.js.map +1 -0
  140. package/dist/services/ToolkitCoordinator.d.ts +280 -7
  141. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  142. package/dist/services/ToolkitCoordinator.js +412 -32
  143. package/dist/services/ToolkitCoordinator.js.map +1 -1
  144. package/dist/services/framework-error-bus.d.ts +73 -0
  145. package/dist/services/framework-error-bus.d.ts.map +1 -0
  146. package/dist/services/framework-error-bus.js +68 -0
  147. package/dist/services/framework-error-bus.js.map +1 -0
  148. package/dist/services/framework-error.d.ts +36 -1
  149. package/dist/services/framework-error.d.ts.map +1 -1
  150. package/dist/services/framework-error.js +37 -0
  151. package/dist/services/framework-error.js.map +1 -1
  152. package/dist/services/interfaces.d.ts +78 -0
  153. package/dist/services/interfaces.d.ts.map +1 -1
  154. package/dist/services/interfaces.js.map +1 -1
  155. package/dist/services/section-controller-types.d.ts +20 -0
  156. package/dist/services/section-controller-types.d.ts.map +1 -1
  157. package/dist/services/tools-config-normalizer.d.ts +23 -0
  158. package/dist/services/tools-config-normalizer.d.ts.map +1 -1
  159. package/dist/services/tools-config-normalizer.js +13 -1
  160. package/dist/services/tools-config-normalizer.js.map +1 -1
  161. package/dist/tools/client.d.ts +0 -1
  162. package/dist/tools/client.d.ts.map +1 -1
  163. package/dist/tools/client.js +0 -2
  164. package/dist/tools/client.js.map +1 -1
  165. package/dist/tools/types.d.ts +0 -26
  166. package/dist/tools/types.d.ts.map +1 -1
  167. package/package.json +23 -7
  168. package/dist/runtime/runtime-event-guards.d.ts +0 -2
  169. package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
  170. package/dist/runtime/runtime-event-guards.js +0 -4
  171. package/dist/runtime/runtime-event-guards.js.map +0 -1
  172. package/dist/tools/tool-coordinator.d.ts +0 -72
  173. package/dist/tools/tool-coordinator.d.ts.map +0 -1
  174. package/dist/tools/tool-coordinator.js +0 -194
  175. package/dist/tools/tool-coordinator.js.map +0 -1
  176. package/dist/types/events.d.ts +0 -157
  177. package/dist/types/events.d.ts.map +0 -1
  178. package/dist/types/events.js +0 -11
  179. package/dist/types/events.js.map +0 -1
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Tool Policy Provenance — generalization of `PnpResolutionProvenance`
3
+ * (M8 — see `.cursor/plans/m8-design.md` § 4).
4
+ *
5
+ * Today's `PnpResolutionProvenance` (in `services/pnp-provenance.ts`)
6
+ * tracks decisions made by `PnpToolResolver` only. M8 generalizes the
7
+ * shape to track decisions from every Pass-1 contributor — host
8
+ * placement, host policy, provider veto, QTI gates, and custom policy
9
+ * sources — without losing PNP debugger compatibility.
10
+ *
11
+ * **Compatibility posture.** This module is the new home of the shape;
12
+ * the legacy `pnp-provenance.ts` module remains untouched in PR 1 and
13
+ * is deleted in PR 5 (the rip-out PR) once every consumer has migrated
14
+ * to the engine. The shape stays structurally compatible: existing
15
+ * fields (`contextId`, `resolvedAt`, `sources`, `features`,
16
+ * `decisionLog`, `summary`) are preserved verbatim. The rule
17
+ * vocabulary expands, and `ResolutionDecision.precedence` widens from
18
+ * `1 | 2 | 3 | 4 | 5 | 6` to a `number` so non-QTI rules can fit
19
+ * (the QTI rules continue to use `1`–`6` exactly as before).
20
+ */
21
+ /**
22
+ * Expanded rule vocabulary for `ResolutionDecision.rule` in M8.
23
+ *
24
+ * The first six values map 1:1 onto `PnpResolutionProvenance`'s
25
+ * existing rule names (the QTI 6-level precedence). The remaining
26
+ * values capture the new contributors the M8 engine surfaces:
27
+ * placement membership (step 1), provider veto (step 2), host policy
28
+ * (steps 3 / 4), the QTI-conflict diagnostic (`qti-required-blocked`),
29
+ * custom sources (step 6), and the catch-all `system-default` for the
30
+ * "not configured at any level" fallback.
31
+ */
32
+ export type ToolPolicyDecisionRule = "district-block" | "test-admin-override" | "item-restriction" | "item-requirement" | "district-requirement" | "pnp-support" | "pnp-prohibited" | "placement-membership" | "provider-disabled" | "host-allowlist" | "host-blocked" | "qti-required-blocked" | "custom-source" | "system-default";
33
+ export type ToolPolicySourceType = "organization" | "assessment" | "section" | "item" | "student" | "system" | "host" | "custom";
34
+ export interface ToolPolicyResolutionDecision {
35
+ /** Monotonic step index across the entire decision log. */
36
+ step: number;
37
+ /**
38
+ * Precedence level. QTI rules use `1`–`6` exactly as in today's
39
+ * `PnpResolutionProvenance`. Non-QTI rules use `0` (host-side
40
+ * gates that always fire before QTI) or `7+` (custom sources that
41
+ * fire after QTI).
42
+ */
43
+ precedence: number;
44
+ rule: ToolPolicyDecisionRule;
45
+ /** Tool ID being resolved (post-PNP-support-mapping where applicable). */
46
+ featureId: string;
47
+ action: "block" | "enable" | "skip" | "advisory";
48
+ source: {
49
+ type: ToolPolicySourceType;
50
+ id?: string;
51
+ name?: string;
52
+ };
53
+ reason: string;
54
+ value?: unknown;
55
+ timestamp: Date;
56
+ }
57
+ export interface ToolPolicyFeatureTrail {
58
+ featureId: string;
59
+ finalState: "enabled" | "blocked" | "not-configured" | "advisory-only";
60
+ winningDecision?: ToolPolicyResolutionDecision;
61
+ allDecisions: ToolPolicyResolutionDecision[];
62
+ explanation: string;
63
+ }
64
+ export interface ToolPolicyProvenance {
65
+ contextId: string;
66
+ resolvedAt: Date;
67
+ sources: {
68
+ organization?: {
69
+ id: string;
70
+ name: string;
71
+ pnpDefaults?: unknown;
72
+ };
73
+ assessment?: {
74
+ id: string;
75
+ name: string;
76
+ settings?: unknown;
77
+ };
78
+ section?: {
79
+ id: string;
80
+ title?: string;
81
+ settings?: unknown;
82
+ };
83
+ item?: {
84
+ id: string;
85
+ name?: string;
86
+ settings?: unknown;
87
+ };
88
+ student?: {
89
+ id: string;
90
+ pnpProfile?: unknown;
91
+ };
92
+ host?: {
93
+ id: string;
94
+ config?: unknown;
95
+ };
96
+ };
97
+ features: Map<string, ToolPolicyFeatureTrail>;
98
+ decisionLog: ToolPolicyResolutionDecision[];
99
+ summary: {
100
+ totalFeatures: number;
101
+ enabled: number;
102
+ blocked: number;
103
+ notConfigured: number;
104
+ bySource: Record<string, number>;
105
+ byRule: Record<string, number>;
106
+ };
107
+ }
108
+ interface AddDecisionParams {
109
+ precedence: number;
110
+ rule: ToolPolicyDecisionRule;
111
+ featureId: string;
112
+ action: ToolPolicyResolutionDecision["action"];
113
+ sourceType: ToolPolicySourceType;
114
+ sourceId?: string;
115
+ sourceName?: string;
116
+ reason: string;
117
+ value?: unknown;
118
+ }
119
+ /**
120
+ * Builder mirroring `PnpProvenanceBuilder` but emitting the expanded
121
+ * `ToolPolicyProvenance` shape. Identical API where the field names
122
+ * overlap so an `M8 PR 5` rename of the legacy builder is a near-pure
123
+ * import-path swap.
124
+ */
125
+ export declare class ToolPolicyProvenanceBuilder {
126
+ private provenance;
127
+ private stepCounter;
128
+ constructor(contextId: string);
129
+ addSource(type: keyof ToolPolicyProvenance["sources"], data: {
130
+ id: string;
131
+ name?: string;
132
+ config?: unknown;
133
+ }): this;
134
+ addDecision(params: AddDecisionParams): this;
135
+ /**
136
+ * Reconcile every feature's `finalState` against the post-pipeline
137
+ * candidate set.
138
+ *
139
+ * The eager `finalState` updates inside `addDecision` track the
140
+ * highest-precedence non-skip decision per feature, but a tool's
141
+ * *actual* visibility is only known after step 6 of the
142
+ * composition pipeline — earlier decisions can be reversed by
143
+ * later host gates, QTI gates, or custom sources. This method
144
+ * walks every feature trail and rewrites `finalState` from the
145
+ * final candidate set so callers can rely on
146
+ * `provenance.features.get(toolId)?.finalState` as the canonical
147
+ * "is this tool visible right now?" answer (M8 design § 4 + § 12).
148
+ *
149
+ * Semantics applied per feature:
150
+ * - In `survivingIds` → "enabled"
151
+ * - Not in `survivingIds`, has any non-skip decision → "blocked"
152
+ * - Not in `survivingIds`, only advisory decisions → "advisory-only"
153
+ * - Otherwise → "not-configured"
154
+ *
155
+ * Surviving features without any logged decisions get a synthetic
156
+ * "enabled" trail so the provenance is dense enough for hosts that
157
+ * iterate `provenance.features` to find the visible set.
158
+ */
159
+ reconcileFinalStates(survivingIds: ReadonlySet<string>): this;
160
+ build(): ToolPolicyProvenance;
161
+ }
162
+ export {};
163
+ //# sourceMappingURL=provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../../src/policy/core/provenance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Tool Policy Provenance — generalization of `PnpResolutionProvenance`
3
+ * (M8 — see `.cursor/plans/m8-design.md` § 4).
4
+ *
5
+ * Today's `PnpResolutionProvenance` (in `services/pnp-provenance.ts`)
6
+ * tracks decisions made by `PnpToolResolver` only. M8 generalizes the
7
+ * shape to track decisions from every Pass-1 contributor — host
8
+ * placement, host policy, provider veto, QTI gates, and custom policy
9
+ * sources — without losing PNP debugger compatibility.
10
+ *
11
+ * **Compatibility posture.** This module is the new home of the shape;
12
+ * the legacy `pnp-provenance.ts` module remains untouched in PR 1 and
13
+ * is deleted in PR 5 (the rip-out PR) once every consumer has migrated
14
+ * to the engine. The shape stays structurally compatible: existing
15
+ * fields (`contextId`, `resolvedAt`, `sources`, `features`,
16
+ * `decisionLog`, `summary`) are preserved verbatim. The rule
17
+ * vocabulary expands, and `ResolutionDecision.precedence` widens from
18
+ * `1 | 2 | 3 | 4 | 5 | 6` to a `number` so non-QTI rules can fit
19
+ * (the QTI rules continue to use `1`–`6` exactly as before).
20
+ */
21
+ /**
22
+ * Builder mirroring `PnpProvenanceBuilder` but emitting the expanded
23
+ * `ToolPolicyProvenance` shape. Identical API where the field names
24
+ * overlap so an `M8 PR 5` rename of the legacy builder is a near-pure
25
+ * import-path swap.
26
+ */
27
+ export class ToolPolicyProvenanceBuilder {
28
+ provenance;
29
+ stepCounter = 0;
30
+ constructor(contextId) {
31
+ this.provenance = {
32
+ contextId,
33
+ resolvedAt: new Date(),
34
+ sources: {},
35
+ features: new Map(),
36
+ decisionLog: [],
37
+ summary: {
38
+ totalFeatures: 0,
39
+ enabled: 0,
40
+ blocked: 0,
41
+ notConfigured: 0,
42
+ bySource: {},
43
+ byRule: {},
44
+ },
45
+ };
46
+ }
47
+ addSource(type, data) {
48
+ const base = { id: data.id, name: data.name ?? data.id };
49
+ switch (type) {
50
+ case "organization":
51
+ this.provenance.sources.organization = {
52
+ ...base,
53
+ pnpDefaults: data.config,
54
+ };
55
+ break;
56
+ case "assessment":
57
+ this.provenance.sources.assessment = {
58
+ ...base,
59
+ settings: data.config,
60
+ };
61
+ break;
62
+ case "section":
63
+ this.provenance.sources.section = { ...base, settings: data.config };
64
+ break;
65
+ case "item":
66
+ this.provenance.sources.item = { ...base, settings: data.config };
67
+ break;
68
+ case "student":
69
+ this.provenance.sources.student = {
70
+ id: data.id,
71
+ pnpProfile: data.config,
72
+ };
73
+ break;
74
+ case "host":
75
+ this.provenance.sources.host = { id: data.id, config: data.config };
76
+ break;
77
+ }
78
+ return this;
79
+ }
80
+ addDecision(params) {
81
+ const decision = {
82
+ step: ++this.stepCounter,
83
+ precedence: params.precedence,
84
+ rule: params.rule,
85
+ featureId: params.featureId,
86
+ action: params.action,
87
+ source: {
88
+ type: params.sourceType,
89
+ id: params.sourceId,
90
+ name: params.sourceName,
91
+ },
92
+ reason: params.reason,
93
+ value: params.value,
94
+ timestamp: new Date(),
95
+ };
96
+ this.provenance.decisionLog.push(decision);
97
+ let trail = this.provenance.features.get(params.featureId);
98
+ if (!trail) {
99
+ trail = {
100
+ featureId: params.featureId,
101
+ finalState: "not-configured",
102
+ allDecisions: [],
103
+ explanation: "",
104
+ };
105
+ this.provenance.features.set(params.featureId, trail);
106
+ }
107
+ trail.allDecisions.push(decision);
108
+ // "advisory" doesn't change finalState — it preserves the existing
109
+ // blocked/enabled state and only flags a documented conflict.
110
+ if (params.action === "advisory") {
111
+ if (trail.finalState === "not-configured") {
112
+ trail.finalState = "advisory-only";
113
+ }
114
+ }
115
+ else if (params.action !== "skip") {
116
+ const isWinning = !trail.winningDecision ||
117
+ decision.precedence < trail.winningDecision.precedence;
118
+ if (isWinning) {
119
+ trail.winningDecision = decision;
120
+ trail.finalState = decision.action === "enable" ? "enabled" : "blocked";
121
+ }
122
+ }
123
+ this.provenance.summary.bySource[params.sourceType] =
124
+ (this.provenance.summary.bySource[params.sourceType] || 0) + 1;
125
+ this.provenance.summary.byRule[params.rule] =
126
+ (this.provenance.summary.byRule[params.rule] || 0) + 1;
127
+ return this;
128
+ }
129
+ /**
130
+ * Reconcile every feature's `finalState` against the post-pipeline
131
+ * candidate set.
132
+ *
133
+ * The eager `finalState` updates inside `addDecision` track the
134
+ * highest-precedence non-skip decision per feature, but a tool's
135
+ * *actual* visibility is only known after step 6 of the
136
+ * composition pipeline — earlier decisions can be reversed by
137
+ * later host gates, QTI gates, or custom sources. This method
138
+ * walks every feature trail and rewrites `finalState` from the
139
+ * final candidate set so callers can rely on
140
+ * `provenance.features.get(toolId)?.finalState` as the canonical
141
+ * "is this tool visible right now?" answer (M8 design § 4 + § 12).
142
+ *
143
+ * Semantics applied per feature:
144
+ * - In `survivingIds` → "enabled"
145
+ * - Not in `survivingIds`, has any non-skip decision → "blocked"
146
+ * - Not in `survivingIds`, only advisory decisions → "advisory-only"
147
+ * - Otherwise → "not-configured"
148
+ *
149
+ * Surviving features without any logged decisions get a synthetic
150
+ * "enabled" trail so the provenance is dense enough for hosts that
151
+ * iterate `provenance.features` to find the visible set.
152
+ */
153
+ reconcileFinalStates(survivingIds) {
154
+ for (const featureId of survivingIds) {
155
+ let trail = this.provenance.features.get(featureId);
156
+ if (!trail) {
157
+ trail = {
158
+ featureId,
159
+ finalState: "enabled",
160
+ allDecisions: [],
161
+ explanation: "",
162
+ };
163
+ this.provenance.features.set(featureId, trail);
164
+ continue;
165
+ }
166
+ trail.finalState = "enabled";
167
+ }
168
+ for (const trail of this.provenance.features.values()) {
169
+ if (survivingIds.has(trail.featureId))
170
+ continue;
171
+ const hasNonSkip = trail.allDecisions.some((d) => d.action !== "skip" && d.action !== "advisory");
172
+ const hasAdvisory = trail.allDecisions.some((d) => d.action === "advisory");
173
+ if (hasNonSkip) {
174
+ trail.finalState = "blocked";
175
+ }
176
+ else if (hasAdvisory) {
177
+ trail.finalState = "advisory-only";
178
+ }
179
+ else {
180
+ trail.finalState = "not-configured";
181
+ }
182
+ }
183
+ return this;
184
+ }
185
+ build() {
186
+ for (const trail of this.provenance.features.values()) {
187
+ trail.explanation = generateExplanation(trail);
188
+ }
189
+ for (const trail of this.provenance.features.values()) {
190
+ this.provenance.summary.totalFeatures++;
191
+ if (trail.finalState === "enabled") {
192
+ this.provenance.summary.enabled++;
193
+ }
194
+ else if (trail.finalState === "blocked") {
195
+ this.provenance.summary.blocked++;
196
+ }
197
+ else {
198
+ this.provenance.summary.notConfigured++;
199
+ }
200
+ }
201
+ return this.provenance;
202
+ }
203
+ }
204
+ function generateExplanation(trail) {
205
+ if (!trail.winningDecision) {
206
+ if (trail.finalState === "advisory-only") {
207
+ const advisory = trail.allDecisions.find((d) => d.action === "advisory");
208
+ return advisory
209
+ ? `Feature "${trail.featureId}" was kept in its host-decided state; ${advisory.reason}`
210
+ : `Feature "${trail.featureId}" carries an advisory note.`;
211
+ }
212
+ return `Feature "${trail.featureId}" was not configured at any level.`;
213
+ }
214
+ const decision = trail.winningDecision;
215
+ const sourceName = decision.source.name || decision.source.type;
216
+ let explanation = `Feature "${trail.featureId}" is ${trail.finalState}.\n\n`;
217
+ explanation += `**Primary Reason** (${decision.rule}):\n`;
218
+ explanation += `${decision.reason}\n`;
219
+ explanation += `Source: ${sourceName} (${decision.source.type})\n`;
220
+ const overridden = trail.allDecisions.filter((d) => d.step !== decision.step &&
221
+ d.action !== "skip" &&
222
+ d.action !== "advisory" &&
223
+ d.precedence > decision.precedence);
224
+ if (overridden.length > 0) {
225
+ explanation += `\n**Overridden Rules**:\n`;
226
+ for (const d of overridden) {
227
+ explanation += `- ${d.rule}: ${d.reason}\n`;
228
+ }
229
+ }
230
+ return explanation;
231
+ }
232
+ //# sourceMappingURL=provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../../src/policy/core/provenance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * QTI input detection (M8 PR 4 — see `.cursor/plans/m8-design.md` F2 and
3
+ * `.cursor/plans/m8-implementation-plan.md` § PR 4).
4
+ *
5
+ * Pure helpers that decide whether the inputs the engine has been given
6
+ * actually carry QTI 6-level precedence material. Used by:
7
+ *
8
+ * - {@link ToolPolicyEngine}'s constructor as the auto-default for
9
+ * `qtiEnforcement` when the host did not pass an explicit `"on"` /
10
+ * `"off"`.
11
+ * - {@link ToolkitCoordinator.resolveEffectiveQtiEnforcement} as the
12
+ * auto-mode rule. PR 2 used a coarse "any non-null assessment → on"
13
+ * placeholder; PR 4 narrows it to "assessment OR currentItemRef
14
+ * carries QTI material."
15
+ *
16
+ * The rule is intentionally narrow. Hosts that bind a bare assessment
17
+ * record (only `id` / `name`, no PNP and no settings) get the legacy
18
+ * floating-tools behavior — QTI gates do not engage. The flip happens
19
+ * the moment the assessment carries any of:
20
+ *
21
+ * - `personalNeedsProfile` (any `supports`, `prohibitedSupports`, or
22
+ * `activateAtInit`),
23
+ * - `settings.districtPolicy` (any `blockedTools`, `requiredTools`,
24
+ * or `policies`),
25
+ * - `settings.testAdministration` (any populated key — `mode`,
26
+ * `toolOverrides`, etc.),
27
+ * - or the bound `currentItemRef.settings` carries
28
+ * `requiredTools` / `restrictedTools` / `toolParameters`.
29
+ *
30
+ * Hosts opt out of the auto-on behavior by passing
31
+ * `qtiEnforcement: "off"` explicitly (engine input or
32
+ * `ToolkitCoordinator.setQtiEnforcement("off")`).
33
+ */
34
+ import type { AssessmentEntity, AssessmentItemRef } from "@pie-players/pie-players-shared/types";
35
+ /**
36
+ * Return `true` when the assessment carries any QTI 6-level precedence
37
+ * material that the engine's `QtiPolicySource` would consume.
38
+ *
39
+ * The check is structural — the presence of a non-empty PNP, district
40
+ * policy, or test administration block is enough; the content does not
41
+ * have to validate against any specific rule.
42
+ */
43
+ export declare function assessmentHasQtiInputs(assessment: AssessmentEntity | null | undefined): boolean;
44
+ /**
45
+ * Return `true` when the bound item reference carries item-level QTI
46
+ * material (`requiredTools`, `restrictedTools`, or `toolParameters`).
47
+ *
48
+ * Used in addition to {@link assessmentHasQtiInputs} so a host that
49
+ * navigates to an item with QTI-relevant settings — without a parent
50
+ * assessment carrying its own PNP/district/test-admin block — still
51
+ * gets QTI gates engaged for that item.
52
+ */
53
+ export declare function itemRefHasQtiInputs(itemRef: AssessmentItemRef | null | undefined): boolean;
54
+ /**
55
+ * Resolve the default `qtiEnforcement` mode given the bound inputs.
56
+ *
57
+ * Returns `"on"` when {@link assessmentHasQtiInputs} or
58
+ * {@link itemRefHasQtiInputs} reports any QTI material; otherwise
59
+ * `"off"`. Hosts override the default by passing an explicit
60
+ * `qtiEnforcement` value (engine input) or by calling
61
+ * `ToolkitCoordinator.setQtiEnforcement("on" | "off")`.
62
+ */
63
+ export declare function resolveDefaultQtiEnforcement(args: {
64
+ assessment?: AssessmentEntity | null;
65
+ currentItemRef?: AssessmentItemRef | null;
66
+ }): "on" | "off";
67
+ //# sourceMappingURL=qti-inputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qti-inputs.d.ts","sourceRoot":"","sources":["../../../src/policy/core/qti-inputs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EACX,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,uCAAuC,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACrC,UAAU,EAAE,gBAAgB,GAAG,IAAI,GAAG,SAAS,GAC7C,OAAO,CA4DT;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,iBAAiB,GAAG,IAAI,GAAG,SAAS,GAC3C,OAAO,CAuBT;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IAClD,UAAU,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrC,cAAc,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC1C,GAAG,IAAI,GAAG,KAAK,CAIf"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * QTI input detection (M8 PR 4 — see `.cursor/plans/m8-design.md` F2 and
3
+ * `.cursor/plans/m8-implementation-plan.md` § PR 4).
4
+ *
5
+ * Pure helpers that decide whether the inputs the engine has been given
6
+ * actually carry QTI 6-level precedence material. Used by:
7
+ *
8
+ * - {@link ToolPolicyEngine}'s constructor as the auto-default for
9
+ * `qtiEnforcement` when the host did not pass an explicit `"on"` /
10
+ * `"off"`.
11
+ * - {@link ToolkitCoordinator.resolveEffectiveQtiEnforcement} as the
12
+ * auto-mode rule. PR 2 used a coarse "any non-null assessment → on"
13
+ * placeholder; PR 4 narrows it to "assessment OR currentItemRef
14
+ * carries QTI material."
15
+ *
16
+ * The rule is intentionally narrow. Hosts that bind a bare assessment
17
+ * record (only `id` / `name`, no PNP and no settings) get the legacy
18
+ * floating-tools behavior — QTI gates do not engage. The flip happens
19
+ * the moment the assessment carries any of:
20
+ *
21
+ * - `personalNeedsProfile` (any `supports`, `prohibitedSupports`, or
22
+ * `activateAtInit`),
23
+ * - `settings.districtPolicy` (any `blockedTools`, `requiredTools`,
24
+ * or `policies`),
25
+ * - `settings.testAdministration` (any populated key — `mode`,
26
+ * `toolOverrides`, etc.),
27
+ * - or the bound `currentItemRef.settings` carries
28
+ * `requiredTools` / `restrictedTools` / `toolParameters`.
29
+ *
30
+ * Hosts opt out of the auto-on behavior by passing
31
+ * `qtiEnforcement: "off"` explicitly (engine input or
32
+ * `ToolkitCoordinator.setQtiEnforcement("off")`).
33
+ */
34
+ /**
35
+ * Return `true` when the assessment carries any QTI 6-level precedence
36
+ * material that the engine's `QtiPolicySource` would consume.
37
+ *
38
+ * The check is structural — the presence of a non-empty PNP, district
39
+ * policy, or test administration block is enough; the content does not
40
+ * have to validate against any specific rule.
41
+ */
42
+ export function assessmentHasQtiInputs(assessment) {
43
+ if (!assessment)
44
+ return false;
45
+ const pnp = assessment.personalNeedsProfile;
46
+ if (pnp) {
47
+ if (Array.isArray(pnp.supports) && pnp.supports.length > 0)
48
+ return true;
49
+ if (Array.isArray(pnp.prohibitedSupports) &&
50
+ pnp.prohibitedSupports.length > 0) {
51
+ return true;
52
+ }
53
+ if (Array.isArray(pnp.activateAtInit) &&
54
+ pnp.activateAtInit.length > 0) {
55
+ return true;
56
+ }
57
+ }
58
+ const settings = assessment.settings;
59
+ if (settings) {
60
+ const district = settings.districtPolicy;
61
+ if (district) {
62
+ if (Array.isArray(district.blockedTools) &&
63
+ district.blockedTools.length > 0) {
64
+ return true;
65
+ }
66
+ if (Array.isArray(district.requiredTools) &&
67
+ district.requiredTools.length > 0) {
68
+ return true;
69
+ }
70
+ if (district.policies &&
71
+ typeof district.policies === "object" &&
72
+ Object.keys(district.policies).length > 0) {
73
+ return true;
74
+ }
75
+ }
76
+ const admin = settings.testAdministration;
77
+ if (admin && typeof admin === "object") {
78
+ for (const key of Object.keys(admin)) {
79
+ const value = admin[key];
80
+ if (value === undefined || value === null)
81
+ continue;
82
+ if (Array.isArray(value) && value.length === 0)
83
+ continue;
84
+ if (typeof value === "object" &&
85
+ !Array.isArray(value) &&
86
+ Object.keys(value).length === 0) {
87
+ continue;
88
+ }
89
+ return true;
90
+ }
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+ /**
96
+ * Return `true` when the bound item reference carries item-level QTI
97
+ * material (`requiredTools`, `restrictedTools`, or `toolParameters`).
98
+ *
99
+ * Used in addition to {@link assessmentHasQtiInputs} so a host that
100
+ * navigates to an item with QTI-relevant settings — without a parent
101
+ * assessment carrying its own PNP/district/test-admin block — still
102
+ * gets QTI gates engaged for that item.
103
+ */
104
+ export function itemRefHasQtiInputs(itemRef) {
105
+ const settings = itemRef?.settings;
106
+ if (!settings)
107
+ return false;
108
+ if (Array.isArray(settings.requiredTools) &&
109
+ settings.requiredTools.length > 0) {
110
+ return true;
111
+ }
112
+ if (Array.isArray(settings.restrictedTools) &&
113
+ settings.restrictedTools.length > 0) {
114
+ return true;
115
+ }
116
+ if (settings.toolParameters &&
117
+ typeof settings.toolParameters === "object" &&
118
+ Object.keys(settings.toolParameters).length > 0) {
119
+ return true;
120
+ }
121
+ return false;
122
+ }
123
+ /**
124
+ * Resolve the default `qtiEnforcement` mode given the bound inputs.
125
+ *
126
+ * Returns `"on"` when {@link assessmentHasQtiInputs} or
127
+ * {@link itemRefHasQtiInputs} reports any QTI material; otherwise
128
+ * `"off"`. Hosts override the default by passing an explicit
129
+ * `qtiEnforcement` value (engine input) or by calling
130
+ * `ToolkitCoordinator.setQtiEnforcement("on" | "off")`.
131
+ */
132
+ export function resolveDefaultQtiEnforcement(args) {
133
+ if (assessmentHasQtiInputs(args.assessment))
134
+ return "on";
135
+ if (itemRefHasQtiInputs(args.currentItemRef))
136
+ return "on";
137
+ return "off";
138
+ }
139
+ //# sourceMappingURL=qti-inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qti-inputs.js","sourceRoot":"","sources":["../../../src/policy/core/qti-inputs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Tool policy engine — facade entry point (M8 PR 1).
3
+ *
4
+ * Narrow, stable public surface for hosts that want to instantiate or
5
+ * consume a `ToolPolicyEngine`. Re-exports the engine class, its
6
+ * decision request/response types, the Svelte context key, and the
7
+ * minimal `PolicySource` extension contract.
8
+ *
9
+ * **Pairing.** This module is the stable counterpart to
10
+ * `./policy/internal`, which exposes the wider, evolving surface
11
+ * (composition pipeline, provenance builder, QTI source). Consumers
12
+ * that only need to drive an engine and read its decisions should
13
+ * import from here. Consumers that need to reach past the facade
14
+ * (e.g. test the composition pipeline directly, build a custom QTI
15
+ * source variant, instrument provenance generation) should import
16
+ * from `./policy/internal` and accept the documented stability
17
+ * disclaimer there.
18
+ *
19
+ * Mirrors the M7 `runtime/engine` / `runtime/internal` split — see
20
+ * the same rationale in `src/runtime/engine.ts`.
21
+ */
22
+ export { ToolPolicyEngine, type QtiEnforcementMode, type ResolvedEngineInputs, type ToolPolicyChangeEvent, type ToolPolicyChangeListener, type ToolPolicyEngineArgs, type ToolPolicyEngineInputs, } from "./core/ToolPolicyEngine.js";
23
+ export { TOOL_POLICY_ENGINE_KEY, type ToolPolicyEngineContext, } from "./core/engine-context.js";
24
+ export type { QtiRequiredBlockedDetails, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolPolicyDiagnostic, ToolPolicyDiagnosticCode, ToolPolicyEntry, ToolPolicyHostGate, ToolScope, } from "./core/decision-types.js";
25
+ export type { PolicySource, PolicySourceDecisionContext, PolicySourceProvenanceEntry, PolicySourceResult, } from "./core/PolicySource.js";
26
+ export type { PolicySourceTag, QtiPolicySourceRule, QtiPolicySourceTag, CustomPolicySourceTag, } from "./core/policy-source-tag.js";
27
+ export type { ToolPolicyDecisionRule, ToolPolicyFeatureTrail, ToolPolicyProvenance, ToolPolicyResolutionDecision, ToolPolicySourceType, } from "./core/provenance.js";
28
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/policy/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACN,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,GAC3B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACN,sBAAsB,EACtB,KAAK,uBAAuB,GAC5B,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACX,yBAAyB,EACzB,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,kBAAkB,EAClB,SAAS,GACT,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACX,YAAY,EACZ,2BAA2B,EAC3B,2BAA2B,EAC3B,kBAAkB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACX,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,GACrB,MAAM,6BAA6B,CAAC;AAErC,YAAY,EACX,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,GACpB,MAAM,sBAAsB,CAAC"}