@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.
- package/README.md +289 -5
- package/dist/components/ItemToolBar.custom-element.js +165 -92
- package/dist/components/PieAssessmentToolkit.custom-element.js +604 -107
- package/dist/components/SectionToolBar.custom-element.js +0 -36
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/policy/core/PolicySource.d.ts +64 -0
- package/dist/policy/core/PolicySource.d.ts.map +1 -0
- package/dist/policy/core/PolicySource.js +17 -0
- package/dist/policy/core/PolicySource.js.map +1 -0
- package/dist/policy/core/ToolPolicyEngine.d.ts +123 -0
- package/dist/policy/core/ToolPolicyEngine.d.ts.map +1 -0
- package/dist/policy/core/ToolPolicyEngine.js +217 -0
- package/dist/policy/core/ToolPolicyEngine.js.map +1 -0
- package/dist/policy/core/compose-decision.d.ts +37 -0
- package/dist/policy/core/compose-decision.d.ts.map +1 -0
- package/dist/policy/core/compose-decision.js +332 -0
- package/dist/policy/core/compose-decision.js.map +1 -0
- package/dist/policy/core/decision-types.d.ts +142 -0
- package/dist/policy/core/decision-types.d.ts.map +1 -0
- package/dist/policy/core/decision-types.js +13 -0
- package/dist/policy/core/decision-types.js.map +1 -0
- package/dist/policy/core/engine-context.d.ts +30 -0
- package/dist/policy/core/engine-context.d.ts.map +1 -0
- package/dist/policy/core/engine-context.js +28 -0
- package/dist/policy/core/engine-context.js.map +1 -0
- package/dist/policy/core/policy-source-tag.d.ts +32 -0
- package/dist/policy/core/policy-source-tag.d.ts.map +1 -0
- package/dist/policy/core/policy-source-tag.js +29 -0
- package/dist/policy/core/policy-source-tag.js.map +1 -0
- package/dist/policy/core/provenance.d.ts +163 -0
- package/dist/policy/core/provenance.d.ts.map +1 -0
- package/dist/policy/core/provenance.js +232 -0
- package/dist/policy/core/provenance.js.map +1 -0
- package/dist/policy/core/qti-inputs.d.ts +67 -0
- package/dist/policy/core/qti-inputs.d.ts.map +1 -0
- package/dist/policy/core/qti-inputs.js +139 -0
- package/dist/policy/core/qti-inputs.js.map +1 -0
- package/dist/policy/engine.d.ts +28 -0
- package/dist/policy/engine.d.ts.map +1 -0
- package/dist/policy/engine.js +24 -0
- package/dist/policy/engine.js.map +1 -0
- package/dist/policy/internal.d.ts +26 -0
- package/dist/policy/internal.d.ts.map +1 -0
- package/dist/policy/internal.js +26 -0
- package/dist/policy/internal.js.map +1 -0
- package/dist/policy/sources/QtiPolicySource.d.ts +146 -0
- package/dist/policy/sources/QtiPolicySource.d.ts.map +1 -0
- package/dist/policy/sources/QtiPolicySource.js +270 -0
- package/dist/policy/sources/QtiPolicySource.js.map +1 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +145 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.js +164 -3
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +144 -0
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts.map +1 -0
- package/dist/runtime/adapter/SectionEngineAdapter.js +183 -0
- package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -0
- package/dist/runtime/adapter/coordinator-bridge.d.ts +93 -0
- package/dist/runtime/adapter/coordinator-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/coordinator-bridge.js +127 -0
- package/dist/runtime/adapter/coordinator-bridge.js.map +1 -0
- package/dist/runtime/adapter/dom-event-bridge.d.ts +63 -0
- package/dist/runtime/adapter/dom-event-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/dom-event-bridge.js +95 -0
- package/dist/runtime/adapter/dom-event-bridge.js.map +1 -0
- package/dist/runtime/adapter/framework-error-bridge.d.ts +37 -0
- package/dist/runtime/adapter/framework-error-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/framework-error-bridge.js +47 -0
- package/dist/runtime/adapter/framework-error-bridge.js.map +1 -0
- package/dist/runtime/adapter/instrumentation-bridge.d.ts +32 -0
- package/dist/runtime/adapter/instrumentation-bridge.d.ts.map +1 -0
- package/dist/runtime/adapter/instrumentation-bridge.js +39 -0
- package/dist/runtime/adapter/instrumentation-bridge.js.map +1 -0
- package/dist/runtime/adapter/subscriber-fanout.d.ts +35 -0
- package/dist/runtime/adapter/subscriber-fanout.d.ts.map +1 -0
- package/dist/runtime/adapter/subscriber-fanout.js +60 -0
- package/dist/runtime/adapter/subscriber-fanout.js.map +1 -0
- package/dist/runtime/core/SectionEngineCore.d.ts +52 -0
- package/dist/runtime/core/SectionEngineCore.d.ts.map +1 -0
- package/dist/runtime/core/SectionEngineCore.js +81 -0
- package/dist/runtime/core/SectionEngineCore.js.map +1 -0
- package/dist/runtime/core/cohort.d.ts +40 -0
- package/dist/runtime/core/cohort.d.ts.map +1 -0
- package/dist/runtime/core/cohort.js +49 -0
- package/dist/runtime/core/cohort.js.map +1 -0
- package/dist/runtime/core/engine-input.d.ts +114 -0
- package/dist/runtime/core/engine-input.d.ts.map +1 -0
- package/dist/runtime/core/engine-input.js +19 -0
- package/dist/runtime/core/engine-input.js.map +1 -0
- package/dist/runtime/core/engine-output.d.ts +62 -0
- package/dist/runtime/core/engine-output.d.ts.map +1 -0
- package/dist/runtime/core/engine-output.js +27 -0
- package/dist/runtime/core/engine-output.js.map +1 -0
- package/dist/runtime/core/engine-readiness.d.ts +49 -0
- package/dist/runtime/core/engine-readiness.d.ts.map +1 -0
- package/dist/runtime/core/engine-readiness.js +47 -0
- package/dist/runtime/core/engine-readiness.js.map +1 -0
- package/dist/runtime/core/engine-resolver.d.ts +172 -0
- package/dist/runtime/core/engine-resolver.d.ts.map +1 -0
- package/dist/runtime/core/engine-resolver.js +170 -0
- package/dist/runtime/core/engine-resolver.js.map +1 -0
- package/dist/runtime/core/engine-stage-derivation.d.ts +19 -0
- package/dist/runtime/core/engine-stage-derivation.d.ts.map +1 -0
- package/dist/runtime/core/engine-stage-derivation.js +30 -0
- package/dist/runtime/core/engine-stage-derivation.js.map +1 -0
- package/dist/runtime/core/engine-state.d.ts +83 -0
- package/dist/runtime/core/engine-state.d.ts.map +1 -0
- package/dist/runtime/core/engine-state.js +49 -0
- package/dist/runtime/core/engine-state.js.map +1 -0
- package/dist/runtime/core/engine-transition.d.ts +49 -0
- package/dist/runtime/core/engine-transition.d.ts.map +1 -0
- package/dist/runtime/core/engine-transition.js +261 -0
- package/dist/runtime/core/engine-transition.js.map +1 -0
- package/dist/runtime/engine-context.d.ts +40 -0
- package/dist/runtime/engine-context.d.ts.map +1 -0
- package/dist/runtime/engine-context.js +32 -0
- package/dist/runtime/engine-context.js.map +1 -0
- package/dist/runtime/engine.d.ts +24 -0
- package/dist/runtime/engine.d.ts.map +1 -0
- package/dist/runtime/engine.js +24 -0
- package/dist/runtime/engine.js.map +1 -0
- package/dist/runtime/internal.d.ts +44 -0
- package/dist/runtime/internal.d.ts.map +1 -0
- package/dist/runtime/internal.js +46 -0
- package/dist/runtime/internal.js.map +1 -0
- package/dist/runtime/runtime-id.d.ts +8 -0
- package/dist/runtime/runtime-id.d.ts.map +1 -0
- package/dist/runtime/runtime-id.js +10 -0
- package/dist/runtime/runtime-id.js.map +1 -0
- package/dist/runtime/section-runtime-engine-host-context.d.ts +74 -0
- package/dist/runtime/section-runtime-engine-host-context.d.ts.map +1 -0
- package/dist/runtime/section-runtime-engine-host-context.js +96 -0
- package/dist/runtime/section-runtime-engine-host-context.js.map +1 -0
- package/dist/runtime/stage-emit-gate.d.ts +93 -0
- package/dist/runtime/stage-emit-gate.d.ts.map +1 -0
- package/dist/runtime/stage-emit-gate.js +83 -0
- package/dist/runtime/stage-emit-gate.js.map +1 -0
- package/dist/services/ToolkitCoordinator.d.ts +280 -7
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +412 -32
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/framework-error-bus.d.ts +73 -0
- package/dist/services/framework-error-bus.d.ts.map +1 -0
- package/dist/services/framework-error-bus.js +68 -0
- package/dist/services/framework-error-bus.js.map +1 -0
- package/dist/services/framework-error.d.ts +36 -1
- package/dist/services/framework-error.d.ts.map +1 -1
- package/dist/services/framework-error.js +37 -0
- package/dist/services/framework-error.js.map +1 -1
- package/dist/services/interfaces.d.ts +78 -0
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/section-controller-types.d.ts +20 -0
- package/dist/services/section-controller-types.d.ts.map +1 -1
- package/dist/services/tools-config-normalizer.d.ts +23 -0
- package/dist/services/tools-config-normalizer.d.ts.map +1 -1
- package/dist/services/tools-config-normalizer.js +13 -1
- package/dist/services/tools-config-normalizer.js.map +1 -1
- package/dist/tools/client.d.ts +0 -1
- package/dist/tools/client.d.ts.map +1 -1
- package/dist/tools/client.js +0 -2
- package/dist/tools/client.js.map +1 -1
- package/dist/tools/types.d.ts +0 -26
- package/dist/tools/types.d.ts.map +1 -1
- package/package.json +23 -7
- package/dist/runtime/runtime-event-guards.d.ts +0 -2
- package/dist/runtime/runtime-event-guards.d.ts.map +0 -1
- package/dist/runtime/runtime-event-guards.js +0 -4
- package/dist/runtime/runtime-event-guards.js.map +0 -1
- package/dist/tools/tool-coordinator.d.ts +0 -72
- package/dist/tools/tool-coordinator.d.ts.map +0 -1
- package/dist/tools/tool-coordinator.js +0 -194
- package/dist/tools/tool-coordinator.js.map +0 -1
- package/dist/types/events.d.ts +0 -157
- package/dist/types/events.d.ts.map +0 -1
- package/dist/types/events.js +0 -11
- package/dist/types/events.js.map +0 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose-decision pipeline (M8 — see `.cursor/plans/m8-design.md` § 3).
|
|
3
|
+
*
|
|
4
|
+
* Pure function: given a decision request, the host's tools config,
|
|
5
|
+
* an optional QTI source, and the registered custom sources, returns
|
|
6
|
+
* a `ToolPolicyDecision`. The function performs no I/O, holds no
|
|
7
|
+
* mutable state, and never reaches into Svelte / DOM — that is the
|
|
8
|
+
* `ToolPolicyEngine` class's job.
|
|
9
|
+
*
|
|
10
|
+
* The six steps below mirror the design doc exactly. The tests in
|
|
11
|
+
* `tests/policy/compose-decision.test.ts` lock the orchestration in.
|
|
12
|
+
*/
|
|
13
|
+
import { normalizeToolList, } from "../../services/tools-config-normalizer.js";
|
|
14
|
+
import { ToolPolicyProvenanceBuilder } from "./provenance.js";
|
|
15
|
+
export function composeDecision(inputs) {
|
|
16
|
+
const { request, tools, qti, customSources, contextId } = inputs;
|
|
17
|
+
const builder = new ToolPolicyProvenanceBuilder(contextId);
|
|
18
|
+
builder.addSource("host", {
|
|
19
|
+
id: request.scope.scopeId,
|
|
20
|
+
name: `${request.level}:${request.scope.scopeId}`,
|
|
21
|
+
config: {
|
|
22
|
+
placement: tools.placement,
|
|
23
|
+
policy: tools.policy,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
const placement = normalizeToolList(tools.placement[request.level]);
|
|
27
|
+
const allowed = normalizeToolList(tools.policy.allowed);
|
|
28
|
+
const blocked = new Set(normalizeToolList(tools.policy.blocked));
|
|
29
|
+
// Step 1 — Membership filter.
|
|
30
|
+
//
|
|
31
|
+
// Logged with `action: "skip"` rather than `"enable"` because a
|
|
32
|
+
// placement-membership entry is the pipeline's starting point, not
|
|
33
|
+
// a winning decision. Subsequent steps may block the tool at the
|
|
34
|
+
// same precedence (0); the builder's `decision.precedence <
|
|
35
|
+
// winning.precedence` rule would otherwise let the placement entry
|
|
36
|
+
// win equality and mask later blocks. Surviving tools are flipped
|
|
37
|
+
// to `enabled` by `builder.reconcileFinalStates(...)` at the end of
|
|
38
|
+
// the pipeline once the final candidate set is known.
|
|
39
|
+
let candidates = [];
|
|
40
|
+
for (const toolId of placement) {
|
|
41
|
+
candidates.push(toolId);
|
|
42
|
+
builder.addDecision({
|
|
43
|
+
precedence: 0,
|
|
44
|
+
rule: "placement-membership",
|
|
45
|
+
featureId: toolId,
|
|
46
|
+
action: "skip",
|
|
47
|
+
sourceType: "host",
|
|
48
|
+
reason: `Listed in tools.placement.${request.level}`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Step 2 — Provider veto.
|
|
52
|
+
candidates = candidates.filter((toolId) => {
|
|
53
|
+
const providerEnabled = tools.providers?.[toolId]?.enabled;
|
|
54
|
+
if (providerEnabled === false) {
|
|
55
|
+
builder.addDecision({
|
|
56
|
+
precedence: 0,
|
|
57
|
+
rule: "provider-disabled",
|
|
58
|
+
featureId: toolId,
|
|
59
|
+
action: "block",
|
|
60
|
+
sourceType: "host",
|
|
61
|
+
reason: `tools.providers.${toolId}.enabled === false`,
|
|
62
|
+
value: false,
|
|
63
|
+
});
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
});
|
|
68
|
+
// Step 3 — Host whitelist (only when non-empty).
|
|
69
|
+
if (allowed.length > 0) {
|
|
70
|
+
const allowedSet = new Set(allowed);
|
|
71
|
+
candidates = candidates.filter((toolId) => {
|
|
72
|
+
if (!allowedSet.has(toolId)) {
|
|
73
|
+
builder.addDecision({
|
|
74
|
+
precedence: 0,
|
|
75
|
+
rule: "host-allowlist",
|
|
76
|
+
featureId: toolId,
|
|
77
|
+
action: "block",
|
|
78
|
+
sourceType: "host",
|
|
79
|
+
reason: "Not present in tools.policy.allowed",
|
|
80
|
+
value: allowed,
|
|
81
|
+
});
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// Step 4 — Host blocklist (absolute).
|
|
88
|
+
candidates = candidates.filter((toolId) => {
|
|
89
|
+
if (blocked.has(toolId)) {
|
|
90
|
+
builder.addDecision({
|
|
91
|
+
precedence: 0,
|
|
92
|
+
rule: "host-blocked",
|
|
93
|
+
featureId: toolId,
|
|
94
|
+
action: "block",
|
|
95
|
+
sourceType: "host",
|
|
96
|
+
reason: "Listed in tools.policy.blocked",
|
|
97
|
+
value: tools.policy.blocked,
|
|
98
|
+
});
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
// Step 5 — QTI gates.
|
|
104
|
+
const diagnostics = [];
|
|
105
|
+
const sourcesByTool = new Map();
|
|
106
|
+
for (const toolId of candidates) {
|
|
107
|
+
sourcesByTool.set(toolId, ["placement"]);
|
|
108
|
+
}
|
|
109
|
+
// Snapshot the post-host candidate set so step 5b can distinguish
|
|
110
|
+
// "host removed a QTI-mandated tool" (qtiRequiredBlocked fires)
|
|
111
|
+
// from "QTI's own precedence removed a QTI-mandated tool" (fix
|
|
112
|
+
// per M8 PR 1 R1 S1 — multi-rule conflict on the same tool must
|
|
113
|
+
// not blame the host).
|
|
114
|
+
const postHostCandidates = new Set(candidates);
|
|
115
|
+
let qtiResult = null;
|
|
116
|
+
if (qti.enforcement === "on" && qti.source && qti.assessment) {
|
|
117
|
+
qtiResult = qti.source.apply({
|
|
118
|
+
assessment: qti.assessment,
|
|
119
|
+
currentItemRef: qti.currentItemRef,
|
|
120
|
+
});
|
|
121
|
+
if (qtiResult.sources.assessment) {
|
|
122
|
+
builder.addSource("assessment", qtiResult.sources.assessment);
|
|
123
|
+
}
|
|
124
|
+
if (qtiResult.sources.student) {
|
|
125
|
+
builder.addSource("student", qtiResult.sources.student);
|
|
126
|
+
}
|
|
127
|
+
if (qtiResult.sources.item) {
|
|
128
|
+
builder.addSource("item", qtiResult.sources.item);
|
|
129
|
+
}
|
|
130
|
+
for (const decision of qtiResult.decisions) {
|
|
131
|
+
builder.addDecision({
|
|
132
|
+
precedence: decision.precedence,
|
|
133
|
+
rule: decision.rule,
|
|
134
|
+
featureId: decision.featureId,
|
|
135
|
+
action: decision.action,
|
|
136
|
+
sourceType: decision.sourceType,
|
|
137
|
+
reason: decision.reason,
|
|
138
|
+
value: decision.value,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// 5a — remove QTI-blocked tools from the candidate set.
|
|
142
|
+
candidates = candidates.filter((toolId) => {
|
|
143
|
+
return !qtiResult.blockedToolIds.has(toolId);
|
|
144
|
+
});
|
|
145
|
+
for (const toolId of Array.from(sourcesByTool.keys())) {
|
|
146
|
+
if (qtiResult.blockedToolIds.has(toolId)) {
|
|
147
|
+
sourcesByTool.delete(toolId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 5b — surface qtiRequiredBlocked diagnostics ONLY for
|
|
151
|
+
// QTI-mandated tools that the host removed (i.e., the tool was
|
|
152
|
+
// NOT in `postHostCandidates`). Mandated tools removed by
|
|
153
|
+
// QTI's own higher-precedence rule (e.g. `restrictedTools`
|
|
154
|
+
// blocking a `requiredTools` mandate) are an internal QTI
|
|
155
|
+
// resolution and must not be reported as a host conflict — the
|
|
156
|
+
// loser's `block` decision is already in the trail with full
|
|
157
|
+
// QTI provenance.
|
|
158
|
+
//
|
|
159
|
+
// `details` carries `{ rule, hostRule, hostValue }` so consumers
|
|
160
|
+
// can render the conflict as a single sentence without
|
|
161
|
+
// re-deriving the host gate from `provenance.features`. See
|
|
162
|
+
// `QtiRequiredBlockedDetails` for the typed shape.
|
|
163
|
+
for (const mandatedToolId of qtiResult.mandatedToolIds) {
|
|
164
|
+
const removedByHost = !postHostCandidates.has(mandatedToolId);
|
|
165
|
+
if (!removedByHost)
|
|
166
|
+
continue;
|
|
167
|
+
const flag = qtiResult.perToolFlags.get(mandatedToolId);
|
|
168
|
+
const ruleName = flag?.rule ?? "qti-required";
|
|
169
|
+
const hostGate = detectHostRemovalGate(mandatedToolId, {
|
|
170
|
+
placement,
|
|
171
|
+
allowed,
|
|
172
|
+
blocked,
|
|
173
|
+
providers: tools.providers,
|
|
174
|
+
});
|
|
175
|
+
const details = {
|
|
176
|
+
rule: ruleName,
|
|
177
|
+
hostRule: hostGate.hostRule,
|
|
178
|
+
hostValue: hostGate.hostValue,
|
|
179
|
+
};
|
|
180
|
+
diagnostics.push({
|
|
181
|
+
code: "tool-policy.qtiRequiredBlocked",
|
|
182
|
+
level: request.level,
|
|
183
|
+
toolId: mandatedToolId,
|
|
184
|
+
message: `QTI mandates "${mandatedToolId}" (${ruleName}) but host policy removed it from level "${request.level}" via ${hostGate.hostRule}.`,
|
|
185
|
+
details,
|
|
186
|
+
});
|
|
187
|
+
builder.addDecision({
|
|
188
|
+
precedence: 0,
|
|
189
|
+
rule: "qti-required-blocked",
|
|
190
|
+
featureId: mandatedToolId,
|
|
191
|
+
action: "advisory",
|
|
192
|
+
sourceType: "host",
|
|
193
|
+
reason: `Host removed QTI-mandated tool "${mandatedToolId}" via ${hostGate.hostRule}`,
|
|
194
|
+
value: details,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// 5c — attach QTI source tags to surviving entries.
|
|
198
|
+
for (const toolId of candidates) {
|
|
199
|
+
const flag = qtiResult.perToolFlags.get(toolId);
|
|
200
|
+
if (flag) {
|
|
201
|
+
const tags = sourcesByTool.get(toolId) ?? [];
|
|
202
|
+
tags.push(`qti.${flag.rule}`);
|
|
203
|
+
sourcesByTool.set(toolId, tags);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Step 6 — Custom PolicySources.
|
|
208
|
+
for (let i = 0; i < customSources.length; i++) {
|
|
209
|
+
const source = customSources[i];
|
|
210
|
+
const before = candidates.slice();
|
|
211
|
+
const result = source.refine({
|
|
212
|
+
request,
|
|
213
|
+
candidates: before,
|
|
214
|
+
});
|
|
215
|
+
const refinedSet = new Set(result.refinedCandidates);
|
|
216
|
+
// Reject any superset additions (custom sources can only narrow).
|
|
217
|
+
for (const toolId of result.refinedCandidates) {
|
|
218
|
+
if (!before.includes(toolId)) {
|
|
219
|
+
diagnostics.push({
|
|
220
|
+
code: "tool-policy.placementMissing",
|
|
221
|
+
level: request.level,
|
|
222
|
+
toolId,
|
|
223
|
+
message: `Custom source "${source.id}" tried to add tool "${toolId}" that was not in candidates; ignored.`,
|
|
224
|
+
source: `custom.${source.id}`,
|
|
225
|
+
});
|
|
226
|
+
refinedSet.delete(toolId);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
candidates = before.filter((id) => refinedSet.has(id));
|
|
230
|
+
// Index the source's emitted block decisions by featureId so we
|
|
231
|
+
// can detect "silent removals" — tools the source dropped from
|
|
232
|
+
// the candidate set without logging an explicit block. Without
|
|
233
|
+
// this auto-log, the provenance trail for a silently-removed
|
|
234
|
+
// tool would show only the prior `enable` decisions and the
|
|
235
|
+
// final-state reconciliation would have no evidence to mark it
|
|
236
|
+
// `blocked` (M8 PR 1 R2 S1 / M1 case B).
|
|
237
|
+
const explicitBlocks = new Set();
|
|
238
|
+
if (result.decisions) {
|
|
239
|
+
for (const event of result.decisions) {
|
|
240
|
+
builder.addDecision({
|
|
241
|
+
precedence: event.precedence ?? 100 + i,
|
|
242
|
+
rule: event.rule,
|
|
243
|
+
featureId: event.featureId,
|
|
244
|
+
action: event.action,
|
|
245
|
+
sourceType: event.sourceType,
|
|
246
|
+
reason: event.reason,
|
|
247
|
+
value: event.value,
|
|
248
|
+
});
|
|
249
|
+
if (event.action === "block") {
|
|
250
|
+
explicitBlocks.add(event.featureId);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Auto-log a `custom-source / block` decision for silent removals.
|
|
255
|
+
for (const toolId of before) {
|
|
256
|
+
if (refinedSet.has(toolId))
|
|
257
|
+
continue;
|
|
258
|
+
if (explicitBlocks.has(toolId))
|
|
259
|
+
continue;
|
|
260
|
+
builder.addDecision({
|
|
261
|
+
precedence: 100 + i,
|
|
262
|
+
rule: "custom-source",
|
|
263
|
+
featureId: toolId,
|
|
264
|
+
action: "block",
|
|
265
|
+
sourceType: "custom",
|
|
266
|
+
reason: `Custom policy source "${source.id}" removed tool "${toolId}" from candidates without emitting a decision; auto-logged for trail completeness.`,
|
|
267
|
+
value: { customSourceId: source.id },
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Annotate surviving entries with `custom.<id>` so hosts can see
|
|
271
|
+
// which custom source kept them in.
|
|
272
|
+
for (const toolId of candidates) {
|
|
273
|
+
const tags = sourcesByTool.get(toolId) ?? ["placement"];
|
|
274
|
+
tags.push(`custom.${source.id}`);
|
|
275
|
+
sourcesByTool.set(toolId, tags);
|
|
276
|
+
}
|
|
277
|
+
// And drop attribution for IDs the source removed.
|
|
278
|
+
for (const toolId of before) {
|
|
279
|
+
if (!refinedSet.has(toolId)) {
|
|
280
|
+
sourcesByTool.delete(toolId);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const survivingIds = new Set(candidates);
|
|
285
|
+
const visibleTools = candidates.map((toolId) => {
|
|
286
|
+
const flag = qtiResult?.perToolFlags.get(toolId);
|
|
287
|
+
return {
|
|
288
|
+
toolId,
|
|
289
|
+
required: flag?.required ?? false,
|
|
290
|
+
alwaysAvailable: flag?.alwaysAvailable ?? false,
|
|
291
|
+
settings: flag?.settings,
|
|
292
|
+
sources: sourcesByTool.get(toolId) ?? ["placement"],
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
builder.reconcileFinalStates(survivingIds);
|
|
296
|
+
return {
|
|
297
|
+
visibleTools,
|
|
298
|
+
diagnostics,
|
|
299
|
+
provenance: builder.build(),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Identify which step-1-through-4 host gate removed a tool. Used by
|
|
304
|
+
* step 5b to enrich `tool-policy.qtiRequiredBlocked` diagnostics. The
|
|
305
|
+
* checks mirror the pipeline order — step 1 (placement) first, step 4
|
|
306
|
+
* (blocklist) last — so the FIRST gate that would have matched wins,
|
|
307
|
+
* which is the gate the pipeline actually invoked. Subsequent gates
|
|
308
|
+
* never get to evaluate a tool that an earlier step already removed.
|
|
309
|
+
*/
|
|
310
|
+
function detectHostRemovalGate(toolId, args) {
|
|
311
|
+
if (!args.placement.includes(toolId)) {
|
|
312
|
+
return { hostRule: "placement-missing", hostValue: args.placement };
|
|
313
|
+
}
|
|
314
|
+
if (args.providers?.[toolId]?.enabled === false) {
|
|
315
|
+
return { hostRule: "provider-disabled", hostValue: false };
|
|
316
|
+
}
|
|
317
|
+
if (args.allowed.length > 0 && !args.allowed.includes(toolId)) {
|
|
318
|
+
return { hostRule: "host-allowlist", hostValue: args.allowed };
|
|
319
|
+
}
|
|
320
|
+
if (args.blocked.has(toolId)) {
|
|
321
|
+
return {
|
|
322
|
+
hostRule: "host-blocked",
|
|
323
|
+
hostValue: Array.from(args.blocked),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// Defensive: `removedByHost` is true at the call site (the tool is
|
|
327
|
+
// in `mandatedToolIds` but not in `postHostCandidates`), so one of
|
|
328
|
+
// the four gates above MUST have fired. If none did, the host
|
|
329
|
+
// pipeline has a bug; surface a best-effort rather than throwing.
|
|
330
|
+
return { hostRule: "placement-missing", hostValue: args.placement };
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=compose-decision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-decision.js","sourceRoot":"","sources":["../../../src/policy/core/compose-decision.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Policy Engine — decision input/output types.
|
|
3
|
+
*
|
|
4
|
+
* These types describe the *request/response* contract for
|
|
5
|
+
* `ToolPolicyEngine.decide(...)`. The engine itself, plus the
|
|
6
|
+
* composition pipeline that produces the response, lives in sibling
|
|
7
|
+
* modules.
|
|
8
|
+
*
|
|
9
|
+
* See `.cursor/plans/m8-design.md` § 2 for the engine shape, § 3 for
|
|
10
|
+
* the composition rule, and § 4 for the provenance contract.
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolPlacementLevel } from "../../services/tools-config-normalizer.js";
|
|
13
|
+
import type { ToolContext, ToolLevel } from "../../services/tool-context.js";
|
|
14
|
+
import type { PolicySourceTag } from "./policy-source-tag.js";
|
|
15
|
+
import type { ToolPolicyProvenance } from "./provenance.js";
|
|
16
|
+
/**
|
|
17
|
+
* Scope identifier for a `decide(...)` request. The engine uses
|
|
18
|
+
* `level` to pick which placement bucket to read; `scopeId` is opaque
|
|
19
|
+
* to the engine (it is round-tripped into provenance for human-readable
|
|
20
|
+
* trails) and is typically the section-id, item-id, or passage-id of
|
|
21
|
+
* the surface asking the question. `contentKind` mirrors the
|
|
22
|
+
* `ToolbarContext.scope.contentKind` field already in use by
|
|
23
|
+
* `<pie-item-toolbar>` Pass-2 visibility.
|
|
24
|
+
*/
|
|
25
|
+
export interface ToolScope {
|
|
26
|
+
level: ToolLevel;
|
|
27
|
+
scopeId: string;
|
|
28
|
+
assessmentId?: string;
|
|
29
|
+
sectionId?: string;
|
|
30
|
+
itemId?: string;
|
|
31
|
+
canonicalItemId?: string;
|
|
32
|
+
contentKind?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ToolPolicyDecisionRequest {
|
|
35
|
+
/**
|
|
36
|
+
* Placement bucket the engine reads from
|
|
37
|
+
* (`tools.placement[level]`). Stays narrow (`section | item |
|
|
38
|
+
* passage`) — broader `ToolLevel` values from `tool-context.ts`
|
|
39
|
+
* are surfaced through `scope.level` only.
|
|
40
|
+
*/
|
|
41
|
+
level: ToolPlacementLevel;
|
|
42
|
+
scope: ToolScope;
|
|
43
|
+
/**
|
|
44
|
+
* Optional Pass-2 context. The engine itself does NOT call
|
|
45
|
+
* `isVisibleInContext(...)`; that gate is intentionally applied
|
|
46
|
+
* at the toolbar boundary (Pass-2 = relevance, Pass-1 = the
|
|
47
|
+
* engine's job). The context is forwarded to custom
|
|
48
|
+
* `PolicySource` instances so they can refine the candidate set
|
|
49
|
+
* if they need to.
|
|
50
|
+
*/
|
|
51
|
+
context?: ToolContext;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Diagnostic codes the engine may emit alongside a decision. The
|
|
55
|
+
* canonical case in M8 is `tool-policy.qtiRequiredBlocked` — fired
|
|
56
|
+
* when a host policy gate (`policy.blocked`, missing-from-`placement`,
|
|
57
|
+
* provider veto) removed a tool that QTI's `requiredTools` /
|
|
58
|
+
* `district.requiredTools` mandates.
|
|
59
|
+
*
|
|
60
|
+
* `tool-policy.placementMissing` fires when a custom `PolicySource`
|
|
61
|
+
* references a tool ID that is not present in `tools.placement[level]`
|
|
62
|
+
* for the resolved level. The host-side `ToolConfigDiagnostic` channel
|
|
63
|
+
* (already used by `tool-config-validation.ts`) covers config-time
|
|
64
|
+
* misconfiguration; this channel covers per-decision conflicts.
|
|
65
|
+
*/
|
|
66
|
+
export type ToolPolicyDiagnosticCode = "tool-policy.qtiRequiredBlocked" | "tool-policy.placementMissing";
|
|
67
|
+
/**
|
|
68
|
+
* Which host gate removed a QTI-mandated tool. Surfaced inside
|
|
69
|
+
* `ToolPolicyDiagnostic.details` for `tool-policy.qtiRequiredBlocked`
|
|
70
|
+
* so consumers can render a single human-readable explanation
|
|
71
|
+
* ("the proctor blocked the calculator that this student's IEP
|
|
72
|
+
* requires") without re-deriving the host gate from
|
|
73
|
+
* `provenance.features[toolId].allDecisions`.
|
|
74
|
+
*
|
|
75
|
+
* The four values mirror steps 1–4 of `composeDecision`:
|
|
76
|
+
* - `placement-missing` — tool is not in `tools.placement[level]`
|
|
77
|
+
* - `provider-disabled` — `tools.providers[id].enabled === false`
|
|
78
|
+
* - `host-allowlist` — non-empty `tools.policy.allowed` excludes the id
|
|
79
|
+
* - `host-blocked` — `tools.policy.blocked` lists the id
|
|
80
|
+
*/
|
|
81
|
+
export type ToolPolicyHostGate = "placement-missing" | "provider-disabled" | "host-allowlist" | "host-blocked";
|
|
82
|
+
/**
|
|
83
|
+
* Strongly-typed payload for `tool-policy.qtiRequiredBlocked`
|
|
84
|
+
* diagnostics. The diagnostic's `details` field still types as
|
|
85
|
+
* `Record<string, unknown>` for forward compatibility, but engines
|
|
86
|
+
* always populate this exact shape today and consumers can safely
|
|
87
|
+
* cast.
|
|
88
|
+
*/
|
|
89
|
+
export interface QtiRequiredBlockedDetails extends Record<string, unknown> {
|
|
90
|
+
/** The QTI rule that mandated the tool (e.g. `district-requirement`). */
|
|
91
|
+
rule: string;
|
|
92
|
+
/** Which host gate removed the tool. */
|
|
93
|
+
hostRule: ToolPolicyHostGate;
|
|
94
|
+
/** The host configuration value that triggered the gate (best-effort, may be omitted). */
|
|
95
|
+
hostValue?: unknown;
|
|
96
|
+
}
|
|
97
|
+
export interface ToolPolicyDiagnostic {
|
|
98
|
+
code: ToolPolicyDiagnosticCode;
|
|
99
|
+
level: ToolPlacementLevel;
|
|
100
|
+
toolId: string;
|
|
101
|
+
message: string;
|
|
102
|
+
source?: PolicySourceTag;
|
|
103
|
+
details?: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
export interface ToolPolicyEntry {
|
|
106
|
+
toolId: string;
|
|
107
|
+
/**
|
|
108
|
+
* `true` when QTI mandates this tool (item or district
|
|
109
|
+
* `requiredTools`, or PNP support without prohibition). Advisory
|
|
110
|
+
* mandates that the host blocked do *not* surface here — they
|
|
111
|
+
* appear in `diagnostics` instead. Hosts that need to know about
|
|
112
|
+
* those should listen on the diagnostic channel.
|
|
113
|
+
*/
|
|
114
|
+
required: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* `true` for PNP `supports` — UI-level signal that the host
|
|
117
|
+
* cannot toggle this tool off in user preferences. Does NOT
|
|
118
|
+
* override host blocks (they would have removed the entry
|
|
119
|
+
* before this flag is read).
|
|
120
|
+
*/
|
|
121
|
+
alwaysAvailable: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Tool-specific settings resolved from the assessment / item
|
|
124
|
+
* settings via the QTI source. Hosts that want to read these
|
|
125
|
+
* directly should use `entry.settings` rather than peeking into
|
|
126
|
+
* the assessment entity from above the engine.
|
|
127
|
+
*/
|
|
128
|
+
settings?: unknown;
|
|
129
|
+
/**
|
|
130
|
+
* Every contributor that helped this entry survive the
|
|
131
|
+
* composition pipeline. Always non-empty — at minimum
|
|
132
|
+
* `["placement"]` for any entry that came from
|
|
133
|
+
* `tools.placement[level]`.
|
|
134
|
+
*/
|
|
135
|
+
sources: PolicySourceTag[];
|
|
136
|
+
}
|
|
137
|
+
export interface ToolPolicyDecision {
|
|
138
|
+
visibleTools: ToolPolicyEntry[];
|
|
139
|
+
diagnostics: ToolPolicyDiagnostic[];
|
|
140
|
+
provenance: ToolPolicyProvenance;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=decision-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-types.d.ts","sourceRoot":"","sources":["../../../src/policy/core/decision-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2CAA2C,CAAC;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAAyB;IACzC;;;;;OAKG;IACH,KAAK,EAAE,kBAAkB,CAAC;IAC1B,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,wBAAwB,GACjC,gCAAgC,GAChC,8BAA8B,CAAC;AAElC;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,kBAAkB,GAC3B,mBAAmB,GACnB,mBAAmB,GACnB,gBAAgB,GAChB,cAAc,CAAC;AAElB;;;;;;GAMG;AACH,MAAM,WAAW,yBAA0B,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzE,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,0FAA0F;IAC1F,SAAS,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,KAAK,EAAE,kBAAkB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IAClC,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,UAAU,EAAE,oBAAoB,CAAC;CACjC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Policy Engine — decision input/output types.
|
|
3
|
+
*
|
|
4
|
+
* These types describe the *request/response* contract for
|
|
5
|
+
* `ToolPolicyEngine.decide(...)`. The engine itself, plus the
|
|
6
|
+
* composition pipeline that produces the response, lives in sibling
|
|
7
|
+
* modules.
|
|
8
|
+
*
|
|
9
|
+
* See `.cursor/plans/m8-design.md` § 2 for the engine shape, § 3 for
|
|
10
|
+
* the composition rule, and § 4 for the provenance contract.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=decision-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-types.js","sourceRoot":"","sources":["../../../src/policy/core/decision-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte context key for the tool policy engine (M8 PR 1).
|
|
3
|
+
*
|
|
4
|
+
* Components inside an assessment toolkit subtree consume the engine
|
|
5
|
+
* through Svelte's native `getContext(TOOL_POLICY_ENGINE_KEY)`. The
|
|
6
|
+
* canonical provider is the `ToolkitCoordinator` wiring (M8 PR 2):
|
|
7
|
+
* the toolkit CE constructs a `ToolPolicyEngine`, attaches it to the
|
|
8
|
+
* coordinator, and exposes it to descendants via
|
|
9
|
+
* `setContext(TOOL_POLICY_ENGINE_KEY, engine)`.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the M7 `SECTION_RUNTIME_ENGINE_KEY` pattern. As with the
|
|
12
|
+
* runtime engine, the key is a `Symbol` so it cannot collide with
|
|
13
|
+
* userland context keys and so descendants are forced to import this
|
|
14
|
+
* module rather than guess a string id.
|
|
15
|
+
*
|
|
16
|
+
* **No cross-CE bridge.** Unlike the runtime engine, there is no
|
|
17
|
+
* `connectToolPolicyEngineHostContext` helper — see M8 design Q8.
|
|
18
|
+
* Every CE in the tree gets its engine from its own coordinator
|
|
19
|
+
* (the toolkit element). This avoids subscription-bridge overhead
|
|
20
|
+
* for a function-style engine that hosts can simply re-call after
|
|
21
|
+
* an input swap.
|
|
22
|
+
*
|
|
23
|
+
* **Stability.** This export is part of the stable `policy/engine`
|
|
24
|
+
* surface; renaming or replacing the symbol is a major breaking
|
|
25
|
+
* change.
|
|
26
|
+
*/
|
|
27
|
+
import type { ToolPolicyEngine } from "./ToolPolicyEngine.js";
|
|
28
|
+
export declare const TOOL_POLICY_ENGINE_KEY: unique symbol;
|
|
29
|
+
export type ToolPolicyEngineContext = ToolPolicyEngine;
|
|
30
|
+
//# sourceMappingURL=engine-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-context.d.ts","sourceRoot":"","sources":["../../../src/policy/core/engine-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,eAAO,MAAM,sBAAsB,EAAE,OAAO,MAE3C,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte context key for the tool policy engine (M8 PR 1).
|
|
3
|
+
*
|
|
4
|
+
* Components inside an assessment toolkit subtree consume the engine
|
|
5
|
+
* through Svelte's native `getContext(TOOL_POLICY_ENGINE_KEY)`. The
|
|
6
|
+
* canonical provider is the `ToolkitCoordinator` wiring (M8 PR 2):
|
|
7
|
+
* the toolkit CE constructs a `ToolPolicyEngine`, attaches it to the
|
|
8
|
+
* coordinator, and exposes it to descendants via
|
|
9
|
+
* `setContext(TOOL_POLICY_ENGINE_KEY, engine)`.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors the M7 `SECTION_RUNTIME_ENGINE_KEY` pattern. As with the
|
|
12
|
+
* runtime engine, the key is a `Symbol` so it cannot collide with
|
|
13
|
+
* userland context keys and so descendants are forced to import this
|
|
14
|
+
* module rather than guess a string id.
|
|
15
|
+
*
|
|
16
|
+
* **No cross-CE bridge.** Unlike the runtime engine, there is no
|
|
17
|
+
* `connectToolPolicyEngineHostContext` helper — see M8 design Q8.
|
|
18
|
+
* Every CE in the tree gets its engine from its own coordinator
|
|
19
|
+
* (the toolkit element). This avoids subscription-bridge overhead
|
|
20
|
+
* for a function-style engine that hosts can simply re-call after
|
|
21
|
+
* an input swap.
|
|
22
|
+
*
|
|
23
|
+
* **Stability.** This export is part of the stable `policy/engine`
|
|
24
|
+
* surface; renaming or replacing the symbol is a major breaking
|
|
25
|
+
* change.
|
|
26
|
+
*/
|
|
27
|
+
export const TOOL_POLICY_ENGINE_KEY = Symbol("@pie-players/tool-policy-engine");
|
|
28
|
+
//# sourceMappingURL=engine-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-context.js","sourceRoot":"","sources":["../../../src/policy/core/engine-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,MAAM,CAAC,MAAM,sBAAsB,GAAkB,MAAM,CAC1D,iCAAiC,CACjC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy source tag — attribution for `ToolPolicyEntry.sources`.
|
|
3
|
+
*
|
|
4
|
+
* The engine attaches a `PolicySourceTag` to every `ToolPolicyEntry` it
|
|
5
|
+
* emits, capturing every step from `compose-decision.ts` that contributed
|
|
6
|
+
* to the entry being kept (or, in the case of `qti-required-blocked`,
|
|
7
|
+
* the step that surfaced the conflict).
|
|
8
|
+
*
|
|
9
|
+
* Tag vocabulary maps onto the six composition steps in
|
|
10
|
+
* `m8-design.md` § 3:
|
|
11
|
+
*
|
|
12
|
+
* 1. Membership filter → `"placement"`
|
|
13
|
+
* 2. Provider veto → `"provider"` (negative; only present when
|
|
14
|
+
* the entry survived a
|
|
15
|
+
* non-`enabled: false` check)
|
|
16
|
+
* 3. Host whitelist → `"policy"`
|
|
17
|
+
* 4. Host blocklist → `"policy"` (negative)
|
|
18
|
+
* 5. QTI gates → `"qti.${rule}"` (e.g. `"qti.pnp-support"`,
|
|
19
|
+
* `"qti.item-requirement"`)
|
|
20
|
+
* 6. Custom PolicySources → `"custom.${source.id}"`
|
|
21
|
+
*
|
|
22
|
+
* Negative tags do not appear on surviving entries — they are reflected
|
|
23
|
+
* only in `ToolPolicyProvenance` decisions. The tag union therefore
|
|
24
|
+
* tracks contributors that *kept* a tool, not contributors that
|
|
25
|
+
* *removed* one. Diagnostic surfacing of removals is the
|
|
26
|
+
* provenance/diagnostic channel's job.
|
|
27
|
+
*/
|
|
28
|
+
export type QtiPolicySourceRule = "district-block" | "test-admin-override" | "item-restriction" | "item-requirement" | "district-requirement" | "pnp-support" | "pnp-prohibited";
|
|
29
|
+
export type QtiPolicySourceTag = `qti.${QtiPolicySourceRule}`;
|
|
30
|
+
export type CustomPolicySourceTag = `custom.${string}`;
|
|
31
|
+
export type PolicySourceTag = "placement" | "policy" | "provider" | QtiPolicySourceTag | CustomPolicySourceTag;
|
|
32
|
+
//# sourceMappingURL=policy-source-tag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-source-tag.d.ts","sourceRoot":"","sources":["../../../src/policy/core/policy-source-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,MAAM,mBAAmB,GAC5B,gBAAgB,GAChB,qBAAqB,GACrB,kBAAkB,GAClB,kBAAkB,GAClB,sBAAsB,GACtB,aAAa,GACb,gBAAgB,CAAC;AAEpB,MAAM,MAAM,kBAAkB,GAAG,OAAO,mBAAmB,EAAE,CAAC;AAE9D,MAAM,MAAM,qBAAqB,GAAG,UAAU,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,eAAe,GACxB,WAAW,GACX,QAAQ,GACR,UAAU,GACV,kBAAkB,GAClB,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy source tag — attribution for `ToolPolicyEntry.sources`.
|
|
3
|
+
*
|
|
4
|
+
* The engine attaches a `PolicySourceTag` to every `ToolPolicyEntry` it
|
|
5
|
+
* emits, capturing every step from `compose-decision.ts` that contributed
|
|
6
|
+
* to the entry being kept (or, in the case of `qti-required-blocked`,
|
|
7
|
+
* the step that surfaced the conflict).
|
|
8
|
+
*
|
|
9
|
+
* Tag vocabulary maps onto the six composition steps in
|
|
10
|
+
* `m8-design.md` § 3:
|
|
11
|
+
*
|
|
12
|
+
* 1. Membership filter → `"placement"`
|
|
13
|
+
* 2. Provider veto → `"provider"` (negative; only present when
|
|
14
|
+
* the entry survived a
|
|
15
|
+
* non-`enabled: false` check)
|
|
16
|
+
* 3. Host whitelist → `"policy"`
|
|
17
|
+
* 4. Host blocklist → `"policy"` (negative)
|
|
18
|
+
* 5. QTI gates → `"qti.${rule}"` (e.g. `"qti.pnp-support"`,
|
|
19
|
+
* `"qti.item-requirement"`)
|
|
20
|
+
* 6. Custom PolicySources → `"custom.${source.id}"`
|
|
21
|
+
*
|
|
22
|
+
* Negative tags do not appear on surviving entries — they are reflected
|
|
23
|
+
* only in `ToolPolicyProvenance` decisions. The tag union therefore
|
|
24
|
+
* tracks contributors that *kept* a tool, not contributors that
|
|
25
|
+
* *removed* one. Diagnostic surfacing of removals is the
|
|
26
|
+
* provenance/diagnostic channel's job.
|
|
27
|
+
*/
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=policy-source-tag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-source-tag.js","sourceRoot":"","sources":["../../../src/policy/core/policy-source-tag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
|