This contract defines the minimum runtime guarantees between host components
(pie-assessment-toolkit, section players, shells) and toolkit-managed tools.
- Applies to all toolkit-managed tools (
pie-tool-*). - Applies to shell-aware tools (item/passage/region-scoped tools).
- Does not change item player internals.
- Runtime tools must consume
assessmentToolkitRuntimeContext. - Shell-aware tools must also consume
assessmentToolkitShellContext. - Region-aware tools must also consume
assessmentToolkitRegionScopeContext.
Use contract helpers exported from @pie-players/pie-assessment-toolkit:
connectToolRuntimeContext(host, onValue)connectToolShellContext(host, onValue)connectToolRegionScopeContext(host, onValue)
These helpers include provider-announcement handling and retry behavior so late provider registration is tolerated.
Cross-boundary events (tool -> host, shell -> host, host -> tool) must be:
bubbles: truecomposed: true
Use helpers:
createCrossBoundaryEvent(name, detail)dispatchCrossBoundaryEvent(target, name, detail)
Tools must tolerate delayed context arrival and context re-binding:
- tool can mount before provider exists
- tool reconnects when provider becomes available
- tool cleans up subscriptions on unmount
Tools must not infer runtime scope from parentElement chains. Host/root
elements should be explicit inputs or context-derived values.
Allowed root sources:
- explicit prop passed from host
- region scope context (
scopeElement) - shell context (
scopeElement) as fallback
Hosts that need content-specific tool behavior should register
toolContextResolvers, not override packaged tool registrations. Section
player hosts usually supply them on runtime.toolContextResolvers; direct
toolkit consumers may pass the same map to ToolkitCoordinator or the
<pie-assessment-toolkit> JS property. A resolver runs only after the framework
has applied placement, provider config, host policy, and PNP/profile gates. It
may hide a surviving tool for the current scope or attach render params for the
tool to consume.
For calculators, the resolver params are:
{
calculatorType: "basic" | "scientific";
availableTypes: Array<"basic" | "scientific">;
}The packaged calculator reads these values through
toolbarContext.getToolRenderParams("calculator") and applies them to the
toolbar button plus calculator element. Content metadata therefore stays in
host code, while PNP/profile restrictions remain framework-owned and higher
precedence.
Tools that call an external service (calculators, server-backed TTS, translation, dictionary, etc.) reach that service through host-owned backend endpoints. The framework does not ship authentication, rate-limiting, or secret management for these routes — it exposes typed provider hooks plus an origin-based header-scrubbing policy and delegates the rest to the host.
Any /api/... route referenced by a toolkit provider must be:
- authenticated by the host under the same session boundary as the assessment itself,
- rate-limited (keyed on student / attempt where the vendor bills per request),
- kept free of vendor credentials on the client side unless the vendor's own protections (origin-pinning, short-lived tokens) make that intentional.
authFetcher— optional provider runtime hook, typed as() => Promise<Partial<TConfig>>. Called during provider initialization inToolProviderRegistry; the return value is merged into the provider config beforeinitialize(). The built-in calculator default usesfetch(..., { credentials: "same-origin" }), i.e. it rides whatever session cookie the host already issued for the assessment. Seepackages/assessment-toolkit/src/tools/registrations/calculator.ts.authToken+apiEndpoint—ServerTTSProviderconfiguration. The token is sent asAuthorization: Bearer <token>on synthesis requests. Host obtains the token (via its own identity provider or throughauthFetcher) and supplies it in config.assetOrigins—ServerTTSProviderallow-list of origins that may receive theAuthorizationheader when the provider follows URLs returned by the TTS server (custom-transport audio and speech-mark URLs). Defaults to the origin ofapiEndpoint; malformed or non-http(s)URLs are always rejected. This is the one piece of provider-backend security the framework enforces directly. Seepackages/tts-client-server/src/ServerTTSProvider.ts.
- Calculator (Desmos). The framework supports two shapes.
DesmosToolProvider.proxyEndpointkeeps the Desmos API key server-side — the host runs a proxy and the client never sees the key.authFetcherreturning{ apiKey }sends the key to the client over an authenticated channel and relies on Desmos's domain-pinning to prevent reuse elsewhere. Either is an acceptable production pattern; the proxy is stronger. In both cases the endpoint that returns the config must require the same session the assessment does — the unauthenticated/api/tools/desmos/authroute inapps/section-demosis a shape reference, not a template. - Server-backed TTS. Follow the security section of
packages/tts-server-polly/examples/INTEGRATION-GUIDE.md#security-considerations— JWT or session-cookie check in a SvelteKithandlehook on/api/tts/*, rate-limit keyed on client identity, AWS / Google credentials via environment variables or IAM roles (never returned to the client). Explicitly setassetOriginson the client provider to the set of CDN origins your TTS server legitimately returns asset URLs for; the default covers same-origin only. For a reference server-side allow-list the same shape is implemented for SchoolCity viaTTS_SCHOOLCITY_ASSET_ORIGINS— seeapps/section-demos/src/routes/api/README.md. - New or custom providers. Anything that adds an
authFetcherorapiEndpointinherits this contract. If the provider follows URLs returned by its backend, it must replicate the same origin-based header-scrubbing thatServerTTSProviderdoes, and matchlegacy-compatibility-boundaries.mdcon any bridged surfaces.
- Unauthenticated
/api/tools/desmos/authin production → Desmos API key exposed to any caller of the URL; Desmos origin-pinning becomes the only defense, and rotation is the only remediation once leaked. - Unauthenticated
/api/tts/synthesize→ anyone on the internet can consume the host's Polly / Google credits; also a content-generation abuse surface (arbitrary text pushed through the TTS pipeline). includeAuthOnAssetFetch: truewithout a correctassetOriginslist → a compromised or misconfigured TTS server can return a cross-origin URL and exfiltrate the bearer token on the follow-up fetch.- Vendor credentials in client bundles or client-side config → permanent leak via the shipped JavaScript; again, rotation is the only remediation.
The routes in apps/section-demos/src/routes/api/ are intentionally
unauthenticated and exist for local development and e2e specs. In
particular, GET /api/tools/desmos/auth returns the configured
DESMOS_API_KEY with no session check. Do not copy these routes verbatim
into a production deployment — use them as shape references and wrap
them in the host's auth middleware.
./tool_provider_system.md— provider configuration model and whereauthFetcher/apiEndpointcome from../../packages/tts-server-polly/examples/INTEGRATION-GUIDE.md— end-to-end TTS integration, including security considerations and a SvelteKithooks.server.tssketch../../packages/tts-client-server/README.md—ServerTTSProviderconfiguration, includingassetOriginsandincludeAuthOnAssetFetch../../packages/assessment-toolkit/src/services/tool-providers/DesmosToolProvider.ts— Desmos provider config (apiKey,proxyEndpoint) with inline security notes