<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<rfc category="info" docName="draft-das-ot-actuation-finality-00"
     ipr="trust200902" submissionType="IETF" xml:lang="en" version="3"
     tocInclude="true" tocDepth="3" symRefs="true" sortRefs="true">
  <front>
    <title abbrev="OT Actuation Finality">A Setpoint Write Is Not Actuation: Finality for ICS, Grid, and Robot Command</title>
    <seriesInfo name="Internet-Draft" value="draft-das-ot-actuation-finality-00"/>
    <author fullname="Sangam Das" initials="S." surname="Das">
      <organization>Independent Inventor</organization>
      <address>
        <postal>
          <city>Balasore</city>
          <region>Odisha</region>
          <code>756001</code>
          <country>India</country>
        </postal>
        <email>info@sangamdas.com</email>
      </address>
    </author>
    <date year="2026" month="August" day="27"/>
    <area>Security</area>
    <keyword>ICS</keyword>
    <keyword>OT</keyword>
    <keyword>SCADA</keyword>
    <keyword>actuation</keyword>
    <keyword>execution finality</keyword>
    <abstract>
      <t>Industrial systems already know how to move a breaker,
      a valve, a robot joint, or a turbine setpoint. They do
      not know whether this write — this tag, this value, this
      device, from this operator session or this agent — is
      the write that was authorized to become motion. A
      signed OPC UA call, a valid DNP3 control, an IEC 61850
      command, or an MQTT publish onto the plant bus can all
      be protocol-correct while the act is wrong. The
      protocol authenticates a channel. It does not bind a
      Candidate Act at the actuation sink.</t>
      <t>That gap is now an agent gap. Copilots sit on
      historians and work-order text. They propose
      "explain this alarm" and then "set this point." If
      the gateway treats a well-formed write as authority,
      reconstruction of plant state becomes physical
      consequence. Past incidents stole engineering
      workstations. Present incidents steal the jump host
      or the agent seat. Future incidents let the model
      format the command.</t>
      <t>This document specifies an OT-side
      execution-finality profile. A write remains an
      Actuation Candidate Act. A Protected Enforcement
      Domain binds principal, zone, device, tag, value
      envelope, mode (local/remote, auto/manual), safety
      interlock state, policy epoch, and intended
      actuation sink, then commits evidence before
      scoped non-bearer authority is issued. The sink
      that would actually drive I/O verifies that
      authority against the live write and consumes it.
      A setpoint write is not actuation.</t>
    </abstract>
  </front>
  <middle>
    <section anchor="intro">
      <name>Introduction</name>
      <t>The command path in a plant, grid, water system,
      warehouse robot cell, or rail interlocking is now:</t>
      <artwork><![CDATA[
HMI / MES / agent / work-order bot
        -> write {device, tag, value}
        -> OPC UA / DNP3 / IEC 61850 / MQTT / ROS
        -> PLC / RTU / drive / robot controller
        -> physical change
]]></artwork>
      <t>This document controls the last arrow. The
      computer may compute a setpoint. The fieldbus may
      authenticate the client. Neither fact is authority
      for this value to leave the logic solver toward
      this actuator in this mode and epoch.</t>
      <t>The profile uses the chain in
      <xref target="I-D.das-6g-finality"/> and the tool
      objects in <xref target="I-D.das-agentic"/>. It
      specifies only physical-actuation predicates:
      tag and value binding, zone and device binding,
      operating-mode binding, interlock and permit-to-work
      inputs, and coexistence with existing ICS protocols
      that remain the transport.</t>
    </section>

    <section anchor="theft">
      <name>How OT Command Abuse Changed</name>
      <section>
        <name>Past: Steal the Engineering Station</name>
        <t>Historical OT incidents stole the thing that
        already issued commands: an engineering
        workstation, a vendor laptop, a PLC project
        file, a default password on an HMI. Defense
        was island-centric. Air-gap the cell. Lock
        the cabinet. Patch when the outage window
        allows. The valuable object was the
        workstation that engineers already trusted
        to write.</t>
      </section>
      <section>
        <name>Present: Steal the Jump Path that Can Write</name>
        <t>Present incidents often leave the PLC
        password in place. The attacker takes a jump
        host, a VPN into the DMZ, an MES API that
        forwards setpoints, or an agent seat allowed
        to "help operations." IEC 62443 zones are
        drawn. The write is still just a write.
        Business-email compromise had a plant cousin:
        a lawful operator session, a new target tag,
        a valid protocol signature.</t>
      </section>
      <section>
        <name>Future: Compute the Setpoint, Then Actuate</name>
        <t>The coming incident does not require the
        attacker to speak DNP3. The model speaks
        the gateway's JSON and the gateway speaks
        DNP3. "Open this valve, the work order says
        so" is a Candidate Act that looks like
        ordinary assistance. If the I/O server
        accepts any authenticated write from that
        worker, language becomes motion.</t>
        <t>Safety PLCs still trip. This profile does
        not replace SIS. It exists so a
        protocol-valid write that is
        safety-irrelevant or that sits below the
        SIS threshold still cannot complete without
        current actuation authority.</t>
      </section>
    </section>

    <section anchor="rfc2119">
      <name>Requirements Language</name>
      <t>The key words "MUST", "MUST NOT", "REQUIRED",
      "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT",
      "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
      "OPTIONAL" in this document are to be
      interpreted as described in BCP 14
      <xref target="RFC2119"/> <xref target="RFC8174"/>
      when, and only when, they appear in all
      capitals, as shown here.</t>
      <t>Failure to establish current actuation
      authority MUST NOT be converted into permission
      to energize, stroke, step, or otherwise change
      a physical output.</t>
    </section>

    <section anchor="problem">
      <name>Problem Space</name>
      <t>An OT write is a Candidate Act whose
      consequence class is PHYSICAL or
      NETWORK_CONTROL. After the coil changes,
      rollback is a process, not a protocol.
      First-class failures:</t>
      <ul>
        <li>Value substitution (80.0 authorized,
        800.0 sent).</li>
        <li>Tag substitution (authorized PCV-12,
        live PCV-21).</li>
        <li>Device or zone substitution (cell A
        authority used on cell B).</li>
        <li>Mode violation (remote write while
        the device is Local or Locked-out).</li>
        <li>Replay of a consumed jog or close
        pulse.</li>
        <li>Permit-to-work expired while the
        command sat in a queue.</li>
        <li>Agent or wrapper substitution
        (approved historian bot, executing
        write bot).</li>
        <li>Safety-bypass or interlock-ignore
        flags set after approval.</li>
      </ul>
    </section>

    <section anchor="existing">
      <name>Existing Solutions and What They Do Not Bind</name>
      <section>
        <name>IEC 62443, Zones, and Conduits</name>
        <t>Zoning answers which hosts may talk.
        It does not consume single-use authority
        for this tag and this value at the I/O
        server.</t>
      </section>
      <section>
        <name>OPC UA, DNP3, IEC 61850, Modbus, MQTT</name>
        <t>These authenticate and structure
        writes. A valid signed call can still
        be the wrong act. Modbus in particular
        is often a raw register write with no
        act object at all.</t>
      </section>
      <section>
        <name>Safety PLC and SIS</name>
        <t>SIS is the last physical backstop. It
        is not an authorization system for
        every non-trip command. Most harmful
        writes never reach a trip setpoint.</t>
      </section>
      <section>
        <name>Jump Hosts, Privileged Access, and MFA</name>
        <t>They authenticate the operator session.
        Possession of the session is bearer-like
        for every tag that session can see.</t>
      </section>
      <section>
        <name>Change Tickets and Permit-to-Work</name>
        <t>A ticket is class approval. It MAY be
        a PED input. It MUST NOT by itself be
        actuation authority for a particular
        write.</t>
      </section>
      <section>
        <name>Robot and ROS Command Buses</name>
        <t>A trajectory topic is a write. Auth
        on the bus does not bind a pose digest
        at the motion controller.</t>
      </section>
      <section>
        <name>What This Profile Adds</name>
        <ul>
          <li>the write remains an Actuation
          Candidate Act;</li>
          <li>device, tag, value, mode, and zone
          are in the digest;</li>
          <li>authority is non-bearer, sink-bound,
          epoch-bound, and consumed;</li>
          <li>legacy fieldbus remains the
          transport;</li>
          <li>hot-path envelopes exist so a
          20 ms loop is not forced through a
          remote ledger.</li>
        </ul>
      </section>
    </section>

    <section anchor="industry">
      <name>Industrial Applicability</name>
      <section>
        <name>Transmission and Distribution</name>
        <t>Sink is the RTU or gateway that would
        emit the 61850/DNP3 operate. Authority
        for breaker A MUST fail on breaker B.
        Overflight-like jurisdiction epochs from
        the NTN profile are not required; yard
        and voltage-level epochs are.</t>
      </section>
      <section>
        <name>Process, Refining, and Batch</name>
        <t>Sink is the DCS write service or PLC
        put. Batch recipe download is a
        Candidate Act whose digest includes
        recipe id and unit.</t>
      </section>
      <section>
        <name>Water, Waste, and Pipelines</name>
        <t>Remote pump and valve writes are the
        classic low-staffing surface. Envelope
        ceilings on flow and pressure are
        load-bearing.</t>
      </section>
      <section>
        <name>Discrete Manufacturing and Robots</name>
        <t>MES "start job" and robot "execute
        trajectory" are separate acts. A job
        start MUST NOT authorize an arbitrary
        pose.</t>
      </section>
      <section>
        <name>Rail, Tunnel, and Building Life-Safety</name>
        <t>Interlocking commands and fire-system
        overrides are cold-path default.
        Timeout MUST NOT actuate.</t>
      </section>
      <section>
        <name>OT Copilots and MCP on the Plant LAN</name>
        <t>A historian explain tool is
        INFORMATIONAL. A write tool is this
        profile plus
        <xref target="I-D.das-agentic"/>.
        Discovery of an MCP server in the DMZ
        is not permit-to-work.</t>
      </section>
      <section>
        <name>How an Operator Would Pilot</name>
        <t>Pick one outbound write API used by a
        new assistant or by a single OEM
        gateway. Deploy consume next to the
        existing command log. Leave the
        dedicated HMI on existing dual control.
        Measure unauthorized-would-have-written
        denies and added latency on the
        supervised path. Expand zone by zone.
        No PLC firmware rewrite is required
        for v1 if the gateway is in-line.</t>
      </section>
    </section>

    <section anchor="terminology">
      <name>Terminology</name>
      <dl newline="true">
        <dt>Actuation Candidate Act</dt>
        <dd>A generated or selected write that
        would change a physical or
        safety-adjacent output, but has not
        been permitted to do so.</dd>
        <dt>Actuation Sink</dt>
        <dd>The last software component that
        can still refuse before I/O changes.
        If that component can be skipped, it
        is not the sink.</dd>
        <dt>Write digest</dt>
        <dd>Hash over canonical device, tag,
        value, units, mode, zone, and
        interlock flags.</dd>
      </dl>
    </section>

    <section anchor="architecture">
      <name>Architecture</name>
      <figure>
        <name>Actuation-time finality</name>
        <artwork><![CDATA[
HMI / MES / agent emits write
              |
              v
    ACTUATION CANDIDATE ACT
              |
              v
        Non-Effective State
              |
              v
   Protected Enforcement Domain
      principal, zone, device, tag
      value envelope, units, mode
      interlock / LOTO / PTW inputs
      policy and revocation epochs
      intended actuation sink
              |
              v
   evidence + scoped authority
              |
              v
        ACTUATION SINK
              |
      +-- PASS -> drive I/O once, consume
      |
      `-- FAIL -> no physical change
]]></artwork>
      </figure>
    </section>

    <section anchor="workflow">
      <name>End-to-End Workflow</name>
      <ol>
        <li>Write constructed. Status
        NON_EFFECTIVE.</li>
        <li>Canonicalize device, tag, value,
        units, mode. Compute write digest.</li>
        <li>Build ActuationCandidateAct.</li>
        <li>PED validates envelope, mode,
        interlock, PTW, epochs.</li>
        <li>Fail: deny, do not write.</li>
        <li>Pass: commit evidence.</li>
        <li>Issue scoped Actuation Finality
        Authority.</li>
        <li>Carry the write on the legacy
        protocol together with authority
        reference.</li>
        <li>Sink verifies live fields
        immediately before I/O.</li>
        <li>Atomic consume + actuate, or
        deny with no I/O.</li>
      </ol>
      <t>Value canonicalization MUST include
      scale and units. 1.5 bar and 150 kPa
      are the same act only if policy says
      so before hashing. expires_at is
      authority TTL, not process dead-time.
      A queued write MUST re-validate mode
      and interlocks at the sink, not only
      at queue entry.</t>
    </section>

    <section anchor="pseudocode">
      <name>Actuation Pseudocode</name>
      <sourcecode type="pseudocode"><![CDATA[
function ON_WRITE(w, ctx):
    act = ActuationCandidateAct{
        candidate_act_id: fresh_id(),
        act_type: map_write(w),
        zone: w.zone,
        device_id: w.device_id,
        tag: w.tag,
        value: canonical_value(w),
        units: w.units,
        mode: w.mode,
        write_digest: HASH(canonicalize(w)),
        policy_state: current_epochs(),
        freshness: {nonce: fresh_nonce()},
        finality_sink: ctx.actuation_sink,
        expires_at: now() + short_ttl
    }
    HOLD_NON_EFFECTIVE(act)
    return PED_VALIDATE(act, w)

function PED_VALIDATE(act, w):
    if malformed(act):
        return DENY(MALFORMED_ACT)
    if not fresh(act.freshness.nonce):
        return DENY(REPLAY_OR_STALE)
    if act.policy_state != current_epochs():
        return DENY(EPOCH_MISMATCH)
    if w.mode in {LOCAL, LOTO, OUT_OF_SERVICE}:
        return DENY(MODE_FORBIDDEN)
    if interlock_open(w) and not override_authorized(act):
        return DENY(INTERLOCK)
    if not ptw_covers(act):
        return DENY(PTW_MISS)
    if not value_in_envelope(act):
        return ESCALATE_OR_DENY(VALUE_ENVELOPE)
    evidence = COMMIT_PROTECTED_EVIDENCE(act)
    authority = ISSUE_SCOPED_AUTHORITY(act, evidence)
    return ALLOW(authority)

function ACTUATION_SINK_WRITE(act, authority, live):
    live_digest = HASH(canonicalize(live))
    if authority missing or integrity_fail(authority):
        return DENY(NO_OR_INVALID_AUTHORITY)
    if live_digest != act.write_digest:
        return DENY(WRITE_SUBSTITUTION)
    if authority.sink_id != THIS_SINK:
        return DENY(SINK_MISMATCH)
    if live.tag != authority.tag or
       live.device_id != authority.device_id or
       canonical_value(live) != authority.value:
        return DENY(FIELD_MISMATCH)
    if live.mode != authority.mode:
        return DENY(MODE_MISMATCH)
    if expired(authority) or consumed(authority):
        return DENY(STALE_OR_USED)
    ATOMICALLY:
        consume(authority)
        result = drive_io(live)
    record_sink_evidence(act, authority, result)
    return result
]]></sourcecode>
      <t>drive_io() is unreachable when any check
      fails. A protocol ACK after a failed
      check is non-conforming.</t>
    </section>

    <section anchor="json">
      <name>JSON Interoperability Profile</name>
      <section>
        <name>ActuationCandidateAct Schema</name>
        <sourcecode type="json"><![CDATA[
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:ietf:params:json-schema:ot-finality:candidate-act:1",
  "title": "ActuationCandidateAct",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "version", "object_type", "candidate_act_id", "act_type",
    "created_at", "expires_at", "principal", "zone",
    "device_id", "tag", "value", "units", "mode",
    "write_digest", "policy_state", "freshness", "finality_sink"
  ],
  "properties": {
    "version": { "type": "string", "const": "1.0" },
    "object_type": {
      "type": "string",
      "const": "actuation_candidate_act"
    },
    "candidate_act_id": { "type": "string", "minLength": 16 },
    "act_type": {
      "type": "string",
      "enum": [
        "SETPOINT_WRITE", "DISCRETE_WRITE", "PULSE",
        "JOG", "BREAKER_OPERATE", "VALVE_STROKE",
        "RECIPE_DOWNLOAD", "TRAJECTORY_EXECUTE",
        "ENABLE_OUTPUT", "INHIBIT_OVERRIDE", "OTHER"
      ]
    },
    "created_at": { "type": "string", "format": "date-time" },
    "expires_at": { "type": "string", "format": "date-time" },
    "principal": {
      "type": "object",
      "required": ["principal_id"],
      "properties": {
        "principal_id": { "type": "string" },
        "principal_type": {
          "type": "string",
          "enum": ["operator", "agent", "mes", "oem", "service"]
        },
        "agent_id": { "type": "string" },
        "workload_id": { "type": "string" }
      }
    },
    "zone": { "type": "string" },
    "device_id": { "type": "string" },
    "tag": { "type": "string" },
    "value": { "type": "string" },
    "units": { "type": "string" },
    "mode": {
      "type": "string",
      "enum": [
        "REMOTE", "LOCAL", "MANUAL", "AUTO",
        "LOTO", "OUT_OF_SERVICE", "TEST"
      ]
    },
    "interlocks": {
      "type": "object",
      "properties": {
        "all_clear": { "type": "boolean" },
        "ptw_id": { "type": "string" },
        "override": { "type": "boolean" }
      }
    },
    "write_digest": {
      "type": "object",
      "required": ["algorithm", "value", "canonicalization"],
      "properties": {
        "algorithm": {
          "type": "string",
          "enum": ["SHA-256", "SHA-384", "SHA-512"]
        },
        "value": { "type": "string" },
        "canonicalization": {
          "type": "string",
          "enum": ["JCS", "implementation-defined"]
        }
      }
    },
    "policy_state": {
      "type": "object",
      "required": ["policy_epoch", "revocation_epoch"],
      "properties": {
        "policy_epoch": { "type": "integer", "minimum": 0 },
        "revocation_epoch": { "type": "integer", "minimum": 0 },
        "safety_config_epoch": { "type": "integer", "minimum": 0 }
      }
    },
    "freshness": {
      "type": "object",
      "required": ["nonce"],
      "properties": {
        "nonce": { "type": "string", "minLength": 16 },
        "sequence": { "type": "integer", "minimum": 0 }
      }
    },
    "finality_sink": {
      "type": "object",
      "required": ["sink_id", "sink_type"],
      "properties": {
        "sink_id": { "type": "string" },
        "sink_type": {
          "type": "string",
          "enum": [
            "PLC_WRITE", "RTU_OPERATE", "DCS_PUT",
            "ROBOT_MOTION", "DRIVE_ENABLE",
            "GATEWAY_IO", "OTHER"
          ]
        }
      }
    }
  }
}
]]></sourcecode>
      </section>
      <section>
        <name>Evidence, Authority, Allow, Deny</name>
        <sourcecode type="json"><![CDATA[
{
  "version": "1.0",
  "object_type": "protected_validation_evidence",
  "evidence_id": "pve-ot-01",
  "candidate_act_id": "act-ot-11aa",
  "decision": "ALLOW",
  "validated_predicates": {
    "mode_remote": true,
    "interlocks_clear": true,
    "ptw_valid": true,
    "value_in_envelope": true,
    "epoch_valid": true,
    "sink_binding_valid": true
  },
  "issued_at": "2026-08-27T03:10:00Z",
  "protector": { "type": "HSM_MAC", "key_id": "ped-ot-2" }
}
]]></sourcecode>
        <sourcecode type="json"><![CDATA[
{
  "version": "1.0",
  "object_type": "actuation_finality_authority",
  "authority_id": "afa-ot-44",
  "candidate_act_id": "act-ot-11aa",
  "evidence_id": "pve-ot-01",
  "scope": {
    "act_type": "SETPOINT_WRITE",
    "device_id": "PIC-12",
    "tag": "SP",
    "value": "8.0",
    "units": "bar",
    "mode": "REMOTE"
  },
  "binding": {
    "write_digest": {
      "algorithm": "SHA-256",
      "value": "base64url-write-digest"
    },
    "nonce": "0A1B2C3D4E5F6789",
    "policy_epoch": 21,
    "finality_sink_id": "plc-write-cell-a"
  },
  "lifetime": {
    "issued_at": "2026-08-27T03:10:00Z",
    "expires_at": "2026-08-27T03:10:05Z",
    "single_use": true
  }
}
]]></sourcecode>
        <sourcecode type="json"><![CDATA[
{
  "operation": "ActuationSinkVerify",
  "decision": "ALLOW",
  "consumption": { "authority_id": "afa-ot-44", "status": "CONSUMED" },
  "effectuation": { "permitted": true, "io_ref": "w-9021" }
}
]]></sourcecode>
        <sourcecode type="json"><![CDATA[
{
  "operation": "ActuationSinkVerify",
  "decision": "DENY",
  "error": {
    "code": "EF_WRITE_SUBSTITUTION",
    "message": "Live value is not the authorized digest.",
    "retryable": false
  },
  "effectuation": { "permitted": false }
}
]]></sourcecode>
      </section>
      <section>
        <name>Complete Allow Transaction</name>
        <sourcecode type="json"><![CDATA[
{
  "step_1_write": {
    "act_type": "SETPOINT_WRITE",
    "device_id": "PIC-12",
    "tag": "SP",
    "value": "8.0",
    "units": "bar",
    "mode": "REMOTE",
    "status": "NON_EFFECTIVE"
  },
  "step_2_authority": { "authority_id": "afa-ot-44", "single_use": true },
  "step_3_sink": { "decision": "ALLOW", "consumed": true },
  "step_4_effect": "PIC-12.SP written once at 8.0 bar"
}
]]></sourcecode>
      </section>
    </section>

    <section anchor="bindings">
      <name>Load-Bearing Bindings</name>
      <t>Authority for PIC-12 / SP / 8.0 bar /
      REMOTE / digest D1 MUST NOT authorize
      18.0 bar, PIC-13, LOCAL mode, or digest
      D2. ENABLE_OUTPUT authority MUST NOT
      authorize INHIBIT_OVERRIDE. Cell-A sink
      authority MUST fail at cell-B.</t>
    </section>

    <section anchor="denies">
      <name>Worked Denials</name>
      <sourcecode type="json"><![CDATA[
{
  "scenario": "VALUE_SWAP",
  "authorized": { "tag": "SP", "value": "8.0" },
  "live": { "tag": "SP", "value": "18.0" },
  "code": "EF_WRITE_SUBSTITUTION",
  "actuated": false
}
]]></sourcecode>
      <sourcecode type="json"><![CDATA[
{
  "scenario": "LOTO_STILL_HUNG",
  "mode_live": "LOTO",
  "code": "EF_MODE_FORBIDDEN",
  "actuated": false
}
]]></sourcecode>
      <sourcecode type="json"><![CDATA[
{
  "scenario": "AGENT_THEN_RAW_MODBUS",
  "authorized_sink": "plc-write-cell-a",
  "alternate_path": "modbus-tcp:502 raw",
  "rule": "if raw path can stroke the same coil it is an open alternate path"
}
]]></sourcecode>
    </section>

    <section anchor="dual">
      <name>Dual Control, LOTO, and Permit-to-Work</name>
      <t>Maker-checker on a write that can kill or
      spill is industrial instinct. PED MUST treat
      missing checker shares as validation failure.
      An agent MAY be the maker. An agent SHOULD
      NOT be the sole checker for PHYSICAL class
      above the hot envelope. LOTO and
      permit-to-work are mode and evidence inputs,
      not a reason to skip consume. A PTW that
      names unit U MUST fail on unit V even if
      both are in the same zone drawing.</t>
    </section>

    <section anchor="protomap">
      <name>Informative Protocol Mappings</name>
      <section>
        <name>OPC UA Write / Call</name>
        <t>device_id and tag map to NodeId.
        value maps to the written DataValue.
        mode maps to a companion property or
        to gateway state. authority_id MAY
        travel as an additional argument on a
        Method call or as a header on the
        gateway that fronts UA. UA signature
        of the session is channel auth, not
        act authority.</t>
      </section>
      <section>
        <name>DNP3 Select/Operate</name>
        <t>Select/Operate is already two-step.
        This profile still applies: Operate
        MUST present current authority for
        the selected point and value. A
        Select that is then Operated with a
        swapped value is WRITE_SUBSTITUTION.
        Replay of Operate after consume is
        EF-005.</t>
      </section>
      <section>
        <name>IEC 61850 Operate</name>
        <t>ctlVal and orCat are load-bearing
        and MUST enter the digest. Test bits
        that would become live operate MUST
        be mode=TEST and MUST not inherit
        REMOTE-live authority.</t>
      </section>
      <section>
        <name>ROS / Robot Trajectory</name>
        <t>A FollowJointTrajectory goal is a
        TRAJECTORY_EXECUTE act. Digest the
        goal points or a hash the controller
        already computes. Start-job from MES
        is a different act_type.</t>
      </section>
    </section>

    <section anchor="feasibility">
      <name>Practical Feasibility: Latency and Legacy</name>
      <section>
        <name>Scan-Time Budget</name>
        <t>Closed loops run in milliseconds to tens
        of milliseconds. This profile is for
        *supervised writes and agent writes*, not
        for replacing the inner PID. Hot-path
        enforce at a gateway is canonicalize,
        hash, MAC verify, compare-and-swap —
        typically well under 1-5 ms on the same
        host as the existing command server.
        Inner loop setpoint tracking stays in
        the PLC. Do not put a network round
        trip inside the 10 ms task.</t>
      </section>
      <section>
        <name>Hot-Path Envelope</name>
        <t>A cold evaluation MAY allow a bounded
        envelope: zone Z, device set D, tag
        family T, value band [lo, hi], mode
        REMOTE, sink S, epochs (P,R), until
        time U. Writes inside the envelope
        MAY use local validation. Change of
        device, band, mode, or epoch MUST
        escalate. Hot does not mean skip
        consume or skip digest compare.</t>
      </section>
      <section>
        <name>Coexistence with OPC UA and DNP3</name>
        <t>Place the sink immediately before the
        component that performs the Operate /
        Write. Carry authority_id in a
        companion attribute or gateway header.
        Field devices that cannot parse JSON
        still only see writes the sink
        admitted.</t>
      </section>
      <section>
        <name>Coexistence with Modbus</name>
        <t>Modbus has no act object. The gateway
        that turns JSON into function-code 6
        or 16 IS the sink. Raw 502 exposure
        of the same registers is an alternate
        path and MUST be closed or listed.</t>
      </section>
      <section>
        <name>Coexistence with SIS</name>
        <t>Do not move trip logic into PED. If
        SIS would refuse the physical state,
        this profile is irrelevant. If SIS
        would allow it, this profile still
        applies to the supervisory write.</t>
      </section>
      <section>
        <name>Coexistence with HMIs</name>
        <t>A dedicated hardwired HMI can stay on
        existing dual control during a pilot.
        Alternate-path closure says that if
        the HMI can emit the same coil change
        as the agent path, it eventually needs
        the same consume semantics or it
        remains a listed exception.</t>
      </section>
      <section>
        <name>What This Profile Does Not Require</name>
        <t>It does not require replacing IEC
        61850, DNP3, or the PLC vendor. It
        does not require a blockchain on the
        scan cycle. It does not require the
        model to detect process hazards. It
        requires that the component which can
        change I/O refuse to do so without
        current, consumed, digest-bound
        authority.</t>
      </section>
    </section>

    <section anchor="combined">
      <name>Combined Agent Tool Call and Actuation</name>
      <t>When the write is born as a model tool
      call, build AgentCandidateAct for
      dispatch and ActuationCandidateAct for
      I/O. Dispatch allow MUST NOT drive I/O
      if the actuation sink denies.</t>
      <sourcecode type="json"><![CDATA[
{
  "agent_candidate_act": {
    "act_type": "TOOL_CALL",
    "tool": { "tool_id": "plc.write", "tool_protocol": "MCP" },
    "consequence_class": "PHYSICAL"
  },
  "actuation_candidate_act": {
    "act_type": "SETPOINT_WRITE",
    "device_id": "PIC-12",
    "tag": "SP",
    "value": "8.0",
    "mode": "REMOTE"
  },
  "rule": "two sinks: host invoke, then I/O; neither replaces the other"
}
]]></sourcecode>
    </section>

    <section anchor="threats">
      <name>OT Threat Catalog</name>
      <ul>
        <li>T1 Stolen jump host or VPN</li>
        <li>T2 Stolen agent seat with write
        tools</li>
        <li>T3 Injected work-order text</li>
        <li>T4 Value substitution after HMI
        approval</li>
        <li>T5 Tag / device swap</li>
        <li>T6 Mode ignore (write while Local)</li>
        <li>T7 Replay of a pulse / jog</li>
        <li>T8 Raw Modbus beside the gated
        API</li>
        <li>T9 PTW expiry on a queued write</li>
        <li>T10 Safety-config epoch rollback</li>
        <li>T11 Recipe download to the wrong
        unit</li>
        <li>T12 Robot trajectory swap</li>
      </ul>
    </section>

    <section anchor="errors">
      <name>Failure Codes</name>
      <t>EF-002 NO_FINALITY_AUTHORITY, EF-005
      AUTHORITY_ALREADY_USED, EF-006 REPLAY,
      EF-023 WRITE_SUBSTITUTION, EF-030
      MODE_FORBIDDEN, EF-031 INTERLOCK,
      EF-032 PTW_MISS, EF-033 VALUE_ENVELOPE,
      EF-040 SINK_MISMATCH, EF-070 ESCALATE,
      EF-080 FAIL_CLOSED. Timeout MUST NOT
      actuate.</t>
    </section>

    <section anchor="checklist">
      <name>Implementation Checklist</name>
      <ol>
        <li>Every supervised or agent write
        has a consumed authority_id.</li>
        <li>Digest covers device, tag, value,
        units, mode.</li>
        <li>Mismatch is deny, not a log
        line.</li>
        <li>Raw protocol paths to the same
        coil are inventoried.</li>
        <li>Timeout does not write.</li>
        <li>Inner PID is not routed through
        this profile.</li>
        <li>Added gateway latency is
        measured.</li>
      </ol>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>If the actuation sink holds
      unrestricted write rights and skips
      verification, the profile collapses for
      that path. Keys that sign authority
      SHOULD live off the agent host.
      Override bits are cold-path default.</t>
    </section>

    <section anchor="privacy">
      <name>Privacy Considerations</name>
      <t>Tag names and values can reveal
      process secrets. Logs SHOULD store
      digests and tag identifiers, not
      continuous process traces, unless the
      plant already archives those traces
      under existing OT policy.</t>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>This document requests no IANA
      actions.</t>
    </section>

    <section anchor="ipr-note">
      <name>Intellectual Property Note</name>
      <t>Related concepts appear in the DAS
      Protocols family, including
      PCT/IB2026/055615. Disclosure should
      follow BCP 79 <xref target="RFC8179"/>.</t>
    </section>

    <section anchor="conclusion">
      <name>Conclusion</name>
      <t>Fieldbuses move coils. Agents and MES
      APIs now write the setpoint. A signed
      OPC call, a jump-host session, or a
      work order is not authority for this
      value to become motion on this device
      now. Bind the digest. Consume at the
      I/O sink. A setpoint write is not
      actuation.</t>
    </section>
  </middle>
  <back>
    <references>
      <name>Normative References</name>
      <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author initials="S." surname="Bradner" fullname="S. Bradner"/>
          <date year="1997" month="March"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
      </reference>
      <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author initials="B." surname="Leiba" fullname="B. Leiba"/>
          <date year="2017" month="May"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="8174"/>
      </reference>
      <reference anchor="RFC8179" target="https://www.rfc-editor.org/info/rfc8179">
        <front>
          <title>Intellectual Property Rights in IETF Technology</title>
          <author initials="S." surname="Bradner" fullname="S. Bradner"/>
          <author initials="J." surname="Contreras" fullname="J. Contreras"/>
          <date year="2017" month="May"/>
        </front>
        <seriesInfo name="BCP" value="79"/>
        <seriesInfo name="RFC" value="8179"/>
      </reference>
    </references>
    <references>
      <name>Informative References</name>
      <reference anchor="I-D.das-6g-finality">
        <front>
          <title>Execution-Finality for AI-Native 5G/6G and O-RAN</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="August"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-ai-native-6g-execution-finality-01"/>
      </reference>
      <reference anchor="I-D.das-agentic">
        <front>
          <title>Tool Selection Is Not Execution: Finality for Agentic Tool Dispatch</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="August"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-agentic-execution-finality-01"/>
      </reference>
    </references>
  </back>
</rfc>
