Data Lab Module Package
@maya/data-lab-module is the first-party trusted sample module for WCP-021. It exists to prove the developer module happy path without becoming product functionality or an installation channel.
The package follows the locked split:
- Module root declares.
- Host resolves runtime.
- Composer composes structure.
- React render runtime executes UI.
v0 Installability
This package is not installable from npm in v0. It is private: true, built by pnpm build:packages, and consumed only by monorepo hosts, workspace-private guide consumers, and workspace-private smoke consumers from built workspace output. Possible publishability opens only if tools/workspace-package-registry.mjs explicitly changes its npmPublishable classification to true.
Exports
| Export | Status | Purpose |
|---|---|---|
@maya/data-lab-module | implemented | Headless metadata, contract, factory, handles, and runtime binding helper. |
@maya/data-lab-module/render-pack | implemented | Explicit React render pack factory for host-provided controllers. |
@maya/data-lab-module/fixtures | implemented | Authoring fixtures generated from public factories. |
@maya/data-lab-module/styles.css | implemented | Explicit CSS subentry scoped under .aui-root .maya-data-lab-*. |
@maya/data-lab-module/package.json | implemented | Tooling metadata and mayaModule compatibility metadata. |
The root export is headless and inert: it does not import React, CSS, storage, network clients, render packs, or Claude product-kit code.
The documentation source is .docs/packages/data-lab-module.md.
Public root symbols: CreateDataLabModuleOptions, DataLabContributionOptions, DataLabModuleCompatibility, DataLabModuleInstance, DataLabModulePackageMetadata, DataLabRuntimeDefinition, bindDataLabModuleRuntime, createDataLabContributions, createDataLabModule, dataLabDefaultRouteBasePath, dataLabDefaultToolClaim, dataLabModuleApiVersion, dataLabModuleCompatibility, dataLabModuleContract, dataLabModuleOwner, dataLabModulePackageMetadata, dataLabPackageId, dataLabPackageVersion, dataLabRenderHandles, and defineDataLabRuntime.
Render-pack symbols: CreateDataLabRenderPackOptions, DataLabController, DataLabQueryInput, DataLabQueryResult, and createDataLabRenderPack.
Fixture symbols: dataLabFixtureIds and dataLabModuleFixtures.
Host Wiring
Current low-level wiring keeps the sample explicit: the host creates a module instance, resolves the per-session runtime, passes a host-owned controller to the render pack, validates coverage, and then renders the composition.
import '@maya/assistant-ui/styles.css';
import '@maya/data-lab-module/styles.css';
import { composeWorkspace } from '@maya/assistant-composition';
import { AssistantWorkspace, type AssistantWorkspaceGroupedProps } from '@maya/assistant-ui';
import { createReactRenderRuntime } from '@maya/assistant-ui/render-runtime';
import { bindDataLabModuleRuntime, createDataLabModule } from '@maya/data-lab-module';
import { createDataLabRenderPack } from '@maya/data-lab-module/render-pack';
import type { DataLabController } from '@maya/data-lab-module/render-pack';
const dataLab = createDataLabModule({ instanceId: 'data-lab-prod', routeBasePath: '/data-lab' });
const runtime = {
instanceId: 'data-lab-prod',
status: 'ready',
grants: ['data-lab.query'],
missing: [],
actions: [],
} as const;
const resolvedModule = bindDataLabModuleRuntime(dataLab, runtime);
const dataLabController = {
instanceId: 'data-lab-prod',
connection: {
baseUrlLabel: 'host-owned.example',
apiKeyConfigured: true,
health: 'ready',
},
metrics: {
datasetCount: 3,
lastRefreshLabel: 'just now',
},
actions: {
runQuery: async (input) => ({
title: input.query,
summary: `Host executed ${input.query}`,
rows: [],
}),
},
} satisfies DataLabController;
const compositionResult = composeWorkspace({ builtins: [], modules: [resolvedModule] });
if (!compositionResult.ok) throw new Error(compositionResult.diagnostics[0]?.message ?? 'Composition failed');
const renderRuntime = createReactRenderRuntime({
packs: [
createDataLabRenderPack({
instanceId: 'data-lab-prod',
controller: dataLabController,
}),
],
});
const coverage = renderRuntime.validateCoverage(compositionResult.composition);
if (!coverage.ok) throw new Error(coverage.diagnostics[0]?.message ?? 'Render coverage failed');
export function DataLabWorkspace({ workspaceControllers }: { workspaceControllers: AssistantWorkspaceGroupedProps }) {
return (
<AssistantWorkspace
{...workspaceControllers}
activeRoutePath="/data-lab"
composition={compositionResult.composition}
renderRuntime={renderRuntime}
/>
);
}
Current product-kit wiring uses the same module instance, runtime, and render pack, but lets @maya/claude-workspace provide Claude Web product defaults:
import { createClaudeWorkspaceRuntime } from '@maya/claude-workspace';
import type { ReactRenderPack } from '@maya/assistant-ui/render-runtime';
import { bindDataLabModuleRuntime, createDataLabModule } from '@maya/data-lab-module';
import { createDataLabRenderPack } from '@maya/data-lab-module/render-pack';
import type { DataLabController } from '@maya/data-lab-module/render-pack';
const dataLab = createDataLabModule({ instanceId: 'data-lab-prod', routeBasePath: '/data-lab' });
const hostResolvedRuntime = {
instanceId: 'data-lab-prod',
status: 'ready',
grants: ['data-lab.query'],
missing: [],
actions: [],
} as const;
const dataLabController = {
instanceId: 'data-lab-prod',
connection: {
baseUrlLabel: 'host-owned.example',
apiKeyConfigured: true,
health: 'ready',
},
metrics: {
datasetCount: 3,
lastRefreshLabel: 'just now',
},
actions: {
runQuery: async (input) => ({
title: input.query,
summary: `Host executed ${input.query}`,
rows: [],
}),
},
} satisfies DataLabController;
const resolvedModule = bindDataLabModuleRuntime(dataLab, hostResolvedRuntime);
const dataLabRenderPack = createDataLabRenderPack({
instanceId: 'data-lab-prod',
controller: dataLabController,
});
export function createWorkspaceRuntime(hostClaudeRenderPacks: readonly ReactRenderPack<any>[]) {
return createClaudeWorkspaceRuntime({
modules: [resolvedModule],
renderPacks: [...hostClaudeRenderPacks, dataLabRenderPack],
});
}
Product hosts that render Claude built-in pages pass their Claude built-in render packs through hostClaudeRenderPacks. Data Lab never self-registers executable UI.
Multi-Instance
This is the concrete multi-instance proof for the package.
The package can be enabled more than once. Each instance must use a distinct instanceId, route path, host-resolved runtime, controller, and render pack. Renderer claims must also be distinct unless the host resolves conflicts through explicit policy.
const staging = createDataLabModule({
instanceId: 'data-lab-staging',
routeBasePath: '/data-lab-staging',
toolClaim: 'tool:data_lab.query.staging',
});
const prod = createDataLabModule({
instanceId: 'data-lab-prod',
routeBasePath: '/data-lab',
toolClaim: 'tool:data_lab.query',
});
Render pack matching is scoped by module owner plus opaque render handle, so one instance's render pack cannot satisfy another instance's handles.
Diagnostics
The package intentionally exercises the same diagnostics a third-party trusted module author will see:
- Missing
@maya/data-lab-module/render-packcoverage producesWCP-HANDLE-001with the Data Lab route, surface, renderer, or fallback contribution as the target. The conformance verifier records this asruntime.missing-pack-diagnostic. - Reusing the same route path from another module fails composition unless the host supplies an explicit path alias or replacement policy.
- Reusing the same tool renderer claim fails composition unless the host supplies an explicit renderer override policy.
- Passing a render pack whose
instanceIddiffers from the host-owned controller fails before render providers are exposed.
CSS conformance failures are explicit verifier failures, not visual review notes:
styles.no-forbidden-global-csscatches remote imports,:root,html,body, universal selectors, and global form control selectors.styles.all-selectors-scopedcatches selectors that do not start under.aui-root.styles.uses-aui-tokenscatches module styles that do not consume--aui-*tokens.sample-module-fail-unscoped-cssandsample-module-fail-stale-css-distare WCP-022 negative fixtures for CSS failure coverage.
State Boundary
The module does not own persistent state. API keys, base URLs, tenant policy, permissions, backend health, audit logs, storage, and network requests stay host-owned. Module React components may only keep transient UI state such as draft query text or the last result label for the current render session.
Frontend grants remain UI activation contracts, not security boundaries. Backend and host policy remain the enforcement layer.
Verification
Changing this package requires:
pnpm --filter @maya/data-lab-module buildpnpm --filter @maya/data-lab-module typecheckpnpm --filter @maya/data-lab-module testpnpm verify:sample-modulepnpm verify:sample-module:conformancepnpm verify:module-conformancenode tools/verify-public-api-surface.mjspnpm verify:source-boundariespnpm verify:docs-dx
pnpm verify:docs-dx is the preferred aggregate docs gate. Use node tools/verify-docs-structure.mjs and node tools/verify-docs-api-parity.mjs directly only when debugging the expanded docs checks.
Canonical module authoring rules stay in .docs/architecture/workspace-composition-module-authoring.md; this file documents the concrete first-party sample package and is no second spec.