@pie-players/pie-assessment-toolkit 0.3.74 → 1.0.0-beta.0
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 +658 -714
- package/custom-elements.json +1315 -0
- package/dist/attempt/TestSession.d.ts +1 -21
- package/dist/attempt/TestSession.js +16 -102
- package/dist/components/ItemScope.custom-element.d.ts +1 -0
- package/dist/components/ItemScope.custom-element.js +1 -0
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +14 -14
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-3zrc4p3d.js +4 -0
- package/dist/components/chunks/ItemToolBar-66hcwmfw.js +2 -0
- package/dist/components/chunks/ItemToolBar-9gqhfxtn.js +40 -0
- package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
- package/dist/components/chunks/{ItemToolBar-gmrryv2v.js → ItemToolBar-kkj69ptr.js} +1 -1
- package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
- package/dist/components/chunks/sre-engine-bj997w4b.js +1 -0
- package/dist/components/item-scope-element.d.ts +2 -0
- package/dist/components/item-scope-element.js +2 -0
- package/dist/context/assessment-toolkit-context.d.ts +33 -6
- package/dist/index.d.ts +25 -50
- package/dist/index.js +15 -45
- package/dist/policy/core/PolicySource.d.ts +2 -2
- package/dist/policy/core/ToolPolicyEngine.d.ts +118 -34
- package/dist/policy/core/ToolPolicyEngine.js +203 -56
- package/dist/policy/core/compose-decision.d.ts +28 -7
- package/dist/policy/core/compose-decision.js +95 -74
- package/dist/policy/core/decision-types.d.ts +98 -55
- package/dist/policy/core/feature-decision.d.ts +28 -19
- package/dist/policy/core/feature-decision.js +15 -11
- package/dist/policy/core/pnp-policy-inputs.d.ts +29 -35
- package/dist/policy/core/pnp-policy-inputs.js +40 -88
- package/dist/policy/core/provenance.d.ts +10 -5
- package/dist/policy/core/provenance.js +1 -1
- package/dist/policy/core/tool-parameters.d.ts +11 -0
- package/dist/policy/core/tool-parameters.js +15 -0
- package/dist/policy/engine.d.ts +7 -23
- package/dist/policy/engine.js +5 -20
- package/dist/policy/sources/PnpPolicySource.d.ts +43 -68
- package/dist/policy/sources/PnpPolicySource.js +133 -132
- package/dist/runtime/SectionControllerBinding.d.ts +283 -0
- package/dist/runtime/SectionControllerBinding.js +456 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
- package/dist/runtime/SectionRuntimeEngine.js +30 -372
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
- package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
- package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
- package/dist/runtime/adapter/dom-event-bridge.js +17 -44
- package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -3
- package/dist/runtime/adapter/subscriber-fanout.js +2 -6
- package/dist/runtime/catalog-context.d.ts +19 -0
- package/dist/runtime/catalog-context.js +28 -0
- package/dist/runtime/composition-emit-scheduler.d.ts +12 -18
- package/dist/runtime/composition-emit-scheduler.js +10 -16
- package/dist/runtime/content-language.d.ts +32 -0
- package/dist/runtime/content-language.js +46 -0
- package/dist/runtime/content-region.d.ts +13 -0
- package/dist/runtime/content-region.js +29 -0
- package/dist/runtime/content-registration.d.ts +13 -0
- package/dist/runtime/content-registration.js +25 -0
- package/dist/runtime/core/SectionEngineCore.d.ts +2 -4
- package/dist/runtime/core/SectionEngineCore.js +2 -6
- package/dist/runtime/core/cohort.d.ts +1 -6
- package/dist/runtime/core/cohort.js +1 -10
- package/dist/runtime/core/engine-input.d.ts +18 -30
- package/dist/runtime/core/engine-input.js +11 -12
- package/dist/runtime/core/engine-output.d.ts +17 -40
- package/dist/runtime/core/engine-output.js +9 -21
- package/dist/runtime/core/engine-readiness.d.ts +5 -37
- package/dist/runtime/core/engine-readiness.js +8 -41
- package/dist/runtime/core/engine-resolver.d.ts +46 -74
- package/dist/runtime/core/engine-resolver.js +18 -43
- package/dist/runtime/core/engine-state.d.ts +28 -36
- package/dist/runtime/core/engine-state.js +18 -21
- package/dist/runtime/core/engine-transition.d.ts +14 -19
- package/dist/runtime/core/engine-transition.js +74 -53
- package/dist/runtime/engine.d.ts +16 -19
- package/dist/runtime/engine.js +12 -19
- package/dist/runtime/page-warnings.d.ts +13 -0
- package/dist/runtime/page-warnings.js +22 -0
- package/dist/runtime/policy-input-forwarding.d.ts +25 -0
- package/dist/runtime/policy-input-forwarding.js +25 -0
- package/dist/runtime/registration-events.d.ts +22 -14
- package/dist/runtime/runtime-event-claim.d.ts +8 -0
- package/dist/runtime/runtime-event-claim.js +15 -0
- package/dist/runtime/runtime-id.d.ts +1 -1
- package/dist/runtime/runtime-id.js +1 -1
- package/dist/runtime/shell-event-bridge.d.ts +45 -0
- package/dist/runtime/shell-event-bridge.js +127 -0
- package/dist/runtime/shell-registration.d.ts +73 -0
- package/dist/runtime/shell-registration.js +95 -0
- package/dist/runtime/shell-scope.d.ts +68 -0
- package/dist/runtime/shell-scope.js +194 -0
- package/dist/runtime/tool-host-contract.d.ts +8 -14
- package/dist/runtime/tool-host-contract.js +12 -12
- package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
- package/dist/runtime/unclaimed-registration-watch.js +23 -0
- package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
- package/dist/services/AccessibilityCatalogResolver.js +29 -42
- package/dist/services/ElementToolStateStore.d.ts +42 -67
- package/dist/services/ElementToolStateStore.js +65 -71
- package/dist/services/HighlightCoordinator.d.ts +28 -40
- package/dist/services/HighlightCoordinator.js +459 -381
- package/dist/services/RangeSerializer.d.ts +11 -33
- package/dist/services/RangeSerializer.js +88 -59
- package/dist/services/SSMLExtractor.d.ts +8 -31
- package/dist/services/SSMLExtractor.js +18 -40
- package/dist/services/TTSService.d.ts +159 -119
- package/dist/services/TTSService.js +937 -822
- package/dist/services/ToolCoordinator.d.ts +42 -102
- package/dist/services/ToolCoordinator.js +178 -256
- package/dist/services/ToolRegistry.d.ts +152 -133
- package/dist/services/ToolRegistry.js +192 -178
- package/dist/services/ToolkitCoordinator.d.ts +233 -237
- package/dist/services/ToolkitCoordinator.js +615 -557
- package/dist/services/audio-handoff.d.ts +6 -7
- package/dist/services/audio-handoff.js +20 -14
- package/dist/services/catalog-owner.d.ts +0 -1
- package/dist/services/framework-error-bus.d.ts +3 -6
- package/dist/services/framework-error-bus.js +3 -6
- package/dist/services/framework-error.d.ts +51 -13
- package/dist/services/framework-error.js +19 -20
- package/dist/services/interfaces.d.ts +189 -184
- package/dist/services/section-controller-types.d.ts +7 -7
- package/dist/services/section-session-assignment.d.ts +21 -0
- package/dist/services/section-session-assignment.js +80 -0
- package/dist/services/spoken-audio-cards.d.ts +2 -6
- package/dist/services/spoken-audio-cards.js +2 -6
- package/dist/services/tool-config-defaults.d.ts +3 -3
- package/dist/services/tool-config-defaults.js +3 -3
- package/dist/services/tool-config-validation.d.ts +9 -12
- package/dist/services/tool-config-validation.js +47 -87
- package/dist/services/tool-context.d.ts +7 -31
- package/dist/services/tool-context.js +99 -65
- package/dist/services/tool-failure.d.ts +30 -0
- package/dist/services/tool-failure.js +11 -0
- package/dist/services/tool-icons.d.ts +5 -5
- package/dist/services/tool-icons.js +6 -5
- package/dist/services/tool-instance-id.d.ts +8 -7
- package/dist/services/tool-instance-id.js +12 -24
- package/dist/services/tool-providers/TTSToolProvider.d.ts +20 -58
- package/dist/services/tool-providers/TTSToolProvider.js +32 -85
- package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -11
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +29 -60
- package/dist/services/tool-providers/ToolProviderRegistry.js +128 -144
- package/dist/services/tool-request.d.ts +31 -12
- package/dist/services/tool-request.js +31 -16
- package/dist/services/toolbar-decided-tools.d.ts +16 -0
- package/dist/services/toolbar-decided-tools.js +29 -0
- package/dist/services/toolbar-items.js +2 -5
- package/dist/services/tools-config-normalizer.d.ts +33 -29
- package/dist/services/tools-config-normalizer.js +26 -28
- package/dist/services/tts/browser-provider.d.ts +39 -6
- package/dist/services/tts/browser-provider.js +280 -160
- package/dist/services/tts/catalog-span-alignment.js +14 -7
- package/dist/services/tts/flat-tree.d.ts +64 -0
- package/dist/services/tts/flat-tree.js +269 -0
- package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
- package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
- package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
- package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +2 -2
- package/dist/services/tts/generated-speech/index.d.ts +1 -1
- package/dist/services/tts/generated-speech/index.js +1 -1
- package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
- package/dist/services/tts/generated-speech/types.d.ts +1 -1
- package/dist/services/tts/generated-speech/types.js +1 -1
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +22 -16
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
- package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
- package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
- package/dist/services/tts/highlight-target-resolver.d.ts +12 -0
- package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
- package/dist/services/tts/math-aware-text-processing.js +42 -47
- package/dist/services/tts/math-control-names.d.ts +21 -0
- package/dist/services/tts/math-control-names.js +142 -0
- package/dist/services/tts/math-speech.d.ts +12 -1
- package/dist/services/tts/math-speech.js +54 -18
- package/dist/services/tts/mathml-sanitization.d.ts +7 -0
- package/dist/services/tts/mathml-sanitization.js +9 -1
- package/dist/services/tts/provider-options.d.ts +52 -0
- package/dist/services/tts/provider-options.js +1 -0
- package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
- package/dist/services/tts/ssml/spoken-text.js +29 -0
- package/dist/services/tts/start-failure.d.ts +4 -0
- package/dist/services/tts/start-failure.js +19 -0
- package/dist/services/tts/text-processing.d.ts +9 -0
- package/dist/services/tts/text-processing.js +46 -33
- package/dist/services/tts/text-segmentation.d.ts +12 -0
- package/dist/services/tts/text-segmentation.js +60 -10
- package/dist/services/tts-runtime-config.d.ts +54 -26
- package/dist/services/tts-runtime-config.js +27 -112
- package/dist/tools/content-capability-resolution.d.ts +9 -11
- package/dist/tools/content-capability-resolution.js +8 -45
- package/dist/tools/registration.d.ts +59 -0
- package/dist/tools/registration.js +55 -0
- package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +8 -2
- package/dist/tools/tool-surface-host.d.ts +6 -10
- package/dist/tools/tool-surface-host.js +45 -50
- package/dist/tools/tool-tag-map.d.ts +5 -15
- package/dist/tools/tool-tag-map.js +6 -20
- package/dist/utils/structural-equality.d.ts +11 -0
- package/dist/utils/structural-equality.js +47 -0
- package/package.json +15 -29
- package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
- package/dist/components/chunks/ItemToolBar-b9dz5jvd.js +0 -40
- package/dist/components/chunks/ItemToolBar-h4xap0t0.js +0 -4
- package/dist/components/chunks/ItemToolBar-wr2myaah.js +0 -2
- package/dist/components/chunks/sre-engine-wf9s43gg.js +0 -1
- package/dist/context/runtime-context-consumer.d.ts +0 -13
- package/dist/context/runtime-context-consumer.js +0 -18
- package/dist/item-loader.d.ts +0 -44
- package/dist/item-loader.js +0 -61
- package/dist/policy/core/engine-context.d.ts +0 -29
- package/dist/policy/core/engine-context.js +0 -27
- package/dist/policy/core/policy-source-tag.d.ts +0 -31
- package/dist/policy/core/policy-source-tag.js +0 -28
- package/dist/policy/internal.d.ts +0 -25
- package/dist/policy/internal.js +0 -25
- package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
- package/dist/runtime/adapter/coordinator-bridge.js +0 -126
- package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
- package/dist/runtime/adapter/framework-error-bridge.js +0 -46
- package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
- package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
- package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
- package/dist/runtime/core/engine-stage-derivation.js +0 -29
- package/dist/runtime/engine-context.d.ts +0 -39
- package/dist/runtime/engine-context.js +0 -31
- package/dist/runtime/internal.d.ts +0 -43
- package/dist/runtime/internal.js +0 -45
- package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -74
- package/dist/runtime/section-runtime-engine-host-context.js +0 -57
- package/dist/runtime/stage-emit-gate.d.ts +0 -92
- package/dist/runtime/stage-emit-gate.js +0 -82
- package/dist/services/ContextVariableStore.d.ts +0 -113
- package/dist/services/ContextVariableStore.js +0 -206
- package/dist/services/I18nService.d.ts +0 -47
- package/dist/services/I18nService.js +0 -84
- package/dist/services/ThemeProvider.d.ts +0 -95
- package/dist/services/ThemeProvider.js +0 -334
- package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
- package/dist/services/defaultPersonalNeedsProfile.js +0 -26
- package/dist/services/pnp-standard-features.d.ts +0 -238
- package/dist/services/pnp-standard-features.js +0 -338
- package/dist/services/tts-control-events.d.ts +0 -13
- package/dist/services/tts-control-events.js +0 -7
- package/dist/tools/client.d.ts +0 -10
- package/dist/tools/client.js +0 -10
- package/dist/tools/internal.d.ts +0 -44
- package/dist/tools/internal.js +0 -39
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.js +0 -10
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* `tests/policy/compose-decision.test.ts` lock the orchestration in.
|
|
13
13
|
*/
|
|
14
14
|
import { normalizeToolList, } from "../../services/tools-config-normalizer.js";
|
|
15
|
-
import {
|
|
15
|
+
import { resolveToolParameters } from "./tool-parameters.js";
|
|
16
|
+
import { ToolPolicyProvenanceBuilder, } from "./provenance.js";
|
|
16
17
|
export function composeDecision(inputs) {
|
|
17
18
|
const { request, tools, pnpPolicy, customSources, contextId } = inputs;
|
|
18
19
|
const builder = new ToolPolicyProvenanceBuilder(contextId);
|
|
@@ -66,7 +67,7 @@ export function composeDecision(inputs) {
|
|
|
66
67
|
}
|
|
67
68
|
return true;
|
|
68
69
|
});
|
|
69
|
-
// Step 3 — Host
|
|
70
|
+
// Step 3 — Host allowlist (only when non-empty).
|
|
70
71
|
if (allowed.length > 0) {
|
|
71
72
|
const allowedSet = new Set(allowed);
|
|
72
73
|
candidates = candidates.filter((toolId) => {
|
|
@@ -103,23 +104,16 @@ export function composeDecision(inputs) {
|
|
|
103
104
|
});
|
|
104
105
|
// Step 5 — PNP/profile policy gates.
|
|
105
106
|
const diagnostics = [];
|
|
106
|
-
|
|
107
|
-
for
|
|
108
|
-
sourcesByTool.set(toolId, ["placement"]);
|
|
109
|
-
}
|
|
110
|
-
// Snapshot the post-host candidate set so step 5b can distinguish
|
|
111
|
-
// "host removed a required tool" (requiredToolBlocked fires)
|
|
112
|
-
// from "profile policy's own precedence removed a required tool" (fix
|
|
113
|
-
// per M8 PR 1 R1 S1 — multi-rule conflict on the same tool must
|
|
114
|
-
// not blame the host).
|
|
107
|
+
// Snapshot the post-host candidate set so step 5b reports
|
|
108
|
+
// requiredToolBlocked only for a mandated tool the host removed.
|
|
115
109
|
const postHostCandidates = new Set(candidates);
|
|
116
110
|
let pnpPolicyResult = null;
|
|
117
111
|
if (pnpPolicy.enforcement === "on" &&
|
|
118
112
|
pnpPolicy.source &&
|
|
119
|
-
(pnpPolicy.assessment || pnpPolicy.
|
|
113
|
+
(pnpPolicy.assessment || pnpPolicy.item)) {
|
|
120
114
|
pnpPolicyResult = pnpPolicy.source.apply({
|
|
121
115
|
assessment: pnpPolicy.assessment,
|
|
122
|
-
|
|
116
|
+
item: pnpPolicy.item,
|
|
123
117
|
});
|
|
124
118
|
if (pnpPolicyResult.sources.assessment) {
|
|
125
119
|
builder.addSource("assessment", pnpPolicyResult.sources.assessment);
|
|
@@ -141,29 +135,12 @@ export function composeDecision(inputs) {
|
|
|
141
135
|
value: decision.value,
|
|
142
136
|
});
|
|
143
137
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// failure a host sending its own vocabulary instead of an AfA/QTI feature
|
|
147
|
-
// id actually hits. Reported per decision rather than at config time
|
|
148
|
-
// because the ids arrive with the profile, not with the tools config.
|
|
149
|
-
for (const supportId of pnpPolicyResult.unmappedSupportIds) {
|
|
150
|
-
diagnostics.push({
|
|
151
|
-
code: "tool-policy.unknownSupportId",
|
|
152
|
-
level: request.level,
|
|
153
|
-
toolId: supportId,
|
|
154
|
-
message: `No registered tool claims PNP support id "${supportId}"; it was carried through as a feature id and matched nothing. Expected an AfA 3.0 / QTI 3.0 access feature id.`,
|
|
155
|
-
source: "pnp.pnp-support",
|
|
156
|
-
});
|
|
138
|
+
for (const [toolId, rule] of pnpPolicyResult.blockedOverrides) {
|
|
139
|
+
diagnostics.push(overrideBlockedDiagnostic(toolId, rule, request.level));
|
|
157
140
|
}
|
|
158
141
|
// 5a — remove PNP/profile-blocked tools from the candidate set.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
162
|
-
for (const toolId of Array.from(sourcesByTool.keys())) {
|
|
163
|
-
if (pnpPolicyResult.blockedToolIds.has(toolId)) {
|
|
164
|
-
sourcesByTool.delete(toolId);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
142
|
+
const { blockedToolIds } = pnpPolicyResult;
|
|
143
|
+
candidates = candidates.filter((toolId) => !blockedToolIds.has(toolId));
|
|
167
144
|
// 5b — surface requiredToolBlocked diagnostics ONLY for
|
|
168
145
|
// PNP/profile-mandated tools that the host removed (i.e., the tool was
|
|
169
146
|
// NOT in `postHostCandidates`). Mandated tools removed by
|
|
@@ -171,20 +148,24 @@ export function composeDecision(inputs) {
|
|
|
171
148
|
// blocking a `requiredTools` mandate) are an internal policy
|
|
172
149
|
// resolution and must not be reported as a host conflict — the
|
|
173
150
|
// loser's `block` decision is already in the trail with full
|
|
174
|
-
// provenance.
|
|
151
|
+
// provenance. A mandate names no level, so a tool placed at another
|
|
152
|
+
// level is served there: only placement at no level is a removal.
|
|
175
153
|
//
|
|
176
154
|
// `details` carries `{ rule, hostRule, hostValue }` so consumers
|
|
177
155
|
// can render the conflict as a single sentence without
|
|
178
156
|
// re-deriving the host gate from `provenance.features`. See
|
|
179
157
|
// `RequiredToolBlockedDetails` for the typed shape.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
158
|
+
const placedAtAnyLevel = new Set(Object.values(tools.placement).flatMap(normalizeToolList));
|
|
159
|
+
for (const [mandatedToolId, ruleName] of pnpPolicyResult.mandates) {
|
|
160
|
+
if (postHostCandidates.has(mandatedToolId))
|
|
183
161
|
continue;
|
|
184
|
-
|
|
185
|
-
|
|
162
|
+
if (!placement.includes(mandatedToolId) &&
|
|
163
|
+
placedAtAnyLevel.has(mandatedToolId)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
186
166
|
const hostGate = detectHostRemovalGate(mandatedToolId, {
|
|
187
|
-
|
|
167
|
+
placedAtAnyLevel,
|
|
168
|
+
allTools: tools.placement,
|
|
188
169
|
allowed,
|
|
189
170
|
blocked,
|
|
190
171
|
providers: tools.providers,
|
|
@@ -198,7 +179,9 @@ export function composeDecision(inputs) {
|
|
|
198
179
|
code: "tool-policy.requiredToolBlocked",
|
|
199
180
|
level: request.level,
|
|
200
181
|
toolId: mandatedToolId,
|
|
201
|
-
message:
|
|
182
|
+
message: hostGate.hostRule === "placement-missing"
|
|
183
|
+
? `Profile policy mandates "${mandatedToolId}" (${ruleName}), but no level of tools.placement lists it.`
|
|
184
|
+
: `Profile policy mandates "${mandatedToolId}" (${ruleName}), but host policy removes it via ${hostGate.hostRule}.`,
|
|
202
185
|
details,
|
|
203
186
|
});
|
|
204
187
|
builder.addDecision({
|
|
@@ -211,15 +194,6 @@ export function composeDecision(inputs) {
|
|
|
211
194
|
value: details,
|
|
212
195
|
});
|
|
213
196
|
}
|
|
214
|
-
// 5c — attach PNP policy source tags to surviving entries.
|
|
215
|
-
for (const toolId of candidates) {
|
|
216
|
-
const flag = pnpPolicyResult.perToolFlags.get(toolId);
|
|
217
|
-
if (flag) {
|
|
218
|
-
const tags = sourcesByTool.get(toolId) ?? [];
|
|
219
|
-
tags.push(`pnp.${flag.rule}`);
|
|
220
|
-
sourcesByTool.set(toolId, tags);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
197
|
}
|
|
224
198
|
// Step 6 — Custom PolicySources.
|
|
225
199
|
for (let i = 0; i < customSources.length; i++) {
|
|
@@ -238,7 +212,7 @@ export function composeDecision(inputs) {
|
|
|
238
212
|
level: request.level,
|
|
239
213
|
toolId,
|
|
240
214
|
message: `Custom source "${source.id}" tried to add tool "${toolId}" that was not in candidates; ignored.`,
|
|
241
|
-
|
|
215
|
+
details: { customSourceId: source.id },
|
|
242
216
|
});
|
|
243
217
|
refinedSet.delete(toolId);
|
|
244
218
|
}
|
|
@@ -250,7 +224,7 @@ export function composeDecision(inputs) {
|
|
|
250
224
|
// this auto-log, the provenance trail for a silently-removed
|
|
251
225
|
// tool would show only the prior `enable` decisions and the
|
|
252
226
|
// final-state reconciliation would have no evidence to mark it
|
|
253
|
-
// `blocked
|
|
227
|
+
// `blocked`.
|
|
254
228
|
const explicitBlocks = new Set();
|
|
255
229
|
if (result.decisions) {
|
|
256
230
|
for (const event of result.decisions) {
|
|
@@ -284,18 +258,27 @@ export function composeDecision(inputs) {
|
|
|
284
258
|
value: { customSourceId: source.id },
|
|
285
259
|
});
|
|
286
260
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
261
|
+
}
|
|
262
|
+
// Step 7 — item settings a shared toolbar does not apply. Placement is the
|
|
263
|
+
// host's section-wide choice, so the tool stays; the diagnostic says where to
|
|
264
|
+
// place it for the item setting to hold.
|
|
265
|
+
for (const toolId of candidates) {
|
|
266
|
+
for (const item of inputs.unappliedItems ?? []) {
|
|
267
|
+
const settings = ["restrictedTools", "requiredTools"].filter((key) => item.settings[key]?.includes(toolId) === true);
|
|
268
|
+
if (settings.length === 0)
|
|
269
|
+
continue;
|
|
270
|
+
const details = {
|
|
271
|
+
itemId: item.id,
|
|
272
|
+
settings,
|
|
273
|
+
toolbarLevel: request.scope.level,
|
|
274
|
+
};
|
|
275
|
+
diagnostics.push({
|
|
276
|
+
code: "tool-policy.itemSettingNotApplied",
|
|
277
|
+
level: request.level,
|
|
278
|
+
toolId,
|
|
279
|
+
message: `Item "${item.id}" names "${toolId}" in ${settings.join(" and ")}, but "${toolId}" is on the ${request.scope.level}-level toolbar, which item settings do not reach. Place "${toolId}" at item level to enforce the setting per item.`,
|
|
280
|
+
details,
|
|
281
|
+
});
|
|
299
282
|
}
|
|
300
283
|
}
|
|
301
284
|
const survivingIds = new Set(candidates);
|
|
@@ -305,8 +288,7 @@ export function composeDecision(inputs) {
|
|
|
305
288
|
toolId,
|
|
306
289
|
required: flag?.required ?? false,
|
|
307
290
|
alwaysAvailable: flag?.alwaysAvailable ?? false,
|
|
308
|
-
|
|
309
|
-
sources: sourcesByTool.get(toolId) ?? ["placement"],
|
|
291
|
+
parameters: resolveToolParameters(toolId, pnpPolicy.assessment, pnpPolicy.item),
|
|
310
292
|
};
|
|
311
293
|
});
|
|
312
294
|
builder.reconcileFinalStates(survivingIds);
|
|
@@ -323,10 +305,11 @@ export function composeDecision(inputs) {
|
|
|
323
305
|
* (blocklist) last — so the FIRST gate that would have matched wins,
|
|
324
306
|
* which is the gate the pipeline actually invoked. Subsequent gates
|
|
325
307
|
* never get to evaluate a tool that an earlier step already removed.
|
|
308
|
+
* Step 5b passes only a tool this level places or no level places.
|
|
326
309
|
*/
|
|
327
310
|
function detectHostRemovalGate(toolId, args) {
|
|
328
|
-
if (!args.
|
|
329
|
-
return { hostRule: "placement-missing", hostValue: args.
|
|
311
|
+
if (!args.placedAtAnyLevel.has(toolId)) {
|
|
312
|
+
return { hostRule: "placement-missing", hostValue: args.allTools };
|
|
330
313
|
}
|
|
331
314
|
if (args.providers?.[toolId]?.enabled === false) {
|
|
332
315
|
return { hostRule: "provider-disabled", hostValue: false };
|
|
@@ -340,9 +323,47 @@ function detectHostRemovalGate(toolId, args) {
|
|
|
340
323
|
hostValue: Array.from(args.blocked),
|
|
341
324
|
};
|
|
342
325
|
}
|
|
343
|
-
// Defensive:
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
return { hostRule: "placement-missing", hostValue: args.
|
|
326
|
+
// Defensive: at the call site the tool is in `mandates` but not in
|
|
327
|
+
// `postHostCandidates`, so one of the four gates above MUST have
|
|
328
|
+
// fired. If none did, the host pipeline has a bug; surface a
|
|
329
|
+
// best-effort rather than throwing.
|
|
330
|
+
return { hostRule: "placement-missing", hostValue: args.allTools };
|
|
331
|
+
}
|
|
332
|
+
/** Where each PNP/profile rule's ids are authored. */
|
|
333
|
+
const PNP_RULE_FIELDS = {
|
|
334
|
+
"district-block": "settings.districtPolicy.blockedTools",
|
|
335
|
+
"test-admin-override": "settings.testAdministration.toolOverrides",
|
|
336
|
+
"item-restriction": "item settings.restrictedTools",
|
|
337
|
+
"pnp-prohibited": "personalNeedsProfile.prohibitedSupports",
|
|
338
|
+
"item-requirement": "item settings.requiredTools",
|
|
339
|
+
"district-requirement": "settings.districtPolicy.requiredTools",
|
|
340
|
+
"pnp-support": "personalNeedsProfile.supports",
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* A `tool-policy.overrideBlocked` diagnostic for a tool a `true` test
|
|
344
|
+
* administration override grants and `rule` withdraws. `level` is absent on a
|
|
345
|
+
* feature decision, which has no toolbar level.
|
|
346
|
+
*/
|
|
347
|
+
export function overrideBlockedDiagnostic(toolId, rule, level) {
|
|
348
|
+
return {
|
|
349
|
+
code: "tool-policy.overrideBlocked",
|
|
350
|
+
...(level ? { level } : {}),
|
|
351
|
+
toolId,
|
|
352
|
+
message: `settings.testAdministration.toolOverrides grants "${toolId}", but ${PNP_RULE_FIELDS[rule]} withdraws it and outranks a granting override.`,
|
|
353
|
+
details: { rule },
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* A `tool-policy.unknownSupportId` diagnostic for an id the registry lacks,
|
|
358
|
+
* naming every list that names it. It describes the engine's inputs rather
|
|
359
|
+
* than a decision, so it carries no level.
|
|
360
|
+
*/
|
|
361
|
+
export function unknownSupportIdDiagnostic(supportId, origins) {
|
|
362
|
+
const fields = origins.map((rule) => PNP_RULE_FIELDS[rule]).join(", ");
|
|
363
|
+
return {
|
|
364
|
+
code: "tool-policy.unknownSupportId",
|
|
365
|
+
toolId: supportId,
|
|
366
|
+
message: `No tool is registered under "${supportId}", named in ${fields}, so it matches nothing. These lists name tools by tool id.`,
|
|
367
|
+
details: { origins: [...origins] },
|
|
368
|
+
};
|
|
348
369
|
}
|
|
@@ -11,16 +11,15 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { ToolPlacementLevel } from "../../services/tools-config-normalizer.js";
|
|
13
13
|
import type { ToolContext, ToolLevel } from "../../services/tool-context.js";
|
|
14
|
-
import type {
|
|
15
|
-
import type { ToolPolicyProvenance } from "./provenance.js";
|
|
14
|
+
import type { PnpPolicySourceRule, ToolPolicyProvenance } from "./provenance.js";
|
|
16
15
|
/**
|
|
17
16
|
* Scope identifier for a `decide(...)` request. The engine uses
|
|
18
17
|
* `level` to pick which placement bucket to read; `scopeId` is opaque
|
|
19
18
|
* to the engine (it is round-tripped into provenance for human-readable
|
|
20
19
|
* trails) and is typically the section-id, item-id, or passage-id of
|
|
21
20
|
* the surface asking the question. `contentKind` mirrors the
|
|
22
|
-
* `ToolbarContext.scope.contentKind` field
|
|
23
|
-
*
|
|
21
|
+
* `ToolbarContext.scope.contentKind` field `<pie-item-toolbar>` reads for
|
|
22
|
+
* content relevance.
|
|
24
23
|
*/
|
|
25
24
|
export interface ToolScope {
|
|
26
25
|
level: ToolLevel;
|
|
@@ -41,29 +40,49 @@ export interface ToolPolicyDecisionRequest {
|
|
|
41
40
|
level: ToolPlacementLevel;
|
|
42
41
|
scope: ToolScope;
|
|
43
42
|
/**
|
|
44
|
-
* Optional
|
|
45
|
-
* `isVisibleInContext(...)
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `PolicySource` instances so they can refine the candidate set
|
|
49
|
-
* if they need to.
|
|
43
|
+
* Optional tool context. The engine does not call
|
|
44
|
+
* `isVisibleInContext(...)`: content relevance is applied at the toolbar
|
|
45
|
+
* boundary. The context is forwarded to custom `PolicySource` instances so
|
|
46
|
+
* they can refine the candidate set.
|
|
50
47
|
*/
|
|
51
48
|
context?: ToolContext;
|
|
52
49
|
}
|
|
53
50
|
/**
|
|
54
|
-
* Diagnostic codes the engine may emit alongside a decision.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
51
|
+
* Diagnostic codes the engine may emit alongside a decision.
|
|
52
|
+
* `tool-policy.requiredToolBlocked` fires when a host policy gate
|
|
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}.
|
|
59
57
|
*
|
|
60
58
|
* `tool-policy.placementMissing` fires when a custom `PolicySource`
|
|
61
59
|
* references a tool ID that is not present in `tools.placement[level]`
|
|
62
|
-
* for the resolved level.
|
|
60
|
+
* for the resolved level. `details` is {@link PlacementMissingDetails}.
|
|
61
|
+
* The host-side `ToolConfigDiagnostic` channel
|
|
63
62
|
* (already used by `tool-config-validation.ts`) covers config-time
|
|
64
63
|
* misconfiguration; this channel covers per-decision conflicts.
|
|
64
|
+
*
|
|
65
|
+
* `tool-policy.unknownSupportId` fires for each id a profile, district
|
|
66
|
+
* policy, test administration or item names that no tool is registered under.
|
|
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}.
|
|
70
|
+
*
|
|
71
|
+
* `tool-policy.itemSettingNotApplied` fires on a section-, assessment- or
|
|
72
|
+
* passage-level decision for each tool on that toolbar a mounted item's
|
|
73
|
+
* `restrictedTools` or `requiredTools` names, once per tool and item: item
|
|
74
|
+
* settings govern only the item's own toolbar. `details` is
|
|
75
|
+
* {@link ItemSettingNotAppliedDetails}.
|
|
76
|
+
*
|
|
77
|
+
* `tool-policy.overrideBlocked` fires for each tool a `true`
|
|
78
|
+
* `settings.testAdministration.toolOverrides` entry grants that an item's
|
|
79
|
+
* `restrictedTools` or the profile's `prohibitedSupports` withdraws instead.
|
|
80
|
+
* `details` is {@link OverrideBlockedDetails}.
|
|
81
|
+
*
|
|
82
|
+
* The toolkit coordinator logs each diagnostic once per code, tool and item,
|
|
83
|
+
* and hands the same ones to its `onPolicyDiagnostic` listeners.
|
|
65
84
|
*/
|
|
66
|
-
export type ToolPolicyDiagnosticCode =
|
|
85
|
+
export type ToolPolicyDiagnosticCode = keyof ToolPolicyDiagnosticDetails;
|
|
67
86
|
/**
|
|
68
87
|
* Which host gate removed a profile-mandated tool. Surfaced inside
|
|
69
88
|
* `ToolPolicyDiagnostic.details` for `tool-policy.requiredToolBlocked`
|
|
@@ -73,66 +92,90 @@ export type ToolPolicyDiagnosticCode = "tool-policy.requiredToolBlocked" | "tool
|
|
|
73
92
|
* `provenance.features[toolId].allDecisions`.
|
|
74
93
|
*
|
|
75
94
|
* The four values mirror steps 1–4 of `composeDecision`:
|
|
76
|
-
* - `placement-missing` —
|
|
95
|
+
* - `placement-missing` — no level of `tools.placement` lists the tool
|
|
77
96
|
* - `provider-disabled` — `tools.providers[id].enabled === false`
|
|
78
97
|
* - `host-allowlist` — non-empty `tools.policy.allowed` excludes the id
|
|
79
98
|
* - `host-blocked` — `tools.policy.blocked` lists the id
|
|
80
99
|
*/
|
|
81
100
|
export type ToolPolicyHostGate = "placement-missing" | "provider-disabled" | "host-allowlist" | "host-blocked";
|
|
82
|
-
/**
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* always populate this exact shape today and consumers can safely
|
|
87
|
-
* cast.
|
|
88
|
-
*/
|
|
89
|
-
export interface RequiredToolBlockedDetails extends Record<string, unknown> {
|
|
90
|
-
/** The profile policy rule that mandated the tool (e.g. `district-requirement`). */
|
|
91
|
-
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";
|
|
92
105
|
/** Which host gate removed the tool. */
|
|
93
106
|
hostRule: ToolPolicyHostGate;
|
|
94
107
|
/** The host configuration value that triggered the gate (best-effort, may be omitted). */
|
|
95
108
|
hostValue?: unknown;
|
|
96
109
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
/** Payload of a `tool-policy.unknownSupportId` diagnostic. */
|
|
111
|
+
export interface UnknownSupportIdDetails {
|
|
112
|
+
/** The rules whose lists name the id, in precedence order. */
|
|
113
|
+
origins: PnpPolicySourceRule[];
|
|
114
|
+
}
|
|
115
|
+
/** Payload of a `tool-policy.placementMissing` diagnostic. */
|
|
116
|
+
export interface PlacementMissingDetails {
|
|
117
|
+
/** The custom `PolicySource` that named the tool. */
|
|
118
|
+
customSourceId: string;
|
|
119
|
+
}
|
|
120
|
+
/** Payload of a `tool-policy.overrideBlocked` diagnostic. */
|
|
121
|
+
export interface OverrideBlockedDetails {
|
|
122
|
+
/** The rule that withdrew the granted tool. */
|
|
123
|
+
rule: "item-restriction" | "pnp-prohibited";
|
|
124
|
+
}
|
|
125
|
+
/** Payload of a `tool-policy.itemSettingNotApplied` diagnostic. */
|
|
126
|
+
export interface ItemSettingNotAppliedDetails {
|
|
127
|
+
/** Canonical id of the item whose setting names the tool. */
|
|
128
|
+
itemId: string;
|
|
129
|
+
/** The item settings that name it. */
|
|
130
|
+
settings: Array<"restrictedTools" | "requiredTools">;
|
|
131
|
+
/** The level of the toolbar the tool is on. */
|
|
132
|
+
toolbarLevel: ToolLevel;
|
|
104
133
|
}
|
|
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;
|
|
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];
|
|
105
156
|
export interface ToolPolicyEntry {
|
|
106
157
|
toolId: string;
|
|
107
158
|
/**
|
|
108
159
|
* `true` when PNP/profile policy mandates this tool (item or district
|
|
109
|
-
* `requiredTools
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* those should listen on the diagnostic channel.
|
|
160
|
+
* `requiredTools`). Advisory mandates that the host blocked do *not*
|
|
161
|
+
* surface here — they appear in `diagnostics` instead. Hosts that need
|
|
162
|
+
* to know about those should listen on the diagnostic channel.
|
|
113
163
|
*/
|
|
114
164
|
required: boolean;
|
|
115
165
|
/**
|
|
116
|
-
* `true` for PNP `supports`
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
166
|
+
* `true` for a tool the student's PNP `supports` or a
|
|
167
|
+
* `testAdministration.toolOverrides` entry grants — UI-level signal that
|
|
168
|
+
* the host cannot toggle this tool off in user preferences. Does NOT
|
|
169
|
+
* override host blocks (they would have removed the entry before this
|
|
170
|
+
* flag is read).
|
|
120
171
|
*/
|
|
121
172
|
alwaysAvailable: boolean;
|
|
122
173
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
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]`.
|
|
174
|
+
* The tool's parameters: the item's `toolParameters` entry on the item's own
|
|
175
|
+
* toolbar, else the assessment's `toolParameters` entry. Set whether or not a
|
|
176
|
+
* grant admitted the tool.
|
|
134
177
|
*/
|
|
135
|
-
|
|
178
|
+
parameters?: Record<string, unknown>;
|
|
136
179
|
}
|
|
137
180
|
export interface ToolPolicyDecision {
|
|
138
181
|
visibleTools: ToolPolicyEntry[];
|
|
@@ -21,20 +21,20 @@
|
|
|
21
21
|
* reasons, and only the second is a defect.
|
|
22
22
|
*/
|
|
23
23
|
import type { PnpPolicyResult } from "../sources/PnpPolicySource.js";
|
|
24
|
-
import type {
|
|
25
|
-
import type { ToolPolicyResolutionDecision, ToolPolicySourceType } from "./provenance.js";
|
|
24
|
+
import type { ToolPolicyDiagnostic } from "./decision-types.js";
|
|
25
|
+
import type { PnpPolicySourceRule, ToolPolicyResolutionDecision, ToolPolicySourceType } from "./provenance.js";
|
|
26
26
|
/**
|
|
27
|
-
* A feature verdict comes from one of the
|
|
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
|
/**
|
|
37
|
-
* `true` only when policy explicitly granted the feature at one of the
|
|
37
|
+
* `true` only when policy explicitly granted the feature at one of the eight
|
|
38
38
|
* precedence levels. A feature nobody configured is **not** available —
|
|
39
39
|
* accommodations require a documented need, so silence means no.
|
|
40
40
|
*/
|
|
@@ -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
|
-
|
|
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;
|
|
@@ -51,9 +52,10 @@ export interface FeaturePolicyDecision {
|
|
|
51
52
|
*
|
|
52
53
|
* `false` makes a *denial* a wiring gap rather than a verdict: no profile,
|
|
53
54
|
* district policy or test administration could be consulted, because there was
|
|
54
|
-
* no assessment to read them from. It is not itself a denial — an item
|
|
55
|
-
* carrying `requiredTools`
|
|
56
|
-
* assessment bound — so read it alongside `granted` rather than
|
|
55
|
+
* no assessment to read them from. It is not itself a denial — an item's
|
|
56
|
+
* registered settings carrying `requiredTools` mandate a feature at precedence
|
|
57
|
+
* 6 with no assessment bound — so read it alongside `granted` rather than
|
|
58
|
+
* instead of it.
|
|
57
59
|
*
|
|
58
60
|
* Granting is unaffected either way: an unbound host with no item mandate still
|
|
59
61
|
* gets `granted: false`, since an accommodation requires a documented need and
|
|
@@ -65,16 +67,23 @@ export interface FeaturePolicyDecision {
|
|
|
65
67
|
*/
|
|
66
68
|
assessmentBound: boolean;
|
|
67
69
|
/**
|
|
68
|
-
* `true` when
|
|
69
|
-
*
|
|
70
|
+
* `true` when item or district `requiredTools` mandates the feature, including
|
|
71
|
+
* under a `true` test-administration override.
|
|
70
72
|
*/
|
|
71
73
|
required: boolean;
|
|
72
74
|
/**
|
|
73
|
-
*
|
|
74
|
-
* `
|
|
75
|
-
*
|
|
75
|
+
* The feature's parameters: the item's `toolParameters` entry for an item's
|
|
76
|
+
* scope, else the assessment's `toolParameters` entry, keyed by the feature id.
|
|
77
|
+
* Set on a denial too; a host denial leaves it out.
|
|
76
78
|
*/
|
|
77
|
-
parameters?:
|
|
79
|
+
parameters?: P;
|
|
80
|
+
/**
|
|
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`.
|
|
85
|
+
*/
|
|
86
|
+
diagnostics: ToolPolicyDiagnostic[];
|
|
78
87
|
}
|
|
79
88
|
/** What the engine knows that a single support-id resolution does not. */
|
|
80
89
|
export interface FeatureDecisionContext {
|
|
@@ -90,7 +99,7 @@ export interface FeatureDecisionContext {
|
|
|
90
99
|
* blocklist is a verdict whether or not an assessment was bound, and the flag
|
|
91
100
|
* only ever qualifies a *policy* denial.
|
|
92
101
|
*/
|
|
93
|
-
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>;
|
|
94
103
|
/**
|
|
95
104
|
* Whether a host gate produced the verdict, rather than a policy source.
|
|
96
105
|
*
|
|
@@ -107,4 +116,4 @@ export declare function isHostDeniedFeature(decision: Pick<FeaturePolicyDecision
|
|
|
107
116
|
* exactly one decision and at most one flags entry — no mapped-tool-id
|
|
108
117
|
* bookkeeping is needed to read it back out.
|
|
109
118
|
*/
|
|
110
|
-
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,13 +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 } from "./compose-decision.js";
|
|
23
24
|
/**
|
|
24
25
|
* Reason text for a denial that had no assessment to decide against.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* source reported them: nothing fired, so naming a seventh rule would describe
|
|
29
|
-
* a precedence level that does not exist.
|
|
27
|
+
* Used in place of "not configured", which is true but reads as a completed
|
|
28
|
+
* evaluation.
|
|
30
29
|
*/
|
|
31
30
|
const unboundAssessmentReason = (featureId) => `No assessment is bound, so no policy source could grant "${featureId}"`;
|
|
32
31
|
/**
|
|
@@ -51,6 +50,7 @@ export function hostFeatureDenial(featureId, rule, hostValue, context) {
|
|
|
51
50
|
: `Not listed in tools.policy.allowed (${hostValue.join(", ")})`,
|
|
52
51
|
required: false,
|
|
53
52
|
assessmentBound: context.assessmentBound,
|
|
53
|
+
diagnostics: [],
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
@@ -71,27 +71,31 @@ export function isHostDeniedFeature(decision) {
|
|
|
71
71
|
* exactly one decision and at most one flags entry — no mapped-tool-id
|
|
72
72
|
* bookkeeping is needed to read it back out.
|
|
73
73
|
*/
|
|
74
|
-
export function interpretFeatureResult(featureId, result, context) {
|
|
74
|
+
export function interpretFeatureResult(featureId, result, context, parameters) {
|
|
75
75
|
const decision = result.decisions[0];
|
|
76
76
|
const flags = Array.from(result.perToolFlags.values())[0];
|
|
77
77
|
const granted = decision?.action === "enable";
|
|
78
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";
|
|
79
82
|
return {
|
|
80
83
|
featureId,
|
|
81
84
|
granted,
|
|
82
85
|
action: decision?.action ?? "skip",
|
|
83
|
-
rule: decision
|
|
84
|
-
precedence: decision
|
|
86
|
+
rule: fired ? decision.rule : "none",
|
|
87
|
+
precedence: fired ? decision.precedence : null,
|
|
85
88
|
sourceType: decision?.sourceType ?? "system",
|
|
86
89
|
// Only a denial is re-worded. An unbound host can still be granted the
|
|
87
|
-
// feature — an item
|
|
88
|
-
// with no assessment in sight — and there the source's
|
|
89
|
-
// one.
|
|
90
|
+
// feature — an item's registered settings carrying `requiredTools` mandate
|
|
91
|
+
// it at precedence 6 with no assessment in sight — and there the source's
|
|
92
|
+
// reason is the true one.
|
|
90
93
|
reason: context.assessmentBound || granted
|
|
91
94
|
? reason
|
|
92
95
|
: unboundAssessmentReason(featureId),
|
|
93
96
|
required: Boolean(flags?.required),
|
|
94
|
-
parameters
|
|
97
|
+
parameters,
|
|
95
98
|
assessmentBound: context.assessmentBound,
|
|
99
|
+
diagnostics: Array.from(result.blockedOverrides, ([toolId, rule]) => overrideBlockedDiagnostic(toolId, rule)),
|
|
96
100
|
};
|
|
97
101
|
}
|