Skip to main content

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

ExportStatusPurpose
@maya/data-lab-moduleimplementedHeadless metadata, contract, factory, handles, and runtime binding helper.
@maya/data-lab-module/render-packimplementedExplicit React render pack factory for host-provided controllers.
@maya/data-lab-module/fixturesimplementedAuthoring fixtures generated from public factories.
@maya/data-lab-module/styles.cssimplementedExplicit CSS subentry scoped under .aui-root .maya-data-lab-*.
@maya/data-lab-module/package.jsonimplementedTooling 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-pack coverage produces WCP-HANDLE-001 with the Data Lab route, surface, renderer, or fallback contribution as the target. The conformance verifier records this as runtime.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 instanceId differs 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-css catches remote imports, :root, html, body, universal selectors, and global form control selectors.
  • styles.all-selectors-scoped catches selectors that do not start under .aui-root.
  • styles.uses-aui-tokens catches module styles that do not consume --aui-* tokens.
  • sample-module-fail-unscoped-css and sample-module-fail-stale-css-dist are 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 build
  • pnpm --filter @maya/data-lab-module typecheck
  • pnpm --filter @maya/data-lab-module test
  • pnpm verify:sample-module
  • pnpm verify:sample-module:conformance
  • pnpm verify:module-conformance
  • node tools/verify-public-api-surface.mjs
  • pnpm verify:source-boundaries
  • pnpm 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.