SMART FACTORY · READ-ONLY STATEMENT

It reads. It cannot write.

Generated from the factory agent's code on every release, as it ships in the agent's package: per protocol, the exact requests the agent can make.

Generated by scripts/factory-attestation.mjs from the factory profile's compiled driver table (agent/profiles/factory.mjs) and the package's file list (scripts/factory-agent-files.mjs). Do not edit by hand: the build checks it. Agent version 1.0.0.

The factory profile of the HOLOIN Field Agent (holoin-agent-factory.mjs) contains no write primitive on any protocol. It reads machines; it never writes to them. This is enforced in three ways, each checked on every build:

  1. The code isn't there. The package holds only what the factory program imports. The full agent's write paths, and the BACnet, KNX and SNMP drivers and their libraries, are not in it.
  2. Every write request is refused. A write, relinquish, BMS acknowledgement or drive command sent by HOLOIN is answered: "refused: this agent is the read-only factory profile — it contains no write primitive on any protocol". A configuration asking for a write, or for a protocol the profile does not contain, stops the agent at start.
  3. The write-attempt test (agent/test/factory-profile.test.mjs, in CI) runs the real factory program against a simulated machine and sends every kind of write: all are refused, and the machine receives reads only. It opens an OPC UA session on a real server: the profile holds only the read wrapper (read, browse, subscribe — a write, call or historyUpdate isn't on it), behind it the session's own write services refuse, and the variable is unchanged.

OPC UA is guarded by wrapper and build check, not compiled out. Its library, node-opcua-client, carries the Write, Call and HistoryUpdate services in its own code, which can't be removed. The factory profile never holds a raw session — only HOLOIN's wrapper exposing read, browse and subscribe — and scripts/check-factory-readonly.mjs (in the build) fails if write, call or historyUpdate is referenced anywhere in the profile's code. Modbus and SunSpec are different: their write encoders are not in this build at all. So is Beckhoff ADS: its codec has no encoder for Write, Write Control or Read Write, and its one encoding function — the only path to the wire — refuses any (command id, index group) pair outside the allow-table below before a byte is made. A PLC variable is found by name through the PLC's symbol table, which is itself read, so no symbol handle is ever asked for. Omron FINS is built the same way: its codec has no encoder for any write, fill, transfer, mode change, clock write, forced set or file command, and its one encoding function refuses any command or memory area code outside its allow-table below. So is Mitsubishi SLMP: its codec has no encoder for any write, remote operation (RUN, STOP, PAUSE, latch clear, reset), file, Clear Error, monitor registration or remote-password command, and its one encoding function refuses any (command, subcommand) or device code outside its allow-table below.

The PLC's or controller's own write protection stays the authoritative control: deploy the agent with an account that has read access only where the device offers one.

Protocol by protocol

ProtocolOperations this build can sendWritesNot builtSource
Modbus TCP01 Read Coils; 02 Read Discrete Inputs; 03 Read Holding Registers; 04 Read Input Registersnonefunction codes 05 Write Single Coil, 06 Write Single Register, 15 Write Multiple Coils and 16 Write Multiple Registers: their encoders are not in this build (the read codec refuses any function code but 01–04 before a byte is made)Modbus Application Protocol Specification V1.1b3; Modbus Messaging on TCP/IP Implementation Guide V1.0b
SunSpec (over Modbus TCP)03 Read Holding Registersnoneno SunSpec model is ever written (it rides on the Modbus read network above: function 03 only)SunSpec Information Models (agent/sunspec, release v2026-04-20)
OPC UARead (Value); CreateSubscription / CreateMonitoredItems (data changes); Browse (discovery)noneGUARDED BY WRAPPER AND BUILD CHECK, not compiled out (owner ruling 2026-10-12): node-opcua-client carries Write, Call and HistoryUpdate in its own code, which can't be removed. The driver's Value write is not in this build; the profile holds only a HOLOIN wrapper exposing read, readNamespaceArray, browse, createSubscription2, close; behind it the session's write, writeSingleNode, call, historyUpdate, addNodes, deleteNodes, addReferences, deleteReferences are replaced by refusals; and a build check fails if write, call or historyUpdate is referenced anywhere in the profile's codeOPC UA Specifications (OPC Foundation Specifications Agreement v1.15): Part 4 Services
MTConnectGET probe; GET current; GET samplenonenothing to leave out: MTConnect's API is read-only (Part 1 §5.1: "the API is read-only") — the driver sends HTTP GET of probe, current and sample, and nothing elseMTConnect Standard v2.5.0 (5 January 2026): Part 1 Fundamentals §5.1, Part 3 Observation Information Model
Beckhoff ADS (AMS/TCP)0x0001 ADS Read Device Info; 0x0004 ADS Read State; 0x0002 ADS Read; 0x0006 ADS Add Device Notification; 0x0007 ADS Delete Device NotificationnoneADS Write (0x0003), Write Control (0x0005) and Read Write (0x0009) have no encoder in this build, and the codec's encodeRequest — the only path to the wire — refuses any (command id, index group) outside its allow-table before a byte is made. Variables are found by NAME through the PLC's symbol table, itself READ (0xF00C, 0xF00B), then read by address: no symbol handle is ever asked for (owner ruling 2026-10-13, option A)TwinCAT 3 ADS Basics TE1000 v2.6.2 (2026-05-04) §5, §6.2, §6.3; Beckhoff AdsLib (MIT) tag 113.0.36-1 for byte order, ADSTRANSMODE and the symbol table
Omron FINS (over UDP)01 01 MEMORY AREA READ; 05 01 CPU UNIT DATA READ; 06 01 CPU UNIT STATUS READnoneMEMORY AREA WRITE / FILL / TRANSFER (01 02, 01 03, 01 05), PARAMETER and PROGRAM AREA writes and clears, RUN / STOP (04 01, 04 02), CLOCK WRITE (07 02), the access rights (0C 01–03), ERROR CLEAR, FORCED SET/RESET (23 01, 23 02), every file command, and the reads that change something — MESSAGE READ/CLEAR (09 20), CYCLE TIME READ (06 20: it can initialize) — have no encoder in this build, and the codec's encodeRequest — the only path to the wire — refuses any command or memory area code outside its allow-table before a byte is madeOmron W342-E1-15 (revision 15, February 2010) §3-3, §5-1-1, §5-2, §5-3-2, §5-3-15, §5-3-17
Mitsubishi SLMP (over TCP)0101 Read Type Name; 0401 Device Read; 0403 Device Read Random; 0406 Device Read BlocknoneDevice Write, Write Random and Write Block (1401, 1402, 1406), the label writes (141A, 141B), the buffer-memory writes (1613, 1601), Remote Run / Stop / Pause / Latch Clear / Reset (1001–1006), every file command (18xx), Clear Error (1617), the remote-password Lock and Unlock (1630, 1631: an agent never holds a PLC's protection password) and the monitor registration (0801, 0802: it creates state on the PLC) have no encoder in this build — nor do the label reads, buffer-memory reads, Self Test and the device-extension subcommands (008x), read only but not needed in v1 — and the codec's encodeRequest, the only path to the wire, refuses any (command, subcommand) outside its allow-table, and any device code but these, before a byte is made: SM (91H), SD (A9H), X (9CH), Y (9DH), M (90H), L (92H), F (93H), V (94H), B (A0H), D (A8H), W (B4H), TS (C1H), TC (C0H), TN (C2H), STS (C7H), STC (C6H), STN (C8H), CS (C4H), CC (C3H), CN (C5H), SB (A1H), SW (B5H), DX (A2H), DY (A3H), Z (CCH), R (AFH), ZR (B0H), RD (2CH). Each request goes to the connected station only (network 00, station FF, module I/O 03FF)Mitsubishi Electric SLMP Reference Manual SH(NA)-080956ENG-N (revision N, October 2025) §2.1, §3.1, §4.1, §4.2, §5.1, §5.2, §5.6

Beckhoff ADS (AMS/TCP): the allow-table

Every frame the Beckhoff ADS (AMS/TCP) code can send, as (command id, index group), generated from its codec's allow-table. Anything else is refused in code, and the write-attempt test proves it: Write (0x0003), Read Write (0x0009) to a data index group and an unlisted pair are each refused, and the simulated PLC receives nothing outside this list.

CommandIndex group
0x0001 ADS Read Device Info— (carries none)
0x0004 ADS Read State— (carries none)
0x0002 ADS Read0x0000F00C
0x0002 ADS Read0x0000F00B
0x0002 ADS Read0x00004020
0x0002 ADS Read0x00004021
0x0002 ADS Read0x00004030
0x0002 ADS Read0x00004040
0x0002 ADS Read0x0000F020
0x0002 ADS Read0x0000F021
0x0002 ADS Read0x0000F030
0x0002 ADS Read0x0000F031
0x0006 ADS Add Device Notification0x00004020
0x0006 ADS Add Device Notification0x00004021
0x0006 ADS Add Device Notification0x00004030
0x0006 ADS Add Device Notification0x00004040
0x0006 ADS Add Device Notification0x0000F020
0x0006 ADS Add Device Notification0x0000F021
0x0006 ADS Add Device Notification0x0000F030
0x0006 ADS Add Device Notification0x0000F031
0x0007 ADS Delete Device Notification— (carries none)

Omron FINS (over UDP): the allow-table

Every frame the Omron FINS (over UDP) code can send, as (command (mrc src), memory area code), generated from its codec's allow-table. Anything else is refused in code, and the write-attempt test proves it: MEMORY AREA WRITE (01 02), RUN (04 01) and a MEMORY AREA READ of an unlisted area code (the CIO forced-status words, F0 hex) are each refused, and the simulated PLC receives nothing outside this list.

Command (MRC SRC)Memory area code
01 01 MEMORY AREA READ02 hex
01 01 MEMORY AREA READ09 hex
01 01 MEMORY AREA READ20 hex
01 01 MEMORY AREA READ21 hex
01 01 MEMORY AREA READ22 hex
01 01 MEMORY AREA READ23 hex
01 01 MEMORY AREA READ24 hex
01 01 MEMORY AREA READ25 hex
01 01 MEMORY AREA READ26 hex
01 01 MEMORY AREA READ27 hex
01 01 MEMORY AREA READ28 hex
01 01 MEMORY AREA READ29 hex
01 01 MEMORY AREA READ2A hex
01 01 MEMORY AREA READ2B hex
01 01 MEMORY AREA READ2C hex
01 01 MEMORY AREA READ2D hex
01 01 MEMORY AREA READ2E hex
01 01 MEMORY AREA READ2F hex
01 01 MEMORY AREA READ30 hex
01 01 MEMORY AREA READ31 hex
01 01 MEMORY AREA READ32 hex
01 01 MEMORY AREA READ33 hex
01 01 MEMORY AREA READ82 hex
01 01 MEMORY AREA READ89 hex
01 01 MEMORY AREA READA0 hex
01 01 MEMORY AREA READA1 hex
01 01 MEMORY AREA READA2 hex
01 01 MEMORY AREA READA3 hex
01 01 MEMORY AREA READA4 hex
01 01 MEMORY AREA READA5 hex
01 01 MEMORY AREA READA6 hex
01 01 MEMORY AREA READA7 hex
01 01 MEMORY AREA READA8 hex
01 01 MEMORY AREA READA9 hex
01 01 MEMORY AREA READAA hex
01 01 MEMORY AREA READAB hex
01 01 MEMORY AREA READAC hex
01 01 MEMORY AREA READAD hex
01 01 MEMORY AREA READAE hex
01 01 MEMORY AREA READAF hex
01 01 MEMORY AREA READB0 hex
01 01 MEMORY AREA READB1 hex
01 01 MEMORY AREA READB2 hex
01 01 MEMORY AREA READB3 hex
05 01 CPU UNIT DATA READ— (carries none)
06 01 CPU UNIT STATUS READ— (carries none)

Mitsubishi SLMP (over TCP): the allow-table

Every frame the Mitsubishi SLMP (over TCP) code can send, as (command, subcommand), generated from its codec's allow-table. Anything else is refused in code, and the write-attempt test proves it: Device Write (1401), Remote Run (1001), Entry Monitor Device (0801), remote-password Unlock (1630), a Device Read with the device-extension subcommand (0080) and a read of an unlisted device code (the step relay, 98H) are each refused, and the simulated PLC receives nothing outside this list.

CommandSubcommand
0101 Read Type Name0000
0401 Device Read0000
0401 Device Read0001
0401 Device Read0002
0401 Device Read0003
0403 Device Read Random0000
0403 Device Read Random0002
0406 Device Read Block0000
0406 Device Read Block0002

Not part of this profile at all: BACnet, KNX, SNMP (the full agent's).

Connections: outbound only — to HOLOIN over TLS, and to the machines it reads. It accepts no inbound connection and opens no listening TCP port. Omron FINS sends FINS/UDP requests and receives the replies on the socket it opened, on the machine-network interface (fins.interface, never every interface).

The package (57 files, 127 npm packages)

README.md
drivers/ads-codec.mjs
drivers/ads.mjs
drivers/fins-codec.mjs
drivers/fins.mjs
drivers/modbus-codec-read.mjs
drivers/modbus-read.mjs
drivers/mtconnect-doc.mjs
drivers/mtconnect-xml.mjs
drivers/mtconnect.mjs
drivers/opcua-read.mjs
drivers/slmp-codec.mjs
drivers/slmp.mjs
drivers/sunspec-decode.mjs
drivers/sunspec-plant.mjs
drivers/sunspec.mjs
holoin-agent-factory.mjs
lib/buffer.mjs
lib/config-core.mjs
lib/link.mjs
lib/platform.mjs
package.json
profiles/factory.mjs
sunspec/LICENSE
sunspec/VENDORED.md
sunspec/models/model_1.json
sunspec/models/model_101.json
sunspec/models/model_102.json
sunspec/models/model_103.json
sunspec/models/model_111.json
sunspec/models/model_112.json
sunspec/models/model_113.json
sunspec/models/model_120.json
sunspec/models/model_122.json
sunspec/models/model_160.json
sunspec/models/model_201.json
sunspec/models/model_202.json
sunspec/models/model_203.json
sunspec/models/model_204.json
sunspec/models/model_211.json
sunspec/models/model_212.json
sunspec/models/model_213.json
sunspec/models/model_214.json
sunspec/models/model_302.json
sunspec/models/model_303.json
sunspec/models/model_304.json
sunspec/models/model_305.json
sunspec/models/model_306.json
sunspec/models/model_307.json
sunspec/models/model_308.json
sunspec/models/model_401.json
sunspec/models/model_402.json
sunspec/models/model_403.json
sunspec/models/model_404.json
sunspec/models/model_802.json
sunspec/models/model_803.json
sunspec/models/model_804.json

npm packages (the dependency closure of its imports, production only):

@leichtgewicht/ip-codec
@peculiar/asn1-asym-key
@peculiar/asn1-cms
@peculiar/asn1-csr
@peculiar/asn1-ecc
@peculiar/asn1-pfx
@peculiar/asn1-pkcs8
@peculiar/asn1-pkcs9
@peculiar/asn1-rsa
@peculiar/asn1-schema
@peculiar/asn1-x509
@peculiar/asn1-x509-attr
@peculiar/asn1-x509-post-quantum
@peculiar/json-schema
@peculiar/utils
@peculiar/webcrypto
@peculiar/x509
@ster5/global-mutex
@types/dns-packet
@types/multicast-dns
@types/node
ansi-styles
array-back
array-flatten
asn1
asn1js
assert-plus
backoff
bcrypt-pbkdf
byline
chalk
chalk-template
chalk
chokidar
color-convert
color-name
command-line-args
command-line-usage
dashdash
dns-equal
dns-packet
ecc-jsbn
env-paths
fast-deep-equal
find-replace
getpass
has-flag
hexy
is-safe-filename
jsbn
lodash.camelcase
long
multicast-dns
node-opcua-address-space-base
node-opcua-alarm-condition
node-opcua-assert
node-opcua-basic-types
node-opcua-binary-stream
node-opcua-buffer-utils
node-opcua-certificate-manager
node-opcua-chunkmanager
node-opcua-client
node-opcua-client-dynamic-extension-object
node-opcua-common
node-opcua-constants
node-opcua-crypto
node-opcua-data-model
node-opcua-data-value
node-opcua-date-time
node-opcua-debug
node-opcua-enum
node-opcua-extension-object
node-opcua-factory
node-opcua-guid
node-opcua-hostname
node-opcua-nodeid
node-opcua-numeric-range
node-opcua-object-registry
node-opcua-packet-analyzer
node-opcua-packet-assembler
node-opcua-pki
node-opcua-pseudo-session
node-opcua-schemas
node-opcua-secure-channel
node-opcua-service-browse
node-opcua-service-call
node-opcua-service-discovery
node-opcua-service-endpoints
node-opcua-service-filter
node-opcua-service-history
node-opcua-service-query
node-opcua-service-read
node-opcua-service-register-node
node-opcua-service-secure-channel
node-opcua-service-session
node-opcua-service-subscription
node-opcua-service-translate-browse-path
node-opcua-service-write
node-opcua-status-code
node-opcua-transport
node-opcua-types
node-opcua-utils
node-opcua-variant
node-opcua-xml2json
pend
precond
pvtsutils
pvutils
readdirp
reflect-metadata
safer-buffer
sshpk
sterfive-bonjour-service
supports-color
table-layout
thenify-ex
thunky
tslib
tsyringe
tslib
tweetnacl
typical
undici-types
webcrypto-core
wordwrapjs
xml-writer
yauzl