Skip to main content

automation — canonical module

Tier: Public Status: Phase B-3 shipped 2026-05-19 (read path + L2 cross-workspace isolation tests); Phase C (real OpenPLC adapter) pending. Code: automation Module tier: 1 (core canonical, reserved code). License: AGPLv3 (per RFC-0001 §5). Audience: anyone consuming, contributing to, or integrating against the automation canonical module.

[!NOTE] Per-module page for automation. Indexed from docs/MODULES.md. For the substantive surface design, see docs/design/canonical-automation-module-surface.md (Accepted 2026-05-19); this page is the navigational view of what already exists in the codebase.


1. What it does

automation is the workspace-scoped runtime control surface for physical equipment. The first reference adapter (brewery.openplc.v1) bridges an OpenPLC supervisor running brewery fermenter / kettle / pump logic into the platform. The module owns:

  • a typed inventory of vessels (fermenters, kettles, HLT, etc.) keyed by workspace-unique code,
  • the adapter SDK contract every connector implements,
  • the Modbus mailbox mirror that the brewery adapter aligns to,
  • the AI tools that let the workspace AI consultant read vessel state, and
  • the tier-limit slice that gates how many vessels and connected adapters a workspace can carry.

It does not own scheduling, capacity planning, or "next planned use" views — those belong to the future crp canonical module (surface boundary guardrail).


2. The four slices (β layout per RFC-0002)

SlicePathStatus
APIservices/api/src/modules/automation/Shipped (B-3)index.ts with registerAutomationModule, routes/automationVesselsRoutes.ts, services/vesselsService.ts, adapters/mockAdapter.ts.
Webapps/web/app/[locale]/(automation)/Shipped (B-3); aligned per Week-1 audit (RFC-0006 + web-route-group-audit.md)vessels/page.tsx (vessel list at /en/vessels), vessels/[vesselCode]/page.tsx (vessel detail at /en/vessels/<code>). Next.js route group (automation)/ does not contribute a URL segment per RFC-0002 §3; the canonical static sub-segment is vessels and the module declares ownedUrlSegments: ["vessels"] via registerWebModule(). The pre-audit layout had (automation)/page.tsx + (automation)/[vesselCode]/page.tsx at the group root — both violated the two β disciplines and produced a routing collision that left /en/automation unreachable.
Native(apps/native/src/modules/automation/ per RFC-0002 §3)Not yet wired. Native surface for automation is pending; mock adapter + web read path are sufficient for Phase B.
Contractspackages/canonical/automation/contracts/@umbraculum/automation-contractsShipped. Adapter SDK types, CONTRACT_VERSION, MAILBOX_SPEC (356 entries mirrored from the OpenPLC sister repo).

Postgres schema name is automation (services/api/prisma/schema.prisma — see the automation.vessels, automation.adapter_connections, automation.alarm_events tables).


3. Registration

The module is wired into the Fastify app via @umbraculum/module-sdk's registerModule(). From services/api/src/modules/automation/index.ts:

export function registerAutomationModule(app: FastifyInstance): void {
const alreadyRecorded = listRegisteredModules().some(
(m) => m.code === MODULE_CODE,
);

if (!alreadyRecorded) {
registerModule(app, {
code: MODULE_CODE,
prismaSchema: "automation",
addonCodes: ["automation_module"],
// Routes registered via per-app `app.register(...)` below so they
// attach on every `buildApp()` call. `registerModule` records the
// metadata once per process; if `routes:` were passed here, the
// guarded first-call path would register routes too, but
// second-and-later `buildApp()` calls (test workers) would skip
// both metadata AND routes — leaving the second app without the
// module's routes wired.
routes: [],
});
}

app.register(automationVesselsRoutes);
}

tierLimits is not yet contributed — it lands with the first vessel-create route in Phase C, contributing maxVessels and maxAdaptersConnected per canonical-automation-module-surface.md §8.2. AI tools are registered separately at app boot — see §5 below.


4. HTTP routes (Phase B-3, read-only)

MethodPathAuthResponse shapeStatus
GET/automation/vesselssession + active workspaceVesselListResponseSchema (from @umbraculum/automation-contracts)Shipped — deterministic code asc ordering. NB: API route path is /automation/vessels (unchanged); the user-facing web URL is /en/vessels (no /automation prefix).
GET/automation/vessels/:codesession + active workspaceVesselStateResponseSchemaShipped — 404 on workspace-mismatch (no cross-workspace leakage). Web URL: /en/vessels/<code>.
POST/automation/vesselsPhase CTBDPending
PATCH/automation/vessels/:codePhase CTBD — mode/target-temp setpointPending

The route source: automationVesselsRoutes.ts. Both shipped routes follow the canonical L2 cross-workspace isolation pattern from the PR3 handoff — service-layer assertMembership plus response parsing through Zod schemas at the boundary (RFC-0003).

OpenAPI: shipped routes appear under tag automation in the committed alpha partial spec — API-OPENAPI.md; artifact openapi.json.


5. AI tools

Two read-scope tools register into the platform's AI orchestrator at app boot (services/api/src/services/ai/tools/automation/index.ts):

Tool namePurposeScopes
automation.listVesselsPaginated fleet snapshot with filtersread
automation.vesselStateOne vessel or fleet snapshotread

Phase D adds automation.activeAlarms, automation.adapterHealth, automation.setpointHistory (per canonical-automation-module-surface.md §8.1). Write-scope tools (mode change, target-temp setpoint) wait for Phase E (H1 2027+) and human-in-the-loop approvals.


6. Adapter SDK contract — what third parties implement

Third-party adapter authors pin only @umbraculum/automation-contracts and @umbraculum/module-sdk. Four surfaces are exported from the contracts package:

ExportWhat it pins
CONTRACT_VERSION + classifyContractVersionSkewVersion-handshake primitives. Major mismatch → adapter refuses to connect. Minor mismatch → warn on adapterHealth. Patch differences silent. (surface design §12.2)
MailboxSpec / MailboxEntry typesTyped view of the OpenPLC sister-repo PI_* Modbus registers. The sister repo is the source of truth; this package mirrors.
MAILBOX_SPEC frozen constantValidated runtime mirror — 356 entries as of 2.0.1-dev. Loaded from data/mailbox.json and asserted at module-load (loud failure on drift).
AutomationAdapterDefinitionThe adapter contract itself — kind, protocol, capabilities, connect / disconnect / readSnapshot / optional applyCommand.

A built-in reference adapter for development and testing lives in services/api/src/modules/automation/adapters/mockAdapter.ts (automation.mock.v0). It's deterministic, has no real Modbus dependency, and is what every test uses. The real brewery.openplc.v1 lands in Phase C.


7. Data model (Prisma automation schema)

Three tables (services/api/prisma/schema.prisma, surface sketch in canonical-automation-module-surface.md §7):

  • Vessel — runtime instance: workspace-scoped code, kind, mode, current/target temps, alarm state, last-seen-at. Optional FK to brewery.EquipmentProfile (Prisma cross-schema @relation per RFC-0010 and surface design §12.3).
  • AdapterConnection — one per installed PLC connection: adapterKind, contractVersion, runtimeVersion, status. Carries the version handshake.
  • AutomationAlarmEvent — open/cleared alarm records with code / severity / message / raisedAt / clearedAt. Phase D enables raise-and-clear flow.

The cross-schema FK to brewery.equipment_profiles uses a formal Prisma @relation (RFC-0010; surface design §12.3).


8. Tier limits (planned)

Per canonical-automation-module-surface.md §8.2. Lands with Phase C vessel-create route:

FieldMeaningIllustrative numbers
maxVesselsVessels per workspacefree 2 / premium 8 / pro 24 / pro_plus 100
maxAdaptersConnectedAdapters in connected statefree 0 / premium 1 / pro 2 / pro_plus 10
automationAiToolsEnabledGate for the automation.* AI tool familyfree false / paid true

Stripe / RevenueCat addon code: automation_module.


9. Phase plan and timeline

From canonical-automation-module-surface.md §9 (working assumption — H2 2026 / H1 2027+):

PhaseWindowDeliverableStatus
A — ContractsQ3 2026@umbraculum/automation-contracts, adapter SDK types, mailbox mirror, CONTRACT_VERSIONDone 2026-05-19
B — Read pathQ3–Q4 2026Prisma schema, mock adapter, vesselState/listVessels routes + AI tools, read-only (automation)/ webDone 2026-05-19 (B-1 / B-2 / B-3)
C — Brewery adapterQ4 2026brewery.openplc.v1 bench Modbus; adapterHealth; version handshake enforcementPending — unblocked
D — AlarmsQ4 2026AutomationAlarmEvent raise/clear, activeAlarms tool, integration-metadata bridgePending
E — Write proposalsH1 2027+Human-in-the-loop setpointsDeferred

10. How to consume automation from another module or vertical

If you are building a Tier 6 vertical configuration (e.g. a distillery or kombucha vertical) that wants automation:

  1. Add @umbraculum/automation-contracts to your vertical's dependencies.
  2. If you ship a vertical-specific adapter (distillery.somecontroller.v1), implement AutomationAdapterDefinition. Use the existing brewery-side approach as a model.
  3. Register your adapter through the connection-config UI surfaced by the (automation)/ shell — your vertical does not ship a parallel automation web page. The platform's (automation)/ shell is the canonical UX; your vertical contributes adapter packs.
  4. Respect the surface boundary — no scheduling or planning data on the automation surface; that's crp's job.

11. Cross-references