Integrate Round 3 OtOpcUa corrections into the plan files (goal-state.md, roadmap.md) and append a Round 3 addendum to the corrections doc for audit trail.

goal-state.md: schemas-repo seed paragraph (line 574) now reflects the `_base` equipment-class template (universal cross-machine baseline that every other class extends), explicit alignment to OPC UA Companion Spec OPC 40010 (Machinery) for the Identification component + MachineryOperationMode enum, OPC UA Part 9 for alarm-summary fields (HasActiveAlarms, ActiveAlarmCount, HighestActiveAlarmSeverity), ISO 22400 for lifetime counters (TotalRunSeconds, TotalCycles) that feed Availability + Performance KPIs, the canonical state vocabulary declared in `_base.stateModel`, and the OtOpcUa central config DB extension with 9 nullable OPC 40010 identity columns (Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, AssetLocation, ManufacturerUri, DeviceManualUri). Updated format-decisions count from 8 to 10 (added D9 _base+extends inheritance, D10 category→folder mapping). Multi-identifier section (line 156) gains a paragraph describing the OPC 40010 fields as additional first-class metadata beyond the five identifiers, with the operator-set / driver-dynamic-override pattern documented.

roadmap.md: OtOpcUa Year 1 cell (line 66) gains the universal `_base` equipment-class template seeded by the OtOpcUa team, with explicit OPC 40010 / OPC UA Part 9 / ISO 22400 references and the rationale ("avoids per-class drift in identity / state / alarm field naming and ensures every machine in the estate exposes the same baseline metadata regardless of vendor").

handoffs/otopcua-corrections-2026-04-17.md: appended a Round 3 addendum capturing the four follow-on additions (ACL design closing B1, dev-environment two-tier model, cutover scope removal closing C5, `_base` template + OPC 40010 columns building on B2). Updated summary table marks B1 / C1 / C5 as CLOSED, B2 as PARTIALLY CLOSED. Round 3 additions are committed in lmxopcua at `4903a19` and `d8fa3a0`, and in 3yearplan at `5953685` and `cd85159`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-17 13:04:18 -04:00
parent cd85159951
commit 8704f9e455
3 changed files with 44 additions and 3 deletions

View File

@@ -165,6 +165,8 @@ The plan commits to a **multi-identifier model** — the v2 implementation desig
**Three operator-set fields** (MachineCode, ZTag, SAPID), **two system-generated** (EquipmentUuid, EquipmentId). CSV imports match by `EquipmentUuid` for updates; rows without UUID create new equipment with system-generated identifiers. **Three operator-set fields** (MachineCode, ZTag, SAPID), **two system-generated** (EquipmentUuid, EquipmentId). CSV imports match by `EquipmentUuid` for updates; rows without UUID create new equipment with system-generated identifiers.
Equipment also carries **OPC UA Companion Spec OPC 40010 (Machinery) Identification fields** as first-class metadata — Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, AssetLocation (free-text supplementary to the UNS path), ManufacturerUri, DeviceManualUri. These are operator-set static metadata (Manufacturer + Model required, the rest optional) exposed on the OPC UA equipment node under the OPC 40010-standard `Identification` sub-folder. Drivers that can read these dynamically (FANUC `cnc_sysinfo()`, Beckhoff `TwinCAT.SystemInfo`, etc.) override the static value at runtime. The full universal cross-machine signal set lives in the `_base` equipment-class template in the schemas repo; every other class extends it.
**`ExternalIdReservation` table — fleet-wide uniqueness for ZTag and SAPID across rollback and re-enable.** Fleet-wide uniqueness for external identifiers cannot be expressed within generation-versioned tables because old generations and disabled equipment can still hold the same values — rollback or re-enable would silently reintroduce duplicates that corrupt downstream ERP/SAP joins. A dedicated `ExternalIdReservation` table sits **outside generation versioning**; `sp_PublishGeneration` reserves IDs atomically at publish; FleetAdmin-only `sp_ReleaseExternalIdReservation` (audit-logged, requires reason) is the only path to free a value for reuse. This is a precedent: some cross-generation invariants need their own non-versioned tables. When ACL design is scoped (see OtOpcUa → Authorization model), check whether any ACL grant has a similar rollback-reuse hazard. **`ExternalIdReservation` table — fleet-wide uniqueness for ZTag and SAPID across rollback and re-enable.** Fleet-wide uniqueness for external identifiers cannot be expressed within generation-versioned tables because old generations and disabled equipment can still hold the same values — rollback or re-enable would silently reintroduce duplicates that corrupt downstream ERP/SAP joins. A dedicated `ExternalIdReservation` table sits **outside generation versioning**; `sp_PublishGeneration` reserves IDs atomically at publish; FleetAdmin-only `sp_ReleaseExternalIdReservation` (audit-logged, requires reason) is the only path to free a value for reuse. This is a precedent: some cross-generation invariants need their own non-versioned tables. When ACL design is scoped (see OtOpcUa → Authorization model), check whether any ACL grant has a similar rollback-reuse hazard.
**Path** (the 5-level UNS hierarchy address) is a **sixth** identifier but is **not** stored on the equipment node as a flat property — it is the node's **browse path** by construction. Path can change (equipment moves, area renamed); UUID and EquipmentId cannot. **Path** (the 5-level UNS hierarchy address) is a **sixth** identifier but is **not** stored on the equipment node as a flat property — it is the node's **browse path** by construction. Path can change (equipment moves, area renamed); UUID and EquipmentId cannot.
@@ -571,12 +573,21 @@ The plan already delivers the infrastructure for a cross-system canonical model
This subsection makes that declaration. It is the plan's answer to **Digital Twin Use Cases 1 and 3** (see **Strategic Considerations → Digital twin**) and — independent of digital twin framing — is load-bearing for pillar 2 (analytics/AI enablement) because a canonical model is what makes "not possible before" cross-domain analytics possible at all. This subsection makes that declaration. It is the plan's answer to **Digital Twin Use Cases 1 and 3** (see **Strategic Considerations → Digital twin**) and — independent of digital twin framing — is load-bearing for pillar 2 (analytics/AI enablement) because a canonical model is what makes "not possible before" cross-domain analytics possible at all.
> **Schemas-repo dependency — partially resolved.** The OtOpcUa team has contributed an initial seed at [`schemas/`](../schemas/) (temporary location in the 3-year-plan repo until the dedicated `schemas` repo is created — Gitea push-to-create is disabled). The seed includes: JSON Schema format definitions (`format/equipment-class.schema.json`, `format/tag-definition.schema.json`, `format/uns-subtree.schema.json`), the **FANUC CNC pilot equipment class** (`classes/fanuc-cnc.json` — 16 signals + 3 alarm definitions + state-derivation notes), a worked UNS subtree example (`uns/example-warsaw-west.json`), and documentation (`docs/overview.md`, `docs/format-decisions.md` with 8 numbered decisions, `docs/consumer-integration.md`). The **UNS hierarchy snapshot** (a per-site equipment-instance walk) feeds the initial hierarchy definition. Core driver scope is already resolved by the v2 implementation team's committed driver list. > **Schemas-repo dependency — partially resolved.** The OtOpcUa team has contributed an initial seed at [`schemas/`](../schemas/) (temporary location in the 3-year-plan repo until the dedicated `schemas` repo is created — Gitea push-to-create is disabled). The seed includes:
> - JSON Schema format definitions (`format/equipment-class.schema.json` with an `extends` field for class inheritance, `format/tag-definition.schema.json`, `format/uns-subtree.schema.json`)
> - A **`_base` equipment-class template** (`classes/_base.json` — the universal cross-machine baseline that every other class extends, providing 27 signals across Identity / Status / Alarm / Diagnostic / Counter / Process categories + 2 universal alarms + the canonical state vocabulary). Aligned with **OPC UA Companion Spec OPC 40010 (Machinery)** for the Identification component (Manufacturer, Model, ProductInstanceUri, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, ManufacturerUri, DeviceManual, AssetLocation) and MachineryOperationMode enum, **OPC UA Part 9** for alarm-summary fields (HasActiveAlarms, ActiveAlarmCount, HighestActiveAlarmSeverity), **ISO 22400** for lifetime counters that feed Availability + Performance KPIs (TotalRunSeconds, TotalCycles), and the canonical state vocabulary (Running / Idle / Faulted / Starved / Blocked) declared in `_base.stateModel`
> - The **FANUC CNC pilot equipment class** (`classes/fanuc-cnc.json`, `extends: "_base"` — adds 14 CNC-specific signals on top of the inherited base) + 3 FANUC-specific alarm definitions
> - A worked UNS subtree example (`uns/example-warsaw-west.json`)
> - Documentation: `docs/overview.md`, `docs/format-decisions.md` with 10 numbered decisions (the original 8 plus D9 covering `_base` + `extends` inheritance and D10 covering the signal `category` → OPC UA folder placement table), `docs/consumer-integration.md`
>
> The **UNS hierarchy snapshot** (a per-site equipment-instance walk) feeds the initial hierarchy definition. Core driver scope is already resolved by the v2 implementation team's committed driver list.
>
> **OtOpcUa central config DB extended** (per lmxopcua decisions #138 + #139): the Equipment table gains 9 nullable columns for the OPC 40010 Identification fields (Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, AssetLocation, ManufacturerUri, DeviceManualUri) so operator-set static identity has a first-class home; drivers that can read these dynamically (FANUC `cnc_sysinfo()`, Beckhoff `TwinCAT.SystemInfo`, etc.) override the static value at runtime. Exposed on the OPC UA equipment node under the OPC 40010-standard `Identification` sub-folder per the `category` → folder mapping in `schemas/docs/format-decisions.md` D10.
> >
> **Still needs cross-team ownership:** > **Still needs cross-team ownership:**
> - Name an owner team for the schemas content (it's consumed by OT and IT systems alike — OtOpcUa, Redpanda, dbt) > - Name an owner team for the schemas content (it's consumed by OT and IT systems alike — OtOpcUa, Redpanda, dbt)
> - Decide whether to move to a dedicated `gitea.dohertylan.com/dohertj2/schemas` repo (proposed) or keep as a 3-year-plan sub-tree > - Decide whether to move to a dedicated `gitea.dohertylan.com/dohertj2/schemas` repo (proposed) or keep as a 3-year-plan sub-tree
> - Ratify or revise the 8 format decisions in `schemas/docs/format-decisions.md` > - Ratify or revise the 10 format decisions in `schemas/docs/format-decisions.md`
> - Establish the CI gate for JSON Schema validation > - Establish the CI gate for JSON Schema validation
> - Decide on consumer-integration plumbing for Redpanda Protobuf code-gen and dbt macro generation per `schemas/docs/consumer-integration.md` > - Decide on consumer-integration plumbing for Redpanda Protobuf code-gen and dbt macro generation per `schemas/docs/consumer-integration.md`

View File

@@ -431,3 +431,33 @@ Neither of these affects the handoff or this corrections doc directly.
| **Addendum hardening fixes** | **4** | EquipmentId system-generated; ExternalIdReservation table; same-cluster namespace binding; Namespace generation-versioned | | **Addendum hardening fixes** | **4** | EquipmentId system-generated; ExternalIdReservation table; same-cluster namespace binding; Namespace generation-versioned |
The hardening fixes are committed in `lmxopcua` branch `v2` at commit `a59ad2e` (2026-04-17). Decisions #122125 in `lmxopcua/docs/v2/plan.md` carry the rationale. The hardening fixes are committed in `lmxopcua` branch `v2` at commit `a59ad2e` (2026-04-17). Decisions #122125 in `lmxopcua/docs/v2/plan.md` carry the rationale.
---
## Round 3 additions (2026-04-17, post-integration)
After the plan team integrated the original 19 corrections + 4 hardening fixes, the OtOpcUa team made a follow-on set of additions that landed directly in the plan files (`goal-state.md`, `roadmap.md`) plus the `schemas/` seed. Captured here for audit trail; no further action required from the plan team.
**ACL design committed** (lmxopcua decisions #129132, closes B1 fully) — `NodePermissions` bitmask covering Browse / Read / Subscribe / HistoryRead / WriteOperate / WriteTune / WriteConfigure / AlarmRead / AlarmAcknowledge / AlarmConfirm / AlarmShelve / MethodCall + bundles, 6-level scope hierarchy with default-deny + additive grants, generation-versioned `NodeAcl` table, cluster-create workflow seeding the v1 LDAP-role-to-permission map for v1 → v2 consumer migration parity, Admin UI ACL tab + bulk grant + permission simulator. Phase 1 ships the schema + Admin UI + evaluator unit tests; per-driver enforcement lands in each driver's phase. Doc: `lmxopcua/docs/v2/acl-design.md`.
**Dev-environment two-tier model** (decisions #133137) — inner-loop on developer machines (in-process simulators only) + integration on a single dedicated Windows host with Docker WSL2 backend so TwinCAT XAR VM can run in Hyper-V alongside containerized simulators. Galaxy testing stays on developer machines that have local Aveva licenses; integration host doesn't carry the license. Doc: `lmxopcua/docs/v2/dev-environment.md`.
**Cutover removed from OtOpcUa v2 scope** (decision #136, closes C5 fully) — owned by a separate integration / operations team (not yet named). OtOpcUa team's responsibility ends at Phase 5 (all drivers built, all stability protections in place, full Admin UI shipped including ACL editor). Already integrated into `roadmap.md` line 66 by the plan team in commit `68dbc01`.
**Schemas-repo seed** at `3yearplan/schemas/` (closes B2 partially — content available, owner team naming + dedicated repo creation still pending). Includes JSON Schema format definitions, FANUC CNC pilot, worked UNS subtree example, documentation. Already integrated into `goal-state.md` line 574 by the plan team in commit `dee56a6`.
**`_base` equipment-class template + OPC 40010 alignment** (lmxopcua decisions #138139, builds on B2 resolution) — universal cross-machine baseline that every other class extends. References OPC UA Companion Spec OPC 40010 (Machinery) for the Identification component + MachineryOperationMode enum, OPC UA Part 9 for alarm-summary fields, ISO 22400 for lifetime counters that feed Availability + Performance KPIs, the canonical state vocabulary from this handoff §"Canonical Model Integration". Equipment table extended with 9 nullable OPC 40010 identity columns (Manufacturer, Model, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, AssetLocation, ManufacturerUri, DeviceManualUri); drivers that can read these dynamically (FANUC `cnc_sysinfo()`, Beckhoff `TwinCAT.SystemInfo`, etc.) override the static value at runtime. `_base` declares 27 signals across Identity / Status / Alarm / Diagnostic / Counter / Process categories + 2 universal alarms (communication-loss, data-stale) + the canonical state vocabulary. Already integrated into `goal-state.md` line 574 (schemas-repo seed paragraph) and `goal-state.md` line 156 (multi-identifier section gains the OPC 40010 fields paragraph) and `roadmap.md` line 66/67 by this commit.
### Updated summary
| Category | Count | Notes |
|----------|------:|-------|
| A. Inaccuracies | 2 | Both wording/framing issues; no architectural conflict |
| B. Missing constraints | 3 | B1 ACLs **CLOSED** (#129132); B2 schemas-repo **PARTIALLY CLOSED** (seed contributed; owner-team + dedicated-repo TBD); B3 cert-distribution remains operational concern |
| C. Architectural decisions to revisit | 6 | C1 driver list **CLOSED** (#128); C5 cutover scope **CLOSED** (#136 — out of v2 scope); others still flagged |
| D. Resolved TBDs | 4 | Pilot class, schemas repo format, ACL location, enterprise shortname (unresolved) |
| E. New TBDs | 4 | UUID-gen authority, Aveva validation, multi-cluster site addressing, cluster-endpoint mental model |
| **Addendum hardening fixes** | **4** | EquipmentId system-generated; ExternalIdReservation table; same-cluster namespace binding; Namespace generation-versioned |
| **Round 3 additions** | **4** | ACL design (#129132); dev-environment two-tier (#133137); cutover scope removal (#136); `_base` template + OPC 40010 columns (#138139) |
The Round 3 additions are committed in `lmxopcua` branch `v2` at commits `4903a19` (ACL + dev-env + cutover removal) and `d8fa3a0` (Equipment OPC 40010 columns + Identification panel), and in `3yearplan` at commits `5953685` (schemas seed) and `cd85159` (`_base` template + OPC 40010 alignment + format-decisions D9 + D10).

View File

@@ -64,7 +64,7 @@ The roadmap is laid out as a 2D grid — **workstreams** (rows) crossed with **y
| Workstream | **Year 1 — Foundation** | **Year 2 — Scale** | **Year 3 — Completion** | | Workstream | **Year 1 — Foundation** | **Year 2 — Scale** | **Year 3 — Completion** |
|---|---|---|---| |---|---|---|---|
| **OtOpcUa** | **Evolve LmxOpcUa into OtOpcUa** — extend the existing in-house OPC UA server to add (a) a new equipment namespace with single session per equipment via native protocols translated to OPC UA (committed core drivers: OPC UA Client, Modbus TCP, AB CIP, AB Legacy, S7, TwinCAT, FOCAS, plus Galaxy carried forward), and (b) clustering (non-transparent redundancy, 2-node per site) on top of the existing per-node deployment. **Driver stability tiers:** Tier A in-process (Modbus, OPC UA Client), Tier B in-process with guards (S7, AB CIP, AB Legacy, TwinCAT), Tier C out-of-process (Galaxy — bitness constraint, FOCAS — uncatchable AVE). Core driver list confirmed by v2 implementation team (protocol survey no longer needed for driver scoping). **UNS hierarchy snapshot walk** — per-site equipment-instance discovery (site/area/line/equipment + UUID assignment) to feed the initial schemas-repo hierarchy definition and canonical model; target done Q1Q2. **ACL model designed and committed** (decisions #129132): 6-level scope hierarchy, `NodePermissions` bitmask, generation-versioned `NodeAcl` table, Admin UI + permission simulator. Phase 1 ships before any driver phase. **Deploy OtOpcUa to every site** as fast as practical. **Begin tier 1 cutover (ScadaBridge)** at large sites. **Prerequisite: certificate-distribution** to consumer trust stores before each cutover. **Aveva System Platform IO pattern validation** — Year 1 or early Year 2 research to confirm Aveva supports upstream OPC UA data sources, well ahead of Year 3 tier 3. _TBD — first-cutover site selection; **cutover plan owner** (not OtOpcUa — a separate integration/operations team, per decision #136, not yet named); enterprise shortname for UNS hierarchy root; schemas-repo owner team and dedicated repo creation._ | **Complete tier 1 (ScadaBridge)** across all sites. **Begin tier 2 (Ignition)** — Ignition consumers redirected from direct-equipment OPC UA to each site's OtOpcUa, collapsing WAN session counts from *N per equipment* to *one per site*. **Build long-tail drivers** on demand as sites require them. Resolve Warsaw per-building multi-cluster consumer-addressing pattern (consumer-side stitching vs site-aggregator OtOpcUa instance). _TBD — per-site tier-2 rollout sequence._ | **Complete tier 2 (Ignition)** across all sites. **Execute tier 3 (Aveva System Platform IO)** with compliance stakeholder validation — the hardest cutover because System Platform IO feeds validated data collection. Reach steady state: every equipment session is held by OtOpcUa, every downstream consumer reads OT data through it. _TBD — per-equipment-class criteria for System Platform IO re-validation._ | | **OtOpcUa** | **Evolve LmxOpcUa into OtOpcUa** — extend the existing in-house OPC UA server to add (a) a new equipment namespace with single session per equipment via native protocols translated to OPC UA (committed core drivers: OPC UA Client, Modbus TCP, AB CIP, AB Legacy, S7, TwinCAT, FOCAS, plus Galaxy carried forward), and (b) clustering (non-transparent redundancy, 2-node per site) on top of the existing per-node deployment. **Driver stability tiers:** Tier A in-process (Modbus, OPC UA Client), Tier B in-process with guards (S7, AB CIP, AB Legacy, TwinCAT), Tier C out-of-process (Galaxy — bitness constraint, FOCAS — uncatchable AVE). Core driver list confirmed by v2 implementation team (protocol survey no longer needed for driver scoping). **UNS hierarchy snapshot walk** — per-site equipment-instance discovery (site/area/line/equipment + UUID assignment) to feed the initial schemas-repo hierarchy definition and canonical model; target done Q1Q2. **ACL model designed and committed** (decisions #129132): 6-level scope hierarchy, `NodePermissions` bitmask, generation-versioned `NodeAcl` table, Admin UI + permission simulator. Phase 1 ships before any driver phase. **Deploy OtOpcUa to every site** as fast as practical. **Begin tier 1 cutover (ScadaBridge)** at large sites. **Prerequisite: certificate-distribution** to consumer trust stores before each cutover. **Aveva System Platform IO pattern validation** — Year 1 or early Year 2 research to confirm Aveva supports upstream OPC UA data sources, well ahead of Year 3 tier 3. _TBD — first-cutover site selection; **cutover plan owner** (not OtOpcUa — a separate integration/operations team, per decision #136, not yet named); enterprise shortname for UNS hierarchy root; schemas-repo owner team and dedicated repo creation._ | **Complete tier 1 (ScadaBridge)** across all sites. **Begin tier 2 (Ignition)** — Ignition consumers redirected from direct-equipment OPC UA to each site's OtOpcUa, collapsing WAN session counts from *N per equipment* to *one per site*. **Build long-tail drivers** on demand as sites require them. Resolve Warsaw per-building multi-cluster consumer-addressing pattern (consumer-side stitching vs site-aggregator OtOpcUa instance). _TBD — per-site tier-2 rollout sequence._ | **Complete tier 2 (Ignition)** across all sites. **Execute tier 3 (Aveva System Platform IO)** with compliance stakeholder validation — the hardest cutover because System Platform IO feeds validated data collection. Reach steady state: every equipment session is held by OtOpcUa, every downstream consumer reads OT data through it. _TBD — per-equipment-class criteria for System Platform IO re-validation._ |
| **Redpanda EventHub** | Stand up central Redpanda cluster in South Bend (single-cluster HA). Stand up bundled Schema Registry. Wire SASL/OAUTHBEARER to enterprise IdP. Create initial topic set (prefix-based ACLs). Hook up observability minimum signal set. Define the three retention tiers (`operational`/`analytics`/`compliance`). **Stand up the central `schemas` repo** with `buf` CI, CODEOWNERS, and the NuGet publishing pipeline. **Publish the canonical equipment/production/event model v1** — including the canonical machine state vocabulary (`Running / Idle / Faulted / Starved / Blocked` + any agreed additions) as a Protobuf enum, the `equipment.state.transitioned` event schema, and initial equipment-class definitions for pilot equipment. This is the foundation for Digital Twin Use Cases 1 and 3 (see `goal-state.md` → Strategic Considerations → Digital twin) and is load-bearing for pillar 2. **Pilot equipment class for canonical definition: FANUC CNC** (pre-defined FOCAS2 hierarchy already exists in OtOpcUa v2 driver design). Land the FANUC CNC class template in the schemas repo before Tier 1 cutover begins. _TBD — sizing decisions, initial topic list, canonical vocabulary ownership (domain SME group)._ | Expand topic coverage as additional domains onboard. Enforce tiered retention and ACLs at scale. Prove backlog replay after a WAN-outage drill (also exercises the Digital Twin Use Case 2 simulation-lite replay path). Exercise long-outage planning (ScadaBridge queue capacity vs. outage duration). Iterate the canonical model as additional equipment classes and domains onboard. _TBD — concrete drill cadence._ | Steady-state operation. Harden alerting and runbooks against the observed failure modes from Years 12. Canonical model is mature and covers every in-scope equipment class; schema changes are routine rather than foundational. | | **Redpanda EventHub** | Stand up central Redpanda cluster in South Bend (single-cluster HA). Stand up bundled Schema Registry. Wire SASL/OAUTHBEARER to enterprise IdP. Create initial topic set (prefix-based ACLs). Hook up observability minimum signal set. Define the three retention tiers (`operational`/`analytics`/`compliance`). **Stand up the central `schemas` repo** with `buf` CI, CODEOWNERS, and the NuGet publishing pipeline. **Publish the canonical equipment/production/event model v1** — including the canonical machine state vocabulary (`Running / Idle / Faulted / Starved / Blocked` + any agreed additions) as a Protobuf enum, the `equipment.state.transitioned` event schema, and initial equipment-class definitions for pilot equipment. This is the foundation for Digital Twin Use Cases 1 and 3 (see `goal-state.md` → Strategic Considerations → Digital twin) and is load-bearing for pillar 2. **Pilot equipment class for canonical definition: FANUC CNC** (pre-defined FOCAS2 hierarchy already exists in OtOpcUa v2 driver design). Land the FANUC CNC class template in the schemas repo before Tier 1 cutover begins. **Universal `_base` equipment-class template** seeded by the OtOpcUa team — every other class extends it via the `extends` field on the equipment-class JSON Schema. `_base` aligns to **OPC UA Companion Spec OPC 40010 (Machinery)** for the Identification component (Manufacturer, Model, ProductInstanceUri, SerialNumber, HardwareRevision, SoftwareRevision, YearOfConstruction, ManufacturerUri, DeviceManual, AssetLocation) and MachineryOperationMode enum, **OPC UA Part 9** for alarm-summary fields, and **ISO 22400** for lifetime counters that feed Availability + Performance KPIs. Avoids per-class drift in identity / state / alarm field naming and ensures every machine in the estate exposes the same baseline metadata regardless of vendor. _TBD — sizing decisions, initial topic list, canonical vocabulary ownership (domain SME group)._ | Expand topic coverage as additional domains onboard. Enforce tiered retention and ACLs at scale. Prove backlog replay after a WAN-outage drill (also exercises the Digital Twin Use Case 2 simulation-lite replay path). Exercise long-outage planning (ScadaBridge queue capacity vs. outage duration). Iterate the canonical model as additional equipment classes and domains onboard. _TBD — concrete drill cadence._ | Steady-state operation. Harden alerting and runbooks against the observed failure modes from Years 12. Canonical model is mature and covers every in-scope equipment class; schema changes are routine rather than foundational. |
| **SnowBridge** | Design and begin custom build in .NET. **Filtered, governed upload to Snowflake is the Year 1 purpose** — the service is the component that decides which topics/tags flow to Snowflake, applies the governed selection model, and writes into Snowflake. Ship an initial version with **one working source adapter** — starting with **Aveva Historian (SQL interface)** because it's central-only, exists today, and lets the workstream progress in parallel with Redpanda rather than waiting on it. First end-to-end **filtered** flow to Snowflake landing tables on a handful of priority tags. Selection model in place even if the operator UI isn't yet (config-driven is acceptable for Year 1). _TBD — team, credential management, datastore for selection state._ | Add the **ScadaBridge/Redpanda source adapter** alongside Historian. Build and ship the operator **web UI + API** on top of the Year 1 selection model, including the blast-radius-based approval workflow, audit trail, RBAC, and exportable state. Onboard priority tags per domain under the UI-driven governance path. _TBD — UI framework._ | All planned source adapters live behind the unified interface. Approval workflow tuned based on Year 2 operational experience. Feature freeze; focus on hardening. | | **SnowBridge** | Design and begin custom build in .NET. **Filtered, governed upload to Snowflake is the Year 1 purpose** — the service is the component that decides which topics/tags flow to Snowflake, applies the governed selection model, and writes into Snowflake. Ship an initial version with **one working source adapter** — starting with **Aveva Historian (SQL interface)** because it's central-only, exists today, and lets the workstream progress in parallel with Redpanda rather than waiting on it. First end-to-end **filtered** flow to Snowflake landing tables on a handful of priority tags. Selection model in place even if the operator UI isn't yet (config-driven is acceptable for Year 1). _TBD — team, credential management, datastore for selection state._ | Add the **ScadaBridge/Redpanda source adapter** alongside Historian. Build and ship the operator **web UI + API** on top of the Year 1 selection model, including the blast-radius-based approval workflow, audit trail, RBAC, and exportable state. Onboard priority tags per domain under the UI-driven governance path. _TBD — UI framework._ | All planned source adapters live behind the unified interface. Approval workflow tuned based on Year 2 operational experience. Feature freeze; focus on hardening. |
| **Snowflake dbt Transform Layer** | Scaffold a dbt project in git, wired to the self-hosted orchestrator (per `goal-state.md`; specific orchestrator chosen outside this plan). Build first **landing → curated** model for priority tags. **Align curated views with the canonical model v1** published in the `schemas` repo — equipment, production, and event entities in the curated layer use the canonical state vocabulary and the same event-type enum values, so downstream consumers (Power BI, ad-hoc analysts, future AI/ML) see the same shape of data Redpanda publishes. This is the dbt-side delivery for Digital Twin Use Cases 1 and 3. Establish `dbt test` discipline from day one — including tests that catch divergence between curated views and the canonical enums. _TBD — project layout (single vs per-domain); reconciliation rule if derived state in curated views disagrees with the layer-3 derivation (should not happen, but the rule needs to exist)._ | Build curated layers for all in-scope domains. **Ship a canonical-state-based OEE model** as a strong candidate for the pillar-2 "not possible before" use case — accurate cross-equipment, cross-site OEE computed once in dbt from the canonical state stream, rather than re-derived in every reporting surface. Source-freshness SLAs tied to the **≤15-minute analytics** budget. Begin development of the first **"not possible before" AI/analytics use case** (pillar 2). | The "not possible before" use case is **in production**, consuming the curated layer, meeting its own SLO. Pillar 2 check passes. | | **Snowflake dbt Transform Layer** | Scaffold a dbt project in git, wired to the self-hosted orchestrator (per `goal-state.md`; specific orchestrator chosen outside this plan). Build first **landing → curated** model for priority tags. **Align curated views with the canonical model v1** published in the `schemas` repo — equipment, production, and event entities in the curated layer use the canonical state vocabulary and the same event-type enum values, so downstream consumers (Power BI, ad-hoc analysts, future AI/ML) see the same shape of data Redpanda publishes. This is the dbt-side delivery for Digital Twin Use Cases 1 and 3. Establish `dbt test` discipline from day one — including tests that catch divergence between curated views and the canonical enums. _TBD — project layout (single vs per-domain); reconciliation rule if derived state in curated views disagrees with the layer-3 derivation (should not happen, but the rule needs to exist)._ | Build curated layers for all in-scope domains. **Ship a canonical-state-based OEE model** as a strong candidate for the pillar-2 "not possible before" use case — accurate cross-equipment, cross-site OEE computed once in dbt from the canonical state stream, rather than re-derived in every reporting surface. Source-freshness SLAs tied to the **≤15-minute analytics** budget. Begin development of the first **"not possible before" AI/analytics use case** (pillar 2). | The "not possible before" use case is **in production**, consuming the curated layer, meeting its own SLO. Pillar 2 check passes. |
| **ScadaBridge Extensions** | Implement **deadband / exception-based publishing** with the global-default model (+ override mechanism). Add **EventHub producer** capability with per-call **store-and-forward** to Redpanda. Verify co-located footprint doesn't degrade System Platform. _TBD — global deadband value, override mechanism location._ | Roll deadband + EventHub producer to **all currently-integrated sites**. Tune deadband and overrides based on observed Snowflake cost. Support early legacy-retirement work with outbound Web API / DB write patterns as needed. | Steady state. Any remaining Extensions work is residual cleanup or support for the tail end of Site Onboarding / Legacy Retirement. | | **ScadaBridge Extensions** | Implement **deadband / exception-based publishing** with the global-default model (+ override mechanism). Add **EventHub producer** capability with per-call **store-and-forward** to Redpanda. Verify co-located footprint doesn't degrade System Platform. _TBD — global deadband value, override mechanism location._ | Roll deadband + EventHub producer to **all currently-integrated sites**. Tune deadband and overrides based on observed Snowflake cost. Support early legacy-retirement work with outbound Web API / DB write patterns as needed. | Steady state. Any remaining Extensions work is residual cleanup or support for the tail end of Site Onboarding / Legacy Retirement. |