driver-lab host tooling specification

This is the normative specification for the driver-lab host tooling at //tools/driver-lab. The companion proxy driver specification lives at //src/devices/driver-lab/SPEC.md.

Phase 1 -- the unclaimed-node proxy workflow, which requires no Driver Manager changes -- is delivered first. Phase 2 -- in-situ driver debugging via an embedded library -- extends host tooling to active, bound drivers without managed takeover or Driver Manager changes, requiring no major contract change. Phase 3 -- software state, runtime knobs, and adaptive bug verification -- extends host plans, the Python API, and agent skills to drive single-build software state and concurrency experiments over StateBank resources.

Phase 1: unclaimed-node proxy workflow

Implementation status

Changeset numbers (CS) are global across this specification and //src/devices/driver-lab/SPEC.md, whose changesets interleave with these; gaps in the numbering below are proxy driver changesets. Every changeset marks itself complete in this list when it lands, so at any commit this list shows which changes precede and which follow that commit.

  • [x] CS2 [driver-lab] Host tooling specification -- this document.
  • [x] CS4 [driver-lab] Transport-neutral host tooling core -- frozen data models (18), grant resolution with the TOML store (10.3-10.5), and plan validation, canonicalization, and digests carrying the reserved takeover keys (11).
  • [x] CS5 [driver-lab] Evidence recording + proxy transport abstraction -- hashed manifest-last evidence bundles (16) and the wire-contract-shaped transport seam with the fake proxy target (12, 20.3).
  • [x] CS6 [driver-lab] Consent resolution and the plan-run API -- interactive consent with exact-rule persistence and fail-closed unattended behavior (10.2), and DriverLab.run_plan composing prepare/execute/finalize with mode and guarantee resolution before any connection (6.2, 7.3, 14).
  • [x] CS7 [driver-lab] fuchsia-controller FIDL transport adapter -- ProxyTransport over the generated bindings, exercised through real FIDL encoding on the build host (12, 20.3).
  • [x] CS8 [driver-lab] Add host CLI tools -- driver-lab.pyz with run, permissions list/explain/add/revoke, plan digest/validate, and spec exit categories (10.5, 12, 13).
  • [x] CS10 [driver-lab] Add live-target conformance runbook -- ephemeral registration and test-node activation exercising the real driver end to end (14, 20.4), verified on the emulator.
  • [x] CS11 [driver-lab] Host node discovery via fuchsia.driver.development -- typed node enumeration, bound driver inspection, unclaimed-node detection over FIDL, and expected_unclaimed plan verification (7.1, 8.3, 14.2, 15; milestones H0, H1).
  • [x] CS12 [driver-lab] Direct mode client and published-protocol workflow -- direct-mode capability validation, typed protocol discovery, protocol transactions, and target audit exclusion (7.1, 8.2, 15; milestone H1).
  • [x] CS13 [driver-lab] Target ceiling policy manifests and verification -- immutable target ceiling manifests, canonical JSON and deterministic SHA-256 digests, non-widening narrowing validation, and one-shot vs poll enforcement (9.2, 9.6, 22, 23.1; milestone P1 remainder).

Remaining phase 1 work:

  • [x] CS14 [driver-lab] Bounded sequences and mutation in proxy driver (11.4, 11.5, 12.2-12.4, 13; milestone P3).
  • [x] CS15 [driver-lab] Host tooling support for mutation and sequences (11, 14; milestone P3 / H4).
  • [x] CS16 [driver-lab] Driver-shaped public Python API -- connect/attach, HardwareSession, MmioRegion, and representative protocol resources (6.1, 9; milestone H4).
  • [x] CS17 [driver-lab] Stop-path hardening and cancellation (19; milestone P2).
  • [x] CS18 [driver-lab] Host CLI subtool expansion and verified teardown (8.3, 12, 14.2; milestone H3 remainder).
  • [x] CS19 [driver-lab] Protocol resources, typed endpoints, and heterogeneous sequence (8.1, 8.4, 9.5, 11.5, 11.6, 12, 14.2; milestone P4).
  • [ ] Engineering assembly inclusion and production-absence verification (7.1, 25).
  • [x] CS20 [driver-lab] Interrupt observation and local acknowledgement (11.7, 17; milestone P5).
  • [x] CS21 [driver-lab] Structured configuration and per-class rate limits (in //src/devices/driver-lab/SPEC.md).
  • [x] CS22 [driver-lab] Serial capture and independent recovery integration (3, 14.1; milestone H5).
  • [ ] Register-metadata-backed consent expansion (10.2; open decision 8).

Phase: 1 -- new-driver development. Covers direct mode and proxy access on unclaimed nodes. In-situ debugging of existing drivers via an embedded library is specified in the Phase 2 section of this document and in //src/devices/driver-lab/SPEC.md, and requires no changes to this contract.

Scope: host Python API, ffx integration, operator authorization, orchestration, and evidence

Companion specifications: the proxy driver specification at //src/devices/driver-lab/SPEC.md, and the Phase 2 section of this document

1. Purpose

The host tooling provides a single, driver-shaped Python programming model for interactive hardware exploration and agent-assisted Fuchsia driver development. The same entry point supports two target access modes:

  1. direct use of protocols already published by a running driver; and
  2. private-resource access through an engineering-only proxy driver.

In phase 1 the proxy binds directly to an otherwise unclaimed development node. In-situ debugging of existing, active drivers through an embedded library is specified in phase 2; the wire and transport contracts here support both deployment paths without breaking changes.

The tooling also exposes an ffx interface, coordinates out-of-band recovery, enforces operator-consent policy, freezes executable plans, and preserves the raw evidence needed to reproduce and audit an experiment.

This specification deliberately does not define how the proxy maps MMIO, handles interrupts, or enforces its target-side policy. Those requirements are owned by the proxy driver specification at //src/devices/driver-lab/SPEC.md.

2. Normative language

The terms must, must not, should, and may describe requirements, recommendations, and optional behavior respectively.

The following terms have specific meanings:

  • Direct mode: The host connects to a FIDL protocol already published by the running driver. The driver remains bound and active.
  • Proxy mode: The host connects to the isolated proxy driver for private, policy-controlled target resources. In phase 1 the node is an unclaimed development node.
  • In-situ debugging: Connecting directly to an active driver's embedded driver-lab endpoint for live hardware exploration without unbinding or quiescing device state. Defined in the Phase 2 section of this document and in //src/devices/driver-lab/SPEC.md.
  • Target ceiling: Immutable target-side limits on resources and operations. Host approval can narrow this ceiling but cannot widen it.
  • Read grant: Operator authorization for a particular class of register reads.
  • Plan: Canonical, bounded experiment intent whose digest changes whenever any executable field changes.
  • Evidence: Raw requests, responses, target identity, audit records, transport events, recovery events, and serial output saved before interpretation.

3. Goals

The host tooling must:

  1. Reach a target control plane served by Driver Manager in the bootstrap realm, with no dependency on a component in the core realm for the target-side endpoint.
  2. Be reachable through supported ffx target transports without precluding a serial-capable transport; serial support itself is separate ffx-team-owned work and does not gate phase 1.
  3. Present one typed asynchronous Python API across direct and proxy modes.
  4. Preserve the production-driver abstractions used for MMIO, GPIO, SPI, I2C, serial, clock, reset, and interrupt access.
  5. Discover target nodes, bound drivers, and published protocols through the existing fuchsia.driver.development surface rather than a parallel discovery protocol.
  6. Make backend capabilities and safety guarantees visible to callers.
  7. Never silently replace a policy-controlled operation with a weaker direct operation.
  8. Require operator authorization for previously unapproved register reads.
  9. Persist an exact read grant when the operator chooses “always allow.”
  10. Fail closed in unattended operation when a required grant is absent.
  11. Require explicit approval for mutating plans.
  12. Remain forward-compatible with phase 2 managed takeover: plan, session, and evidence schemas reserve the takeover fields so phase 2 requires no major contract change.
  13. Produce complete, hashed evidence for every attempted hardware run.
  14. Integrate with Driver Agent without combining proposal, approval, execution, and interpretation into one opaque step.
  15. Keep serial capture, reset, and power recovery independent of the in-band experiment transport.

4. Non-goals

The host tooling must not:

  • expose arbitrary physical addresses;
  • claim that devfs or a published service reveals a driver's private MMIO, interrupt handles, or incoming namespace;
  • infer that every offset in an MMIO region is safe to read;
  • interpret a persistent host grant as a target security capability;
  • silently authorize an offset because a nearby offset was approved;
  • silently downgrade a requested access mode or guarantee;
  • automatically retry a mutating plan after a disconnect, reboot, or uncertain partial execution;
  • promise that stopping a production driver preserves its live hardware state;
  • execute arbitrary Python or shell expressions from a plan;
  • coordinate multiple device nodes in one plan or session (single-node only in V1);
  • guarantee sub-millisecond target-local timing precision;
  • provide a universal translation from every Python convenience to Rust or C++; or
  • require the embedded proxy library discussed in earlier designs.

5. Architecture

Driver Agent / Python script / human CLI
                    |
                    v
Driver-shaped Python API and plan/evidence layer
                    |
                    v
FFX transport adapter
  network / USB / serial-capable target transport
                    |
                    v
Driver Manager control plane
  discovery: fuchsia.driver.development
  (takeover protocol arrives in phase 2)
          |                           |
          | direct                    | proxy
          v                           v
Published driver protocol       Isolated proxy driver
running driver remains bound    bound to an unclaimed node

Paniolo or equivalent --- serial / liveness / reset / power recovery

The Driver Manager integration is a control plane. Hardware operations run in the production driver in direct mode or in the isolated proxy driver in proxy mode. Driver Manager must not become a generic MMIO executor.

5.1 Why there is no core broker

The target endpoint must remain available on engineering systems whose normal component topology or networking is unavailable. Discovery and channel routing therefore belong at the Driver Framework/Driver Manager layer rather than at a stable moniker under core. This placement was suggested by driver-framework engineering during early design review.

Two requirements are distinct here. The target-side control plane must not live under core; serving it from Driver Manager in the bootstrap realm satisfies that. End-to-end operation without core additionally requires a transport that terminates outside core; no such ffx transport exists today, so this design must not preclude one, and serial transport is tracked as separate work with the ffx team. The public Python API must not expose the transport choice.

5.2 Standalone proxy vs embedded library

The standalone proxy (lab_proxy) is the Phase 1 mechanism for private hardware access on unclaimed nodes. For nodes with an active, bound driver, Phase 2 introduces the embedded library (driver_lab_rust / driver_lab_cpp) for in-situ debugging. Rather than unbinding the driver, the embedded library preserves live hardware state and solves clock/power gating by running inside the active driver component, using cooperative quiesce locks and interrupt event taps to prevent concurrent interference.

6. Design invariants

6.1 Programming-model fidelity

The public Python API must preserve, as closely as practical:

  • resource types;
  • production FIDL method boundaries;
  • MMIO access width and ordering;
  • asynchronous waits;
  • error propagation;
  • resource acquisition and lifetime;
  • explicit resets and state transitions; and
  • the distinction between semantic device protocols and raw registers.

Discovery, transport, consent, target policy, batching, and evidence may be implemented underneath or alongside this API. They must not flatten every hardware resource into an unrelated generic remote-control interface.

For protocol-backed resources, use generated Python FIDL types directly or a thin shape-preserving adapter. For MMIO, expose a remote MmioRegion object whose scalar operations resemble the MMIO operations used by an on-device driver. Target-local batches are an explicit timing and safety facility, not the only programming model.

6.2 Explicit capability degradation

The host must report:

  • selected access mode;
  • resource-level capabilities;
  • target-policy enforcement;
  • target audit availability;
  • target-local timing availability;
  • fault-isolation level;
  • whether a production driver remains active; and
  • whether restoration will be required (always false until phase 2).

Unsupported operations fail before hardware access. A direct-mode connection must not pretend that private MMIO, private interrupts, target audit, or target-local batch timing is available.

6.3 Evidence before interpretation

Raw evidence is finalized before an agent may label an observation verified, contradicted, or inconclusive. Interpretation is a derived artifact and must refer back to exact operation results.

6.4 Independent recovery

The experiment channel is not the recovery channel. Serial capture, target liveness, reset, and power control remain usable after loss of the FIDL or ffx path.

7. Access modes

7.1 Direct published-protocol mode

Direct mode connects to a protocol already published by the active driver. Discovery may use an aggregated service, devfs compatibility path, or another Driver Manager-provided route, but those details are hidden below the public API.

Direct mode is appropriate for:

  • black-box driver testing;
  • normal device configuration;
  • querying state exposed by the driver;
  • standard GPIO, SPI, I2C, serial, clock, or reset protocols;
  • an existing fuchsia.hardware.registers endpoint; and
  • low-risk ad-hoc exploration that does not require private resources.

Direct mode does not provide private-register reflection. It provides only what the running driver publishes.

7.2 Proxy mode

Proxy mode is appropriate when the caller needs:

  • MMIO owned privately by the production driver;
  • target-local polling or timing;
  • proxy target policy and audit;
  • proxy interrupt observation;
  • exclusive experimental ownership; or
  • an initialization sequence that precedes the production driver.

In phase 1 the node must be unclaimed: the proxy binds through engineering-only node metadata and existing registration and bind mechanisms. If a normal driver is bound, the node requires managed takeover, which is phase 2 and is reported as unsupported. Either way the proxy backend must be compatible with the resources offered by the parent.

7.3 Selection rules

The caller may request direct, proxy, or auto. A proxy request may also require a specific activation policy: bind-unclaimed (phase 1) or takeover (reserved; rejected as unsupported until phase 2).

auto may select direct mode only when every plan requirement is satisfied by direct mode. It must never:

  • unbind a driver (takeover authorization does not exist until phase 2);
  • fall back from a required target policy to host-only validation;
  • fall back from target-local timing to host round trips;
  • convert private-resource intent into a semantically different public protocol call; or
  • downgrade a mutating plan.

The selected mode and its guarantees are included in the canonical plan and evidence.

8. Target control-plane contract

Discovery reuses the existing fuchsia.driver.development and fuchsia.driver.registrar protocols; this specification adds no parallel discovery surface and phase 1 requires no Driver Manager changes. The engineering-only takeover protocol is defined in the Phase 2 section of this document and in //src/devices/driver-lab/SPEC.md. The exact FIDL syntax is revision-dependent, but the semantics below are normative.

8.1 Node discovery

Discovery uses fuchsia.driver.development (GetNodeInfo, GetDriverInfo, and related methods). Work package 1 produces a gap matrix mapping each required field to that surface in the selected revision. Discovery must provide:

  • stable node identity criteria (moniker and node properties, not per-boot numeric IDs);
  • bound driver URL and driver-host koid, if any; and
  • published protocol/service descriptors, composed from devfs or component-framework queries where fuchsia.driver.development does not report them directly.

Boot identity, proxy generation, resource descriptions, and their digests are reported by the bound proxy's Describe, not by Driver Manager. Takeover eligibility, active proxy-access state, and pending restoration failure are reported by the phase 2 takeover protocol, not by node discovery.

Physical addresses and raw handles are never returned.

8.2 Direct connection

Direct mode connects only to a protocol that discovery reported. Phase 1 uses existing devfs or aggregated-service routing; the host tooling validates the typed protocol/service selector itself and never accepts an arbitrary path or component moniker from plan JSON. A typed Driver Manager route may be added later if selector validation proves insufficient.

8.3 Proxy activation on unclaimed nodes

Phase 1 activates the proxy only on nodes with no bound driver:

  • the proxy is loaded through engineering assembly inclusion or ephemeral registration (fuchsia.driver.registrar);
  • eligibility comes from explicit engineering-only node metadata or a developer-created test node, never from broad bind rules;
  • before connecting, the host verifies the node identity criteria and that the bound driver is the expected proxy at the expected generation;
  • the host connects to the proxy's published service through existing devfs or aggregated-service routing, then correlates the endpoint back to stable node identity; and
  • ending access stops or unbinds the proxy and verifies the node is again unclaimed.

The caller cannot select an arbitrary driver package. The managed-takeover activation path (BeginProxyAccess, EndProxyAccess, exclusive leases, and restoration) is specified in phase 2; phase 1 plans, sessions, and evidence reserve the fields those flows require.

9. Public Python API

The illustrative API below fixes responsibilities, not final spelling:

class DriverLab:
    @classmethod
    async def connect(
        cls,
        target: str,
        *,
        transport: str = "auto",
        timeout_s: float = 10.0,
    ) -> "DriverLab": ...

    async def list_nodes(self) -> list[NodeSummary]: ...
    async def describe_node(self, node_id: str) -> NodeDescription: ...

    async def attach(
        self,
        node_id: str,
        *,
        mode: Literal["auto", "direct", "proxy"],
        requirements: AccessRequirements,
        context: RunContext,
    ) -> "HardwareSession": ...

class HardwareSession:
    @property
    def capabilities(self) -> SessionCapabilities: ...

    async def mmio(self, resource: str) -> "MmioRegion": ...
    async def gpio(self, resource: str) -> "Gpio": ...
    async def spi(self, resource: str) -> "Spi": ...
    async def i2c(self, resource: str) -> "I2c": ...
    async def serial(self, resource: str) -> "Serial": ...
    async def clock(self, resource: str) -> "Clock": ...
    async def reset(self, resource: str) -> "Reset": ...
    async def interrupt(self, resource: str) -> "Interrupt": ...
    async def sequence(self, operations: list[Operation]) -> ExecutionReport: ...

class MmioRegion:
    async def read32(self, offset: int) -> int: ...
    async def write32(
        self,
        offset: int,
        value: int,
        *,
        mask: int,
        expected_before: int | None = None,
        expected_mask: int | None = None,
        require_readback: bool = True,
    ) -> WriteResult: ...

    async def poll32(
        self,
        offset: int,
        *,
        expected: int,
        mask: int,
        interval_s: float,
        timeout_s: float,
    ) -> PollResult: ...

sequence is available only when the selected backend can preserve the requested ordering and timing. Resource-specific methods remain the canonical programming model.

9.1 Translation metadata

Each public hardware method should document:

  • its closest production C++ and Rust analogue;
  • whether it is directly translatable;
  • differences caused by remote execution;
  • whether it depends on target-local timing; and
  • whether it is an experiment-only convenience.

A future translation checker may reject or flag scripts that use experiment-only facilities. Automatic translation is not required in the initial implementation.

9.2 Async behavior

Use one asyncio event loop per process. All waits have host deadlines. Target deadlines must expire slightly before host deadlines so a normal target timeout can be distinguished from transport loss.

Do not call blocking sleep from asynchronous code. Hold strong references to serial, health, evidence, and execution tasks until each has completed or been explicitly canceled and joined.

10. Operator authorization

10.1 Two independent layers

Authorization has two layers:

  1. The target ceiling determines what the target can ever access.
  2. Host consent determines what the current operator or agent may request within that ceiling.

A host grant never widens the target ceiling. A target accepting a session-specific allowlist does not prove who approved it; approval provenance is evidence, not cryptographic authorization.

10.2 Default read behavior

An MMIO read that matches neither an active plan approval nor a persistent grant must pause for operator consent. In unattended operation it fails closed. For an unknown register, the active plan approval must itself contain an explicit human read decision; classifying a plan as read-only is not sufficient authorization.

The interactive choices are:

  • Allow once: authorize the exact access rule for the current canonical plan or interactive session.
  • Always allow: persist the exact read rule.
  • Deny once: reject the request without changing persistent policy.
  • Always deny: persist a matching denial.

The UI must warn that reads may clear status, consume FIFO data, acknowledge events, or fault when hardware dependencies are disabled.

“Read all registers” is not a primitive permission:

  • when register metadata exists, the host expands the request into the exact declared readable offsets and resolves consent for each rule;
  • when only an MMIO byte range is known, the host must describe the action as a raw range sweep rather than pretending every aligned word is a register;
  • a raw range sweep requires an explicit human-approved plan enumerating every offset before execution; and
  • the initial implementation must not persist an “always allow this entire MMIO region” grant.

The UI may approve an exact displayed set in one interaction, but the stored and target-enforced result remains a set of exact access rules.

Consent gates register-class access regardless of transport. A direct-mode read through a published raw-register endpoint such as fuchsia.hardware.registers resolves against the same plan approvals and persistent grants as a proxy MMIO read. Semantic protocol operations (GPIO, SPI, I2C, serial, clock, reset) are governed by plan approval and target policy, not per-offset read consent.

Register metadata is a first-class deliverable, not an ambient assumption: its schema, provenance, and review process must be specified before consent expansion depends on it (see open decisions).

In V1 the proxy session allowlist is fixed when the session opens. Consent granted mid-session takes effect by closing the session and reopening it with the expanded allowlist; a narrowing-only ExtendAllowlist operation is a deferred open decision.

Permission-resolution evidence records the source of every decision -- persistent rule, allow-once, always-allow, deny-once, always-deny, or fail-closed -- so a run's authorization basis is auditable. A persistent deny short-circuits prompting for the rest of the plan.

10.3 Grant identity

A persistent read grant must include:

  • grant schema version;
  • stable target/product scope;
  • stable node identity criteria;
  • resource-description digest;
  • logical resource name or ID;
  • byte offset;
  • width;
  • access class;
  • optional poll limits;
  • decision;
  • approval timestamp;
  • approval source or identity when available; and
  • optional rationale/reference.

It must not match solely by nodename, boot ID, devfs path, or dynamic service instance.

The resource-description digest covers the resource kind, logical size, provider identity, and any stable metadata needed to prevent an old grant from matching a changed mapping. A boot change alone need not invalidate a persistent read grant; a resource-description change does.

10.4 Access classes

At minimum distinguish:

  • one-shot read;
  • bounded snapshot;
  • bounded poll;
  • write;
  • protocol transaction; and
  • interrupt wait.

A one-shot read grant does not implicitly authorize polling. Poll authorization includes maximum interval frequency and timeout. All target-local execution remains subject to proxy limits.

10.5 Persistent configuration

The persistent store is a checked, human-readable TOML file. For example:

schema_version = 1

[[read_grants]]
target_scope = "example-engineering-target"
node_id = "example-device"
resource_digest = "sha256:..."
resource = "control"
offset = 0x3c
width = 32
access = "read_once"
decision = "allow"
approved_at = "2026-07-29T23:10:00Z"
reason = "Reviewed against the device register specification"

Interactive persistence must:

  1. lock against concurrent writers;
  2. parse and validate the existing file;
  3. reject ambiguous or overlapping entries;
  4. write a temporary file;
  5. flush and atomically replace the original;
  6. preserve a revision/audit record; and
  7. print the exact rule that was added.

The CLI provides list, explain, add, and revoke operations. Manual config edits are allowed but undergo the same validation on load.

10.6 Writes

Persistent read permission never authorizes a write. A mutating plan requires:

  • an explicit target write policy;
  • exact value and mask;
  • target/device expectations;
  • a precondition when semantics permit;
  • readback semantics;
  • bounded cleanup or recovery;
  • canonical plan approval; and
  • active recovery monitoring.

The first implementation should not offer “always allow arbitrary write.”

11. Probe plans

Plans are data, not executable code. A representative plan is:

{
  "schema_version": 1,
  "run_id": "2026-07-29T23-10-00Z-status-read",
  "case_id": "device-status-observation",
  "target": {
    "selector": "lab-target",
    "expected_boot_id": "..."
  },
  "node": {
    "id": "example-device",
    "expected_unclaimed": true,
    "expected_resource_digest": "..."
  },
  "access": {
    "mode": "proxy",
    "activation": "bind-unclaimed",
    "requires_target_policy": true,
    "requires_target_audit": true,
    "requires_target_local_timing": false
  },
  "operations": [
    {
      "kind": "mmio_read32",
      "resource": "control",
      "offset": "0x3c"
    }
  ]
}

The plan schema reserves the managed-takeover fields (expected bound driver, expected topology generation, expected proxy identity, restoration policy) for phase 2. For mutation the canonical plan records target policy digest, proxy generation, exact operation fields, and recovery intent.

Plans must not support:

  • Python expressions;
  • shell interpolation;
  • arbitrary driver URLs;
  • arbitrary component monikers or protocol paths;
  • includes outside approved workspace roots; or
  • unvalidated plugins.

Canonicalization normalizes numeric representations, key order, and operation order before hashing.

12. FFX tool and transport adapter

The supported command surface should be available as an ffx subtool so that target discovery and transport selection remain in the Fuchsia tooling layer. Proposed commands:

ffx driver-lab list
ffx driver-lab describe --node <id>
ffx driver-lab direct --node <id> --protocol <typed-selector>
ffx driver-lab bind-proxy --node <id>
ffx driver-lab end-proxy --node <id>
ffx driver-lab run --plan <json> --evidence-dir <dir>
ffx driver-lab permissions list
ffx driver-lab permissions explain --plan <json>
ffx driver-lab permissions revoke --grant <id>

ffx driver-lab takeover arrives in phase 2.

The Python package links fuchsia-controller in process and uses the generated fuchsia.driver.lab bindings, behind a transport-neutral ProxyTransport interface so tests run against a wire-contract-faithful fake and future transports slot in without public API changes. Transport implementation must not leak into hardware scripts.

Until the ffx subtool exists, the same operations ship as a standalone driver-lab.pyz host tool with JSON on stdout, diagnostics on stderr, and the exit categories below.

A serial-capable path, once the separately tracked transport work lands, must be tested without the normal core topology before it is considered supported. It does not gate phase 1.

13. CLI behavior

Structured results go to stdout and diagnostics/prompts go to stderr or the dedicated interactive UI channel.

Recommended exit categories:

0  success
2  local argument, plan, or permission error
3  stale target/node/resource expectation
4  operation failure or partial execution
5  transport/channel failure
6  target reboot or liveness failure
7  evidence persistence failure
8  proxy activation failure
9  restoration failure (reserved; used by phase 2)
10 unsupported capability or unsafe downgrade

Ctrl-C cancels bounded execution, finalizes available evidence, and ends proxy access when one is active. A second interrupt may request out-of-band escalation but must not abandon proxy-access state silently.

14. Proxy-mode workflow

The managed-takeover workflow is specified in phase 2. The phase 1 unclaimed-node workflow is:

14.1 Prepare

  1. Discover the node and verify it is unclaimed.
  2. Capture boot, node, and resource identities.
  3. Confirm a compatible proxy is available.
  4. Resolve plan requirements.
  5. Resolve persistent read grants.
  6. Prompt for missing read consent.
  7. Require explicit mutation approval when applicable.
  8. Freeze and hash the canonical plan.
  9. Verify independent serial/recovery availability.

Preparation performs no hardware operations.

14.2 Execute

  1. Create a non-reused evidence directory.
  2. Start serial and liveness capture.
  3. Activate the proxy on the unclaimed node with frozen expectations.
  4. Describe the bound proxy and verify its policy/resource digest.
  5. Open a session with an exact session allowlist.
  6. Execute the bounded plan, draining proxy audit after every mutating operation so the host record never runs more than one write ahead of drained target audit.
  7. Drain remaining proxy audit.
  8. Close the proxy session.
  9. End proxy access and verify the node is again unclaimed.
  10. Stop capture and finalize evidence.

14.3 Failure behavior

  • A stale expectation fails before activation.
  • Channel loss never causes automatic replay.
  • If a write may have completed, the result remains partial or unknown.
  • A reboot invalidates all boot-scoped expectations.
  • Recovery creates a new run context; it does not resume the old mutation.

15. Direct-mode workflow

  1. Discover a typed published protocol.
  2. Record the running driver identity and generation.
  3. Confirm the plan requires no private resources or takeover guarantees.
  4. Resolve any applicable host consent.
  5. Connect through Driver Manager's typed route.
  6. Execute production FIDL methods through generated or shape-preserving Python bindings.
  7. Save requests, responses, timing, and transport events.

Target proxy policy and audit are absent unless the published protocol itself provides equivalent guarantees.

16. Evidence

Each run gets a new directory:

evidence/<run_id>/
    manifest.json
    plan.requested.json
    plan.canonical.json
    approval.json
    permission-resolution.json
    target.description.json
    node.before.json
    access.capabilities.json
    proxy-access.jsonl
    execution.response.json
    operations.jsonl
    target-audit.jsonl
    node.after.json
    restoration.json
    serial.log
    host-events.jsonl
    interpretation.json

Files that do not apply to a run are marked not applicable in the manifest rather than fabricated. restoration.json and the takeover entries of proxy-access.jsonl are reserved for phase 2 and marked not applicable in phase 1 runs.

manifest.json contains:

  • evidence schema version;
  • run and case IDs;
  • canonical plan digest;
  • selected access mode and guarantees;
  • target boot/build/product identity;
  • node, driver, resource, proxy, and policy identities;
  • resolved grant IDs and approval source;
  • start and end wall-clock timestamps;
  • target monotonic range when available;
  • proxy-access outcome (takeover/restoration fields reserved for phase 2);
  • serial capture metadata;
  • terminal exit category;
  • audit-gap status; and
  • hash and byte length of every evidence file.

Write each artifact to a unique temporary file, flush, close, atomically rename, and hash the final bytes. Write the manifest last. A run whose evidence cannot be finalized is not successful.

17. Driver Agent integration

The workflow remains:

proposed
    -> permissions-resolved
    -> approved
    -> running
    -> observed
    -> interpreted
    -> verified | contradicted | inconclusive

Driver Agent:

  1. proposes the smallest plan needed to resolve a claim;
  2. requests missing consent rather than broadening the plan;
  3. freezes approved intent;
  4. invokes the host API;
  5. watches execution and recovery;
  6. preserves raw evidence;
  7. interprets only finalized evidence; and
  8. never converts a transport success into semantic verification.

Agent interpretation cites the operation, resource, offset or protocol method, raw values, target timestamp, driver/proxy generation, and relevant source.

18. Host data models

Use frozen dataclasses or equivalent validated models for:

  • node summaries and descriptions;
  • access requirements and capabilities;
  • target and resource identity;
  • run context;
  • read grants and permission decisions;
  • plans and approvals;
  • operation requests/results;
  • proxy-access transitions (takeover/restoration variants reserved for phase 2);
  • audit pages; and
  • evidence manifests.

Conversion among JSON, internal models, generated FIDL, and subprocess messages occurs only in explicit functions. Do not pass unvalidated dictionaries through the system.

19. Security and trust boundaries

The host consent file protects the workflow from accidental or unapproved agent behavior. It is not a target security boundary: a malicious host with engineering access may bypass it.

The target ceiling must remain safe enough for the intended engineering environment even if host approval is bypassed. The plan digest proves stable intent within the workflow; it is not bearer authorization.

Permission files:

  • contain no secrets;
  • use least-specific filesystem permissions appropriate to the environment;
  • are never read from unapproved workspace paths;
  • have explicit schema and revision history;
  • reject duplicate or ambiguous matches; and
  • are included by digest in evidence.

20. Testing

20.1 Permission tests

Test:

  • exact grant match;
  • offset, width, resource, node, and digest mismatch;
  • boot change with stable resource description;
  • resource-description change;
  • one-shot grant rejected for poll;
  • poll limit enforcement;
  • persistent deny precedence;
  • unattended fail-closed behavior;
  • concurrent config writers;
  • malformed/ambiguous config;
  • atomic persistence; and
  • revoke/explain behavior.

20.2 Plan and evidence tests

Test:

  • deterministic canonicalization and hashing;
  • every executable change invalidates approval;
  • selected access mode is frozen;
  • unsupported guarantee fails before connection;
  • arbitrary path/moniker/driver injection is rejected;
  • partial execution classification;
  • atomic artifact writes;
  • manifest hashes;
  • Ctrl-C finalization;
  • disk-full failure; and
  • no evidence-directory reuse.

20.3 Transport tests

Use fake and physical transports to test:

  • normal network connection;
  • supported USB connection;
  • serial-capable connection without core (applicable once the serial transport lands);
  • target discovery;
  • target reboot;
  • peer closure;
  • transport migration without public API change; and
  • identical structured output across transports.

20.4 Proxy-activation tests

Test:

  • unclaimed-node verification before bind;
  • a node with a bound driver is rejected (takeover is phase 2);
  • incompatible proxy;
  • proxy bind failure;
  • host disconnect during activation and execution;
  • execution failure;
  • proxy stop failure; and
  • the node is verified unclaimed after end of access.

Managed-takeover state-transition tests are specified in phase 2.

20.5 Direct-mode tests

Test:

  • typed protocol discovery;
  • direct production FIDL call;
  • no private MMIO capability;
  • no target-audit claim;
  • direct-mode evidence;
  • refusal of a takeover-only plan; and
  • no fallback after direct connection failure.

20.6 Driver Agent tests

Test:

  • prepare performs no access;
  • missing read grant pauses or fails closed;
  • “always allow” persists the exact rule;
  • plan mutation invalidates approval;
  • target drift fails before proxy activation;
  • interpretation cannot precede finalized evidence;
  • target loss causes no automatic mutation retry; and
  • a failed proxy teardown prevents a verified outcome.

21. Build and repository placement

The ffx subtool, target bindings, and transport integration belong in the Fuchsia tree. The Python package is built there, at tools/driver-lab, because it links fuchsia-controller and the generated bindings.

Driver Agent adapters, workflow approval objects, and repository-specific orchestration remain in the external Driver Agent repository.

Every run records exact revisions and dirty-state artifacts for both repositories. Directory layout is not part of the evidence identity.

22. Milestones

Milestone H0: transport and control-plane feasibility

  • prove the target control plane is reachable through a supported transport;
  • produce the serial-transport discovery report (FDomain byte-stream connector status, bootstrap-realm host candidates, ffx team roadmap);
  • enumerate nodes and bound drivers via fuchsia.driver.development and produce the discovery gap matrix;
  • validate unclaimed-node proxy binding via existing registration and bind mechanisms; and
  • decide the stable Python-to-ffx adapter.

Exit: no architecture-critical dependency is assumed rather than demonstrated.

Milestone H1: direct mode

  • typed node/protocol discovery;
  • one direct published-protocol interaction;
  • capability reporting;
  • structured CLI output; and
  • evidence bundle.

Exit: direct mode makes no claim to private resources or target audit.

Milestone H2: permissions

  • read-grant model;
  • interactive allow-once/always-deny flows;
  • atomic persistent config;
  • unattended fail-closed behavior; and
  • plan permission resolution.

Exit: an unapproved register read cannot reach the target.

Milestone H3: proxy activation on unclaimed nodes

  • direct proxy bind for an unclaimed synthetic node;
  • prepare, activate, execute, and end-access workflow;
  • stale-state validation;
  • disconnect handling;
  • evidence of every transition; and
  • failure recovery.

Exit: the proxy activates, executes, and deactivates on a synthetic unclaimed node without manual target intervention.

Milestone H4: driver-shaped Python API

  • MMIO and representative protocol-backed resource objects;
  • capability-aware operation dispatch;
  • translation metadata;
  • target-local sequence support; and
  • async cancellation tests.

Exit: the same overlapping Python operations work through direct and proxy backends without hiding semantic differences.

Milestone H5: Driver Agent and recovery

  • proposal/permission/approval/run/interpret separation;
  • paniolo integration;
  • panic/reboot handling;
  • no automatic mutating retry; and
  • evidence-linked conclusions.

Exit: an agent can complete an approved read-only investigation and safely stop for missing authority.

23. Definition of done

The phase 1 host tooling is ready when:

  • its target control plane has no core component dependency;
  • supported target transports pass end-to-end tests without precluding a serial-capable path;
  • direct and proxy modes share one typed public API;
  • programming-model differences remain visible;
  • private resources are never claimed in direct mode;
  • unknown reads require exact operator consent;
  • persistent grants are stable, auditable, and revocable;
  • mutations require target policy and explicit plan approval;
  • proxy access on an unclaimed node activates, executes, and tears down verifiably;
  • transport loss never triggers automatic mutation replay;
  • every run produces a complete hashed evidence bundle;
  • Driver Agent separates observation from interpretation;
  • no plan can inject arbitrary target paths, monikers, or driver URLs; and
  • plan, session, and evidence schemas carry the reserved takeover fields so phase 2 requires no major contract change.

24. Open decisions

Before implementation, resolve:

  1. (deferred, ffx-team-owned) The serial-capable target transport; phase 1 must not preclude it.
  2. Resolved: the Python API links fuchsia-controller in process, with the generated fuchsia.driver.lab bindings behind the transport-neutral ProxyTransport seam.
  3. Which node/resource descriptors are stable enough for grant matching.
  4. The user-level versus workspace-level permission-file location and merge precedence.
  5. Whether persistent read grants may be shared across equivalent physical targets.
  6. The first standard FIDL resources used to validate programming-model fidelity.
  7. Whether a narrowing-only ExtendAllowlist session operation replaces V1's reopen-per-grant behavior.
  8. The register-metadata schema, provenance, and review process backing consent expansion.

Takeover-related decisions (force-bind, node persistence, restoration policy, takeover-protocol packaging) live in the Phase 2 section of this document and in //src/devices/driver-lab/SPEC.md.

25. Primary references

Phase 2: in-situ driver debugging workflow

Implementation status

Changesets continue the global numbering:

  • [x] CS24 [driver-lab] Core library extraction & pre-mapped MMIO adapter (in //src/devices/driver-lab/SPEC.md).
  • [x] CS25 [driver-lab] Embedded Rust driver library with ServiceFs integration (in //src/devices/driver-lab/SPEC.md).
  • [x] CS26 [driver-lab] Cooperative locking, quiesce hooks, and interrupt tap support (in //src/devices/driver-lab/SPEC.md).
  • [x] CS27 [driver-lab] Assembly gating, CML debug shard, and production-absence verification
  • [x] CS28 [driver-lab] Reference integration: synthetic platform driver in testing/
  • [x] CS29 [driver-lab] Host tooling discovery of embedded debug endpoints and in-situ session workflow
  • [x] CS30 [driver-lab] In-situ live-target conformance test suite
  • [x] CS34 [driver-lab] Expose FIDL and debug shard to Bazel (in //src/devices/driver-lab/SPEC.md).
  • [x] CS35 [driver-lab] Native Bazel build for driver_lab_rust (in //src/devices/driver-lab/SPEC.md).
  • [x] CS36 [driver-lab] Add C FFI staticlib shim (driver_lab_c) (in //src/devices/driver-lab/SPEC.md).
  • [x] CS37 [driver-lab] Add C/C++ wrapper and StateVmoBank (in //src/devices/driver-lab/SPEC.md).
  • [x] CS38 [driver-lab] Add C++ embedded server unit tests (in //src/devices/driver-lab/SPEC.md).

Phase: 2 -- in-situ driver debugging workflow. Extends: the Phase 1 section of this document. The wire client, transport adapters, CLI parsing, and evidence models apply unchanged.

Companion specification: the proxy driver specification at //src/devices/driver-lab/SPEC.md.

1. Purpose

Phase 2 enables host tooling (ffx driver-lab, the public Python API, and Driver Agent) to connect directly to active, bound drivers equipped with the embedded driver-lab library. This eliminates the need for managed takeover, avoids disturbing live hardware state, and provides real-time in-situ inspection of wedged or misbehaving devices.

2. Workflow differences: standalone proxy vs in-situ library

StagePhase 1 (Standalone Proxy)Phase 2 (Embedded In-Situ Library)
Node StateUnclaimed dev-node (no driver bound)Active driver bound and running
Target Monikerlab_proxy.cm on synthetic or test nodeBound driver moniker (e.g. /bootstrap/boot-drivers:sample-driver)
Transport Connectionconnect_device_proxy(proxy_moniker, ...)connect_device_proxy(driver_moniker, ...)
Hardware StateFreshly initialized / unclaimedLive in-flight state (preserves bugs)
Teardown / RestoreStop lab_proxy; discard nodeClose session; release mutation lease; driver resumes normal work

3. Design invariants

3.1 No unbind or hardware reset

In-situ debugging connects to the live driver component. It does not unbind the driver, does not drop power domains or clock votes, and does not reset hardware registers.

3.2 Safe reads by default

Scalar reads and snapshots default to non-destructive inspection. Known clear-on-read or FIFO data registers must be marked hard_denied in the target ceiling manifest unless explicitly authorized in an exact session allowlist.

3.3 Cooperative quiesce during mutation

Opening a mutating session acquires an exclusive mutation lease. If registered by the driver, the library invokes an optional driver-registered synchronous quiesce_hook(bool paused) callback to pause driver-side background polling loops while host mutations and sequences execute. If complex driver dispatchers require asynchronous coordination in the future, this interface may be extended to support an async stream/channel notification.

4. Host discovery additions

Host discovery via fuchsia.driver.development.Manager remains the discovery mechanism:

  1. ffx driver-lab list --debug-capable: Queries active drivers on the target and filters for components exposing fuchsia.driver.lab.Service.
  2. ffx driver-lab describe --moniker <driver-moniker>: Fetches ProxyDescription directly from the live driver's embedded endpoint, inspecting its registered MMIO banks, protocol resources, and ceiling policy digest.

5. In-situ experiment workflow

5.1 Prepare

  1. Query target driver monikers and confirm that fuchsia.driver.lab.Service is exposed.
  2. Run describe to fetch registered resources, logical sizes, and the target policy digest.
  3. Operator approves exact read allowlists and probe plans.

5.2 Execute

  1. Create a non-reused evidence directory.
  2. Start serial and liveness capture.
  3. Open session with exact allowlist and expectations matching live driver state.
  4. Execute probe plan (scalar reads, snapshots, timing sequences).
  5. If mutating, the driver's internal background loop is paused via the quiesce interlock while operations execute.
  6. Drain audit ring directly from the driver's embedded buffer.
  7. Close session channel.

5.3 Teardown

  1. Closing the session releases the mutation lease and unquiesces the driver.
  2. No Driver Manager rebind, node restoration, or reboot is required.
  3. Stop capture and finalize evidence with driver moniker and hash verification.

6. Failure behavior

  • Target Disconnect: Dropping the FIDL connection closes the session on the target; the driver's embedded server immediately clears the mutation lease and resumes normal operations.
  • Hardware Wedging: If a mutating register write locks the hardware bus, out-of-band serial capture and target reboot recovery run as specified in Phase 1 Milestone H5.

7. Host surface additions

  • attach and connect accept a driver component moniker directly without requiring an unclaimed node.
  • The CLI adds ffx driver-lab inspect --moniker <id> as the primary entry point for live driver diagnostics.
  • Evidence records the driver component moniker and URL in manifest.json.

Phase 3: software state, runtime knobs, and adaptive verification workflow

Implementation status

Changesets continue the global numbering:

  • [x] CS31 [driver-lab] Software state banks, runtime knobs, and diagnostic triggers (in //src/devices/driver-lab/SPEC.md).
  • [x] CS32 [driver-lab] Host plan aliases and StateHandle API for state and knob experiments
  • [x] CS33 [driver-lab] Update driver-lab and autoda-fix skills for Mode A/B/C verification

Phase: 3 -- software state, runtime knobs, and adaptive verification workflow. Extends: Phases 1 and 2 of this document.

Companion specification: the proxy driver specification at //src/devices/driver-lab/SPEC.md.

1. Purpose

Phase 3 enables host probe plans, the asynchronous Python API, and the autoda-fix skill to interact ergonomically with target-side StateBank resources (CS31) for single-build software state and concurrency experimentation, while selecting the lightest-weight verification mode for any driver bug.

2. Host plan operation aliases and StateHandle API (CS32)

2.1 Plan operation aliases (driver_lab/plans.py, api.py, cli.py)

Probe plans support semantic operation kinds for StateBank resources both at top-level operations and inside sequence.items:

  1. state_read32:
    • Reads a 32-bit state slot or read probe at offset.
    • resource defaults to "state0" when omitted.
    • Maps to AccessClass.READ_ONCE (or AccessClass.SEQUENCE inside a sequence) and executes via session.read32(resource_id, offset).
  2. state_poll32:
    • Polls a 32-bit state slot until (value & mask) == (expected & mask) or timeout (timeout_ns defaults to 100_000_000 when omitted).
    • resource defaults to "state0" when omitted.
    • Maps to AccessClass.POLL and executes via session.poll32(...).
  3. knob_write32:
    • Writes a 32-bit runtime tuning knob or fix-toggle flag at offset.
    • resource defaults to "state0" when omitted; readback defaults to True.
    • Marks the plan as mutating (is_mutating_plan() == True), maps to AccessClass.WRITE (requiring one-time operator consent), and executes via session.write32(...).
  4. trigger_write32:
    • Invokes a deterministic diagnostic trigger callback at offset.
    • resource defaults to "state0" when omitted; accepts arg or value (defaulting to 1); readback defaults to False.
    • Marks the plan as mutating (is_mutating_plan() == True), maps to AccessClass.WRITE (requiring one-time operator consent), and executes via session.write32(...).

Evidence bundles (operations.jsonl) preserve the exact semantic operation kind ("state_read32", "state_poll32", "knob_write32", "trigger_write32") for audit clarity.

2.2 Asynchronous Python StateHandle (driver_lab/session.py)

HardwareSession.state(name: str = "state0") -> StateHandle wraps the named StateBank resource with a driver-shaped asynchronous API:

  • await state.read32(offset) -> int
  • await state.poll32(offset, expected=0, mask=0xFFFF_FFFF, interval_s=0.001, timeout_s=1.0) -> PollOutcome
  • await state.write_knob(offset, value, ..., require_readback=True) -> WriteOutcome
  • await state.trigger(offset, arg=1, ..., require_readback=False) -> WriteOutcome
  • await state.snapshot32(offsets) -> list[int]

3. Adaptive Mode A/B/C verification workflow (autoda-fix, CS33)

The autoda-fix skill (//src/devices/skills/autoda-fix) and companion driver-lab skill (//src/devices/skills/driver-lab) classify driver bugs during Phase 0 reconnaissance into one of three verification modes:

  1. Mode A: Hardware Register / Interrupt Loop (in-situ MMIO/IRQ):
    • Used for bugs involving hardware register configuration, bitfields, clocks, FIFOs, or interrupts.
    • Verifies live register values and interrupt delivery via mmio_read32, mmio_write32, mmio_poll32, and wait_for_interrupt with hashed evidence bundles.
  2. Mode B: In-Situ Software State & Knob Loop (in-situ StateBank):
    • Used for concurrency/race bugs, state-machine bugs, and retry/timeout tuning on live hardware.
    • Phase 1 (Single Instrumentation Build): Instruments suspect code paths with a "state0" StateBank (define_state_slot, define_knob for fault and fix toggles, define_trigger) and deploys once (preferring ffx driver restart <driver_url> for reloadable non-bootfs drivers).
    • Phase 2 (Zero-Compile Experimentation Loop): Executes a Repro Plan (knob_write32 with fix knob = 0, trigger_write32, state_read32) followed in the same boot by a Fix Proof Plan (knob_write32 with fix knob = 1, trigger_write32, state_read32 / state_poll32).
    • Phase 3 (Clean Final Patch): Replaces temporary knobs with the clean, unconditional production fix and adds a permanent unit/realm regression test.
  3. Mode C: Hermetic Test & Log Verification Loop (No driver-lab Required):
    • Used for pure logic, protocol/message parsing, or deterministic lifecycle bugs reproducible via fx test (unit/realm tests) or ffx log.
    • Explicitly forbids shoe-horning driver-lab into Mode C bugs when a unit test or log assertion is faster and more direct.