@pie-players/pie-assessment-toolkit 0.3.66 → 0.3.67

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/dist/index.d.ts CHANGED
@@ -53,6 +53,7 @@ export type { ToolConfigDiagnostic, ToolConfigDiagnosticSeverity, ToolConfigStri
53
53
  export type { ToolbarButtonItem, ToolbarItem, ToolbarItemBase, ToolbarLinkItem, } from "./services/toolbar-items.js";
54
54
  export { isExternalIconUrl, isInlineSvgIcon, isToolbarLinkItem, isValidToolbarItemShape, } from "./services/toolbar-items.js";
55
55
  export { normalizeToolsConfig, normalizeToolAlias, normalizeToolList, parseToolList, } from "./services/tools-config-normalizer.js";
56
+ export { isHostDeniedFeature } from "./policy/core/feature-decision.js";
56
57
  export { frameworkErrorFromToolConfigValidation, normalizeAndValidateToolsConfig, } from "./services/tool-config-validation.js";
57
58
  export type { ParsedToolInstanceId, ToolScopeLevel, } from "./services/tool-instance-id.js";
58
59
  export { createScopedToolId, parseScopedToolId, toOverlayToolId, } from "./services/tool-instance-id.js";
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ export { ToolkitCoordinator } from "./services/ToolkitCoordinator.js";
42
42
  export { formatFrameworkErrorForConsole, frameworkErrorFromToolConfigDiagnostics, frameworkErrorFromUnknown, toFrameworkErrorModel, } from "./services/framework-error.js";
43
43
  export { isExternalIconUrl, isInlineSvgIcon, isToolbarLinkItem, isValidToolbarItemShape, } from "./services/toolbar-items.js";
44
44
  export { normalizeToolsConfig, normalizeToolAlias, normalizeToolList, parseToolList, } from "./services/tools-config-normalizer.js";
45
+ export { isHostDeniedFeature } from "./policy/core/feature-decision.js";
45
46
  export { frameworkErrorFromToolConfigValidation, normalizeAndValidateToolsConfig, } from "./services/tool-config-validation.js";
46
47
  export { createScopedToolId, parseScopedToolId, toOverlayToolId, } from "./services/tool-instance-id.js";
47
48
  export { PlaybackState, TTSService } from "./services/TTSService.js";
@@ -113,6 +113,18 @@ export declare class ToolPolicyEngine {
113
113
  * {@link FeaturePolicyDecision.assessmentBound}.
114
114
  */
115
115
  decideFeature(featureId: string): FeaturePolicyDecision;
116
+ /**
117
+ * The host gates that hold for a feature id, or `null` when none fires.
118
+ *
119
+ * `policy.allowed` / `policy.blocked` name capabilities, not placements, so
120
+ * they are the one part of the host pipeline that is meaningful without a
121
+ * placement level — and the only lever a host has over a capability that
122
+ * renders as its own surface, since a `region` capability is rejected from
123
+ * `tools.placement` by configuration validation. `provider-disabled` and
124
+ * `placement-membership` are deliberately not applied: both are statements
125
+ * about a toolbar the feature was never on.
126
+ */
127
+ private hostFeatureGate;
116
128
  /**
117
129
  * Convenience wrapper for hosts that just want the visible tool
118
130
  * IDs. Equivalent to `decide(...).visibleTools.map(e => e.toolId)`.
@@ -16,8 +16,8 @@
16
16
  * PR 1 ships the engine *without callers*. PR 2 wires it into
17
17
  * `ToolkitCoordinator`. PR 3 switches `<pie-item-toolbar>` over.
18
18
  */
19
- import { normalizeToolsConfig } from "../../services/tools-config-normalizer.js";
20
- import { interpretFeatureResult } from "./feature-decision.js";
19
+ import { normalizeToolList, normalizeToolsConfig, } from "../../services/tools-config-normalizer.js";
20
+ import { hostFeatureDenial, interpretFeatureResult, } from "./feature-decision.js";
21
21
  import { composeDecision } from "./compose-decision.js";
22
22
  import { resolveDefaultPnpEnforcement } from "./pnp-policy-inputs.js";
23
23
  import { PnpPolicySource } from "../sources/PnpPolicySource.js";
@@ -111,6 +111,9 @@ export class ToolPolicyEngine {
111
111
  */
112
112
  decideFeature(featureId) {
113
113
  this.assertNotDisposed();
114
+ const hostDenial = this.hostFeatureGate(featureId);
115
+ if (hostDenial)
116
+ return hostDenial;
114
117
  return interpretFeatureResult(featureId, this.pnpPolicySource.resolveFeature(featureId, {
115
118
  assessment: this.assessment ?? undefined,
116
119
  currentItemRef: this.currentItemRef ?? undefined,
@@ -120,6 +123,29 @@ export class ToolPolicyEngine {
120
123
  // The engine can.
121
124
  { assessmentBound: this.assessment !== null });
122
125
  }
126
+ /**
127
+ * The host gates that hold for a feature id, or `null` when none fires.
128
+ *
129
+ * `policy.allowed` / `policy.blocked` name capabilities, not placements, so
130
+ * they are the one part of the host pipeline that is meaningful without a
131
+ * placement level — and the only lever a host has over a capability that
132
+ * renders as its own surface, since a `region` capability is rejected from
133
+ * `tools.placement` by configuration validation. `provider-disabled` and
134
+ * `placement-membership` are deliberately not applied: both are statements
135
+ * about a toolbar the feature was never on.
136
+ */
137
+ hostFeatureGate(featureId) {
138
+ const context = { assessmentBound: this.assessment !== null };
139
+ const blocked = normalizeToolList(this.tools.policy.blocked);
140
+ if (blocked.includes(featureId)) {
141
+ return hostFeatureDenial(featureId, "host-blocked", blocked, context);
142
+ }
143
+ const allowed = normalizeToolList(this.tools.policy.allowed);
144
+ if (allowed.length > 0 && !allowed.includes(featureId)) {
145
+ return hostFeatureDenial(featureId, "host-allowlist", allowed, context);
146
+ }
147
+ return null;
148
+ }
123
149
  /**
124
150
  * Convenience wrapper for hosts that just want the visible tool
125
151
  * IDs. Equivalent to `decide(...).visibleTools.map(e => e.toolId)`.
@@ -23,6 +23,13 @@
23
23
  import type { PnpPolicyResult } from "../sources/PnpPolicySource.js";
24
24
  import type { PnpPolicySourceRule } from "./policy-source-tag.js";
25
25
  import type { ToolPolicyResolutionDecision, ToolPolicySourceType } from "./provenance.js";
26
+ /**
27
+ * A feature verdict comes from one of the six PNP precedence levels, or from a
28
+ * host gate that never reaches them: `tools.policy.blocked` and a non-empty
29
+ * `tools.policy.allowed` are absolute for the id they name, exactly as they are
30
+ * on the placement-scoped path.
31
+ */
32
+ export type FeaturePolicyRule = PnpPolicySourceRule | "host-allowlist" | "host-blocked";
26
33
  export interface FeaturePolicyDecision {
27
34
  /** The PNP/AfA support id that was evaluated (e.g. `"signLanguage"`). */
28
35
  featureId: string;
@@ -34,8 +41,8 @@ export interface FeaturePolicyDecision {
34
41
  granted: boolean;
35
42
  action: ToolPolicyResolutionDecision["action"];
36
43
  /** Which precedence rule produced the verdict. */
37
- rule: PnpPolicySourceRule;
38
- precedence: 1 | 2 | 3 | 4 | 5 | 6;
44
+ rule: FeaturePolicyRule;
45
+ precedence: 0 | 1 | 2 | 3 | 4 | 5 | 6;
39
46
  sourceType: ToolPolicySourceType;
40
47
  /** Human-readable explanation, suitable for a policy debugger. */
41
48
  reason: string;
@@ -74,6 +81,25 @@ export interface FeatureDecisionContext {
74
81
  /** Whether the engine has an assessment bound. */
75
82
  assessmentBound: boolean;
76
83
  }
84
+ /**
85
+ * A host gate denied the feature outright, so no policy source was consulted.
86
+ *
87
+ * `precedence: 0` and the two `host-*` rules are the same values
88
+ * `composeDecision(...)` records for the placement-scoped path, so a debugger
89
+ * reads one vocabulary for both. `assessmentBound` is still reported: a host
90
+ * blocklist is a verdict whether or not an assessment was bound, and the flag
91
+ * only ever qualifies a *policy* denial.
92
+ */
93
+ export declare function hostFeatureDenial(featureId: string, rule: "host-allowlist" | "host-blocked", hostValue: readonly string[], context: FeatureDecisionContext): FeaturePolicyDecision;
94
+ /**
95
+ * Whether a host gate produced the verdict, rather than a policy source.
96
+ *
97
+ * The distinction is load-bearing for a content-dependent capability that
98
+ * declares `resolvesWithoutGrant`: an absent grant is a case it is allowed to
99
+ * answer from the content alone, while a host denial is the host's off switch
100
+ * and nothing may reopen it.
101
+ */
102
+ export declare function isHostDeniedFeature(decision: Pick<FeaturePolicyDecision, "rule"> | null | undefined): boolean;
77
103
  /**
78
104
  * Interpret a single-feature `PnpPolicySource.resolveFeature(...)` result.
79
105
  *
@@ -29,6 +29,41 @@
29
29
  * a precedence level that does not exist.
30
30
  */
31
31
  const unboundAssessmentReason = (featureId) => `No assessment is bound, so no policy source could grant "${featureId}"`;
32
+ /**
33
+ * A host gate denied the feature outright, so no policy source was consulted.
34
+ *
35
+ * `precedence: 0` and the two `host-*` rules are the same values
36
+ * `composeDecision(...)` records for the placement-scoped path, so a debugger
37
+ * reads one vocabulary for both. `assessmentBound` is still reported: a host
38
+ * blocklist is a verdict whether or not an assessment was bound, and the flag
39
+ * only ever qualifies a *policy* denial.
40
+ */
41
+ export function hostFeatureDenial(featureId, rule, hostValue, context) {
42
+ return {
43
+ featureId,
44
+ granted: false,
45
+ action: "block",
46
+ rule,
47
+ precedence: 0,
48
+ sourceType: "host",
49
+ reason: rule === "host-blocked"
50
+ ? "Listed in tools.policy.blocked"
51
+ : `Not listed in tools.policy.allowed (${hostValue.join(", ")})`,
52
+ required: false,
53
+ assessmentBound: context.assessmentBound,
54
+ };
55
+ }
56
+ /**
57
+ * Whether a host gate produced the verdict, rather than a policy source.
58
+ *
59
+ * The distinction is load-bearing for a content-dependent capability that
60
+ * declares `resolvesWithoutGrant`: an absent grant is a case it is allowed to
61
+ * answer from the content alone, while a host denial is the host's off switch
62
+ * and nothing may reopen it.
63
+ */
64
+ export function isHostDeniedFeature(decision) {
65
+ return (decision?.rule === "host-blocked" || decision?.rule === "host-allowlist");
66
+ }
32
67
  /**
33
68
  * Interpret a single-feature `PnpPolicySource.resolveFeature(...)` result.
34
69
  *
@@ -21,7 +21,7 @@
21
21
  */
22
22
  export { ToolPolicyEngine, type PnpEnforcementMode, type ResolvedEngineInputs, type ToolPolicyChangeEvent, type ToolPolicyChangeListener, type ToolPolicyEngineArgs, type ToolPolicyEngineInputs, } from "./core/ToolPolicyEngine.js";
23
23
  export { TOOL_POLICY_ENGINE_KEY, type ToolPolicyEngineContext, } from "./core/engine-context.js";
24
- export type { FeaturePolicyDecision } from "./core/feature-decision.js";
24
+ export { isHostDeniedFeature, type FeaturePolicyDecision, type FeaturePolicyRule, } from "./core/feature-decision.js";
25
25
  export type { RequiredToolBlockedDetails, ToolPolicyDecision, ToolPolicyDecisionRequest, ToolPolicyDiagnostic, ToolPolicyDiagnosticCode, ToolPolicyEntry, ToolPolicyHostGate, ToolScope, } from "./core/decision-types.js";
26
26
  export type { PolicySource, PolicySourceDecisionContext, PolicySourceProvenanceEntry, PolicySourceResult, } from "./core/PolicySource.js";
27
27
  export type { PolicySourceTag, PnpPolicySourceRule, PnpPolicySourceTag, CustomPolicySourceTag, } from "./core/policy-source-tag.js";
@@ -21,3 +21,4 @@
21
21
  */
22
22
  export { ToolPolicyEngine, } from "./core/ToolPolicyEngine.js";
23
23
  export { TOOL_POLICY_ENGINE_KEY, } from "./core/engine-context.js";
24
+ export { isHostDeniedFeature, } from "./core/feature-decision.js";
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Whether a content capability has anything to show, for one entity and one
3
+ * profile.
4
+ *
5
+ * Two independent halves, and a capability is in play only when both answer yes:
6
+ * policy granted one of its support ids, and its own `requiresAuthoredContent`
7
+ * found the resource in this entity's catalogs. Neither implies the other — a
8
+ * learner with an accommodation still sees nothing on an item carrying no
9
+ * resource, and an item carrying one shows nothing to a learner without the
10
+ * grant.
11
+ *
12
+ * Policy answers in three states rather than two, because a host gate is not the
13
+ * absence of a grant: `resolvesWithoutGrant` lets a capability answer from the
14
+ * content when nobody was granted anything, and a host that switched the
15
+ * capability off must not be read as nobody having spoken. Resolution order is
16
+ * denial, then grant, then the content exception.
17
+ *
18
+ * It lives here, data-only and DOM-free, because two renderers ask it. The
19
+ * section player asks continuously: policy and catalogs both change under a
20
+ * mounted card, and it re-resolves to reconcile what is on screen. Print asks
21
+ * once — one learner, one profile, decided before the page exists, with nothing
22
+ * to toggle. A second implementation for the one-shot case would be two
23
+ * renderers disagreeing about the same card, which is the failure this exists to
24
+ * prevent.
25
+ *
26
+ * Capability-neutral, and gated as such: nothing here names a support id, a
27
+ * catalog type, or a surface. The caller passes the registrations and the slot,
28
+ * a capability interprets its own cards, and the resolution owns only the
29
+ * two-halves rule and its one documented exception.
30
+ */
31
+ import type { ToolRegistration } from "../services/ToolRegistry.js";
32
+ import type { CatalogOwnerSnapshot } from "../services/AccessibilityCatalogResolver.js";
33
+ /** What policy answered about one feature id. */
34
+ export type ContentCapabilityPolicy = {
35
+ outcome: "granted";
36
+ /** The support id that was granted — a capability may declare several. */
37
+ featureId: string;
38
+ /** Feature parameters carried by the decision, if any. */
39
+ parameters?: unknown;
40
+ }
41
+ /**
42
+ * No source granted it and none denied it. A capability declaring
43
+ * {@link ToolRegistration.resolvesWithoutGrant} may still answer from the
44
+ * content: silence is what an authored-presentation alternate looks like,
45
+ * since no profile speaks for one either way.
46
+ */
47
+ | {
48
+ outcome: "silent";
49
+ }
50
+ /**
51
+ * A host gate denied it — the off switch, not the absence of a grant. It
52
+ * outranks `resolvesWithoutGrant`, because a host saying a capability has no
53
+ * place in this delivery is a statement authored content cannot overrule.
54
+ */
55
+ | {
56
+ outcome: "denied";
57
+ };
58
+ /** Which half of a capability's resolution failed. */
59
+ export type ContentCapabilityPhase = "policy" | "content";
60
+ /** A capability that is in play, and everything its renderer needs to mount it. */
61
+ export interface ResolvedContentCapability {
62
+ registration: ToolRegistration;
63
+ /**
64
+ * The granted support id, or `""` when the capability resolved without a
65
+ * grant. Passed through to the render context unchanged, so a capability that
66
+ * serves both an authored-presentation case and an accommodation can still
67
+ * tell them apart at render time.
68
+ */
69
+ featureId: string;
70
+ parameters?: unknown;
71
+ /** Whatever the capability's own `resolve` returned; never inspected here. */
72
+ content: unknown;
73
+ }
74
+ export interface ResolveContentCapabilitiesArgs {
75
+ /**
76
+ * The capabilities to consider. A caller with a registry passes
77
+ * `getToolsBySurface(surface)`, which is what keeps a renderer from naming
78
+ * one.
79
+ */
80
+ registrations: readonly ToolRegistration[];
81
+ /** The entity's cards, or `null` when no resolver is available. */
82
+ catalogs: CatalogOwnerSnapshot | null;
83
+ /**
84
+ * Policy's answer about one feature id, in three states.
85
+ *
86
+ * Granting requires a documented need, so an unconfigured feature is
87
+ * `"silent"`, never granted. What the third state buys is the distinction
88
+ * `"silent"` cannot carry: a host that switched the capability off said
89
+ * something, and a capability allowed to answer from content alone must not
90
+ * treat that as nobody having spoken.
91
+ */
92
+ policyFor: (featureId: string) => ContentCapabilityPolicy;
93
+ /**
94
+ * Report a capability that threw. It is dropped either way; this is how a
95
+ * caller surfaces it as its own recoverable warning rather than letting one
96
+ * capability's defect reach the learner's content.
97
+ */
98
+ onError?: (registration: ToolRegistration, phase: ContentCapabilityPhase, error: unknown) => void;
99
+ }
100
+ /**
101
+ * The capabilities in play, in the order the caller offered them.
102
+ *
103
+ * Registry order is preserved so a renderer's slot ordering stays a property of
104
+ * the registry rather than of resolution timing.
105
+ */
106
+ export declare function resolveContentCapabilities(args: ResolveContentCapabilitiesArgs): ResolvedContentCapability[];
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Whether a content capability has anything to show, for one entity and one
3
+ * profile.
4
+ *
5
+ * Two independent halves, and a capability is in play only when both answer yes:
6
+ * policy granted one of its support ids, and its own `requiresAuthoredContent`
7
+ * found the resource in this entity's catalogs. Neither implies the other — a
8
+ * learner with an accommodation still sees nothing on an item carrying no
9
+ * resource, and an item carrying one shows nothing to a learner without the
10
+ * grant.
11
+ *
12
+ * Policy answers in three states rather than two, because a host gate is not the
13
+ * absence of a grant: `resolvesWithoutGrant` lets a capability answer from the
14
+ * content when nobody was granted anything, and a host that switched the
15
+ * capability off must not be read as nobody having spoken. Resolution order is
16
+ * denial, then grant, then the content exception.
17
+ *
18
+ * It lives here, data-only and DOM-free, because two renderers ask it. The
19
+ * section player asks continuously: policy and catalogs both change under a
20
+ * mounted card, and it re-resolves to reconcile what is on screen. Print asks
21
+ * once — one learner, one profile, decided before the page exists, with nothing
22
+ * to toggle. A second implementation for the one-shot case would be two
23
+ * renderers disagreeing about the same card, which is the failure this exists to
24
+ * prevent.
25
+ *
26
+ * Capability-neutral, and gated as such: nothing here names a support id, a
27
+ * catalog type, or a surface. The caller passes the registrations and the slot,
28
+ * a capability interprets its own cards, and the resolution owns only the
29
+ * two-halves rule and its one documented exception.
30
+ */
31
+ /** The support ids a capability answers to, defaulting to its own id. */
32
+ const supportIdsOf = (registration) => registration.pnpSupportIds?.length
33
+ ? registration.pnpSupportIds
34
+ : [registration.toolId];
35
+ /**
36
+ * Everything policy has to say about one capability: a grant, an off switch, or
37
+ * nothing.
38
+ *
39
+ * Denial is checked ahead of a grant on each id rather than after the scan,
40
+ * because the two can only disagree when a host blocked one of a capability's ids
41
+ * while a profile granted another, and there the off switch is the later, more
42
+ * specific statement about this delivery.
43
+ *
44
+ * A host gate names *capabilities*, so the tool id is probed too when it is not
45
+ * already a declared support id — otherwise a capability whose id differs from
46
+ * its support ids would slip a host block. That probe is gate-only: a grant on
47
+ * the tool id is ignored, or blocking would double as a second way to switch a
48
+ * capability on.
49
+ */
50
+ function policyForCapability(registration, args) {
51
+ const supportIds = supportIdsOf(registration);
52
+ for (const supportId of supportIds) {
53
+ const answer = args.policyFor(supportId);
54
+ if (answer.outcome === "denied")
55
+ return { grant: null, denied: true };
56
+ if (answer.outcome === "granted")
57
+ return { grant: answer, denied: false };
58
+ }
59
+ if (supportIds.includes(registration.toolId)) {
60
+ return { grant: null, denied: false };
61
+ }
62
+ return {
63
+ grant: null,
64
+ denied: args.policyFor(registration.toolId).outcome === "denied",
65
+ };
66
+ }
67
+ function resolveOne(registration, args) {
68
+ let grant = null;
69
+ try {
70
+ const answer = policyForCapability(registration, args);
71
+ // Nothing reopens a host denial — not a grant it outranked, and not the
72
+ // content exception below.
73
+ if (answer.denied)
74
+ return null;
75
+ grant = answer.grant;
76
+ }
77
+ catch (error) {
78
+ args.onError?.(registration, "policy", error);
79
+ return null;
80
+ }
81
+ // The exception, and the only one: a capability whose content can declare
82
+ // itself authored presentation is consulted even when policy granted nothing,
83
+ // because an item family designed to be delivered with its alternate on screen
84
+ // is not an accommodation and no profile grants or revokes it. The capability
85
+ // still decides — `granted` is how it tells the two cases apart.
86
+ if (!grant && !registration.resolvesWithoutGrant)
87
+ return null;
88
+ if (!registration.requiresAuthoredContent) {
89
+ // Nothing to look for in the content, so the grant is the whole answer. A
90
+ // capability reaching here without one has no second half to supply it.
91
+ if (!grant)
92
+ return null;
93
+ return {
94
+ registration,
95
+ featureId: grant.featureId,
96
+ parameters: grant.parameters,
97
+ content: null,
98
+ };
99
+ }
100
+ const context = {
101
+ featureId: grant?.featureId ?? "",
102
+ parameters: grant?.parameters,
103
+ catalogs: args.catalogs,
104
+ granted: Boolean(grant),
105
+ };
106
+ let content;
107
+ try {
108
+ content = registration.requiresAuthoredContent.resolve(context);
109
+ }
110
+ catch (error) {
111
+ args.onError?.(registration, "content", error);
112
+ return null;
113
+ }
114
+ // Absent content is the honest answer to "is there anything to show", not a
115
+ // failure: the item carries no resource for this capability.
116
+ if (content === null || content === undefined)
117
+ return null;
118
+ return {
119
+ registration,
120
+ featureId: context.featureId,
121
+ parameters: context.parameters,
122
+ content,
123
+ };
124
+ }
125
+ /**
126
+ * The capabilities in play, in the order the caller offered them.
127
+ *
128
+ * Registry order is preserved so a renderer's slot ordering stays a property of
129
+ * the registry rather than of resolution timing.
130
+ */
131
+ export function resolveContentCapabilities(args) {
132
+ return args.registrations.flatMap((registration) => {
133
+ const resolved = resolveOne(registration, args);
134
+ return resolved ? [resolved] : [];
135
+ });
136
+ }
@@ -21,6 +21,8 @@
21
21
  export type { HostedToolContext, HostedToolSize, ResolvedToolContext, ToolActivation, ToolContentDependency, ToolContentDependencyContext, ToolModuleLoader, ToolProviderDescriptor, ToolRegistration, ToolRenderElement, ToolSingletonScope, ToolSurfaceRenderContext, ToolSurfaceRenderResult, ToolSurfaceServices, ToolToolbarButtonDefinition, ToolToolbarRenderResult, ToolWindowShellConfig, ToolbarContext, } from "../services/ToolRegistry.js";
22
22
  export { ToolRegistry } from "../services/ToolRegistry.js";
23
23
  export type { CatalogOwnerCard, CatalogOwnerSnapshot, } from "../services/AccessibilityCatalogResolver.js";
24
+ export type { ContentCapabilityPhase, ContentCapabilityPolicy, ResolveContentCapabilitiesArgs, ResolvedContentCapability, } from "./content-capability-resolution.js";
25
+ export { resolveContentCapabilities } from "./content-capability-resolution.js";
24
26
  export type { ToolContext, ToolLevel } from "../services/tool-context.js";
25
27
  export { hasChoiceInteraction, hasMathContent, hasReadableText, hasScienceContent, } from "../services/tool-context.js";
26
28
  export { createScopedToolId } from "../services/tool-instance-id.js";
@@ -19,6 +19,7 @@
19
19
  * point — the mechanism is the same one our own registrations use.
20
20
  */
21
21
  export { ToolRegistry } from "../services/ToolRegistry.js";
22
+ export { resolveContentCapabilities } from "./content-capability-resolution.js";
22
23
  export { hasChoiceInteraction, hasMathContent, hasReadableText, hasScienceContent, } from "../services/tool-context.js";
23
24
  // Scoped tool instance ids, so two placements of one tool do not share state.
24
25
  export { createScopedToolId } from "../services/tool-instance-id.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-players/pie-assessment-toolkit",
3
- "version": "0.3.66",
3
+ "version": "0.3.67",
4
4
  "type": "module",
5
5
  "description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
6
6
  "license": "MIT",
@@ -41,6 +41,10 @@
41
41
  "types": "./dist/components/section-toolbar-element.d.ts",
42
42
  "import": "./dist/components/section-toolbar-element.js"
43
43
  },
44
+ "./services/AccessibilityCatalogResolver": {
45
+ "types": "./dist/services/AccessibilityCatalogResolver.d.ts",
46
+ "import": "./dist/services/AccessibilityCatalogResolver.js"
47
+ },
44
48
  "./services/pnp-standard-features": {
45
49
  "types": "./dist/services/pnp-standard-features.d.ts",
46
50
  "import": "./dist/services/pnp-standard-features.js"
@@ -79,15 +83,15 @@
79
83
  "test": "bun test"
80
84
  },
81
85
  "dependencies": {
82
- "@pie-players/pie-calculator": "0.3.66",
83
- "@pie-players/pie-context": "0.3.66",
84
- "@pie-players/pie-players-shared": "0.3.66",
85
- "@pie-players/pie-tts": "0.3.66",
86
+ "@pie-players/pie-calculator": "0.3.67",
87
+ "@pie-players/pie-context": "0.3.67",
88
+ "@pie-players/pie-players-shared": "0.3.67",
89
+ "@pie-players/pie-tts": "0.3.67",
86
90
  "speech-rule-engine": "^5.0.0-rc.4"
87
91
  },
88
92
  "peerDependencies": {
89
- "@pie-players/pie-calculator-desmos": "0.3.66",
90
- "@pie-players/tts-client-server": "0.3.66"
93
+ "@pie-players/pie-calculator-desmos": "0.3.67",
94
+ "@pie-players/tts-client-server": "0.3.67"
91
95
  },
92
96
  "peerDependenciesMeta": {
93
97
  "@pie-players/pie-calculator-desmos": {
@@ -100,8 +104,8 @@
100
104
  "devDependencies": {
101
105
  "@biomejs/biome": "^2.5.7",
102
106
  "@happy-dom/global-registrator": "^20.11.1",
103
- "@pie-players/pie-calculator-desmos": "0.3.66",
104
- "@pie-players/tts-client-server": "0.3.66",
107
+ "@pie-players/pie-calculator-desmos": "0.3.67",
108
+ "@pie-players/tts-client-server": "0.3.67",
105
109
  "svelte": "^5.56.8",
106
110
  "typescript": "^5.9.3"
107
111
  },