In short

L1 reads plant and document signals and turns them into canonical JSON envelopes that L2 can store. It is three repos: connectors-edge at the plant (protocol adapters, a SQLite buffer and an MQTT uplink), connectors-cloud for intake and quality gates, and connectors-doc for documents such as utility bills. L1 never opens TimescaleDB, and the edge agent never writes to OT registers. The only write path is the separate Plant Box writer, which is direction. Field paths are proven against lab simulators, not at a named plant.

Status
as-built (feat/l1-complete, 2026-09-27) · contract (schemas, MQTT topics) · direction (Plant Box fast read, stamped-writer)
Conceptual layer
① Plant systems and ② Context
Repo layer
L1 connectors-edge, connectors-cloud, connectors-doc
Source
architecture section 3.6.1, section 3.6.2, section 7 · ADRs 006, 001, 028 · L1-L2-DATA-PLANE.md

L1 reads plant and document signals and turns them into canonical JSON that L2 can store. It never opens Timescale. The edge agent never writes OT registers; the only write path is the separate Plant BoxPlant-side computer for the fast loop (direction; D4) stamped-writer (D10, direction). Energy and waste is one outcome; utility bills are one document family on the people door.

LabelMeaning
as-builtCode on the three connector repos’ feat/l1-complete (not a claim about main)
contractSchemas and MQTT topics in this pack
simulator-provenField path proven against lab simulators (T1 11–13, B6). Not a named plant

Workspace proof: docs/audits/l1-layer-completeness.md · docs/plans/l1-complete/{E1_RESULTS,R1_BOOT,T1_TRIALS}.md.


Repos (three doors)#

RepoJobMust not
connectors-edgePlant gateway: protocol adapters + context-agent → tag/pack map → SQLite buffer (prune flushed ~72h) → MQTT uplinkOT write; inbound plant HTTP as SoR; L2_DATABASE_URL
connectors-cloudCloud door: MQTT/HTTP intake → schema + quality gate → Postgres outbox → HTTP relay to L2Plant protocol poll; L2 SQL
connectors-docDocument ingest PWA: photos, scans, PDFs, CSV/XLSX; utility bills one family with ₹ gate → review → explicit MQTT publishMQTT consumer; outbox writer; L2 SQL

connectors-doc was renamed from connectors-bill. Payload names (bill_line, …/bills, discom_bill) stay.


Data flow#

L1 connect flow

connectors-cloud

connectors-edge

Plant and people

Plant OT and IT

connectors-doc PWA

edge-agent and context-agent

Mosquitto MQTT stamped/v1

ingest and outbox

schema and quality gate
fail-closed

POST L2 /v1/ingest/records

↩ DLQ on invalid payload

How to read it.

  1. Edge polls/subscribes (Modbus, MQTT/Sparkplug, OPC UA, filewatch, REST, historian Postgres/MySQL/sqlite, MTConnect, BACnet/IP read-only, DLMS read-only LN Low, fake) → RawReading → signed mapping → MQTT under stamped/v1/{org}/{plant}/…. context-agent maps IT file/REST/SQL packs. Unmapped → unmapped_tag, never a zero.
  2. Doc extracts bill_line and plant docs. recompute_bill ±₹1 sets extraction.validated. Missing printed total fails. Status stays review until POST /v1/documents/{id}/publish.
  3. Cloud validates fail-closed against contracts, refuses bill_line unless extraction.validated=true (bill_unvalidated → DLQ), SHA-256 dedupe, wraps StampedRecordEnvelope, writes l1_outbox, relays POST L2 /v1/ingest/records. Invalid → DLQ. Ingest MQTT is a persistent session (MQTT_CLIENT_ID, clean_session=False, MQTT 3.1.1). Broker persistence queues QoS 1 while ingest is down.

Build now: three-door uplink into L2. Later: Plant Box fast read and stamped-writer on site (D4, ADR-034, ADR-035).

View Mermaid source
flowchart TB
    %% house-style: l1-connect-flow
    subgraph sources["Plant and people"]
        direction LR
        plant["Plant OT and IT"]
        pwa["connectors-doc PWA"]
    end
    subgraph edgeDoor["connectors-edge"]
        direction LR
        edge["edge-agent and context-agent"]
    end
    mqtt["Mosquitto MQTT stamped/v1"]
    subgraph cloudDoor["connectors-cloud"]
        direction LR
        cloud["ingest and outbox"]
        gate{{"schema and quality gate<br/>fail-closed"}}
        l2["POST L2 /v1/ingest/records"]
    end
    dlq(["↩ DLQ on invalid payload"])
    plant --> edge
    pwa --> mqtt
    edge --> mqtt
    mqtt --> cloud --> gate --> l2
    gate -.-> dlq

    classDef govc fill:#fff4d6,stroke:#c99a2e,color:#000
    classDef agentc fill:#e8f0ff,stroke:#5b7bd5,color:#000
    classDef loopc fill:#eef7ee,stroke:#4f9a4f,color:#000
    class gate govc
    class pwa agentc
    class dlq loopc

There is no POST /v1/bills. HTTP ingest is /v1/measurements, /v1/production-orders, /v1/context. bill_line over HTTP is accept(..., transport="http").


Record catalog (contract)#

Record typeTypical sourceNotes
measurementedgeLive + historian backfill
eventedge health / doc arrivalunmapped_tag lives here
production_record / production_orderedge MES-lite / doc exports
bill_linedocL1 cloud refuses unless extraction.validated=true. Schema boolean is not const: true
Context (asset_state, batches, flow, maintenance, quality, materials, shift, rates)edge context-agent / doc sheetsADR-019 · MQTT …/context wrapper

Schemas: contracts/schemas/ · topics: contracts/TOPICS.md.


Field protocols and SQL#

PathAs-builtBound
MTConnectHTTP/XML /current + /sample; sim mtconnect/demo:2.7Simulator-proven. No FOCAS
BACnet/IPgo-bacnet ReadProperty / RPM only; UDP 47808Simulator-proven. No WriteProperty*
DLMS/COSEMIn-house GET, LN, WRAPPER, Low/LLSSimulator-proven. Not HLS
Historian / sql_rowssqlite + Postgres (pgx) + MySQL; session read-only + ValidateSelectWrite statements fail at DB

Fast loop on the Plant Box (direction)#

Nothing in this section is as-built. Design: ../fast-loop/02-fast-read-and-writer.md · topics and owners: ../fast-loop/07-interfaces-and-ownership.md.

PieceRepoJobDecision
Fast readconnectors-edgeSub-second reads (1 s heater, 60 s heat treatment) published to the plant-only broker on stamped/v1/{org}/{plant}/fast/{line}/{asset}/{signal}; carries PLC time and edge receive time; the normal uplink path is unchangedADR-034
Time syncconnectors-edgeNTP/PTP discipline on the Plant Box; a clock jump over 2 s flags the window and stops the writerADR-034
stamped-writerconnectors-edge (separate binary and process)Only writer to the PLC. Writes allow-listed process setpoints within signed limits, on twin heartbeat, restores operator values on loss; result on writer/write/resultADR-035, D10
sync-agentconnectors-edgeStore-and-forward of twin records (records/{type}) and fast batches to L2; outbound pull of the signed plant_box_config bundle07 section 4
Zones and conduitsSite deploymentPlant Box sits in its own zone; the writer is the single conduit to the PLC; only outbound connections to the cloud (IEC 62443 style)05, D4

Safety asymmetry. A remote change can only switch writes off. Switching writes on for a tag or shift takes a named person at the plant. The allow-list, limits and hazard rows live in the site packVersioned, owner-reviewed plant configuration including topology, signed by the production head.

Hard rules#

RuleWhy
Read-only OT by default; edge agent never writesArchitecture section 7 and master document section 7 (control today). The only exception (direction) is stamped-writer: separate process, allow-listed tags, signed limits, staged per tag, plant-approved, heartbeat-bound, every write recorded
No L2_DATABASE_URL in L1Only L2 opens Timescale for plant truth
Bill ₹ gate + explicit publishOCR must not invent charges; a person publishes
Cloud refuses unvalidated bill_lineBad money does not become an outbox fact
Schema fail-closed at cloudBad payload → DLQ, not silent store

Master document section 7 (control today) also binds: quality holds, maintenance authorisation, dispatch and master data stay with the plant; no invented currency precision.


Integration playbooks under ../../handoff/connectors/. Prefer this page for architecture; handoffs may still carry older build detail.

Page history: last 5 changes
  1. 2026-10-07 docs(technical): rewrite layers/ and L1-L2-DATA-PLANE.md to the architecture 322bf46
  2. 2026-10-03 docs(layers): L1 fast read, writer and sync; L2 tables; L5 relay and budgets; L6 surfaces; data-plane topics 5127af9
  3. 2026-10-03 docs(decisions): renumber live ADRs 001-032 in order, mark withdrawn refs ADR-W###, repoint withdrawn links to archive, note partial supersessions 36c944e
  4. 2026-09-27 docs(l1): layer page from trial evidence 9f80101
  5. 2026-09-27 docs(l1): connectors-doc across architecture and handoff a86cfcb

Diagram

100%

Search the architecture