| <!-- Copyright 2026 The Fuchsia Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. --> |
| |
| # 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 |
| |
| ```text |
| 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: |
| |
| ```python |
| 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: |
| |
| ```toml |
| 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: |
| |
| ```json |
| { |
| "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: |
| |
| ```text |
| 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: |
| |
| ```text |
| 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: |
| |
| ```text |
| 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: |
| |
| ```text |
| 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 |
| |
| - [Fuchsia Controller remote scripting](https://fuchsia.dev/fuchsia-src/development/tools/fuchsia-controller/scripting-remote-actions) |
| - [Driver communication and services](https://fuchsia.dev/fuchsia-src/concepts/drivers/driver_communication) |
| - [Fuchsia registers driver](https://fuchsia.dev/fuchsia-src/development/drivers/driver_guides/registers/overview) |
| - [Driver runner and driver hosts](https://fuchsia.dev/fuchsia-src/concepts/components/v2/driver_runner) |
| - [FFX overview and target connections](https://fuchsia.dev/fuchsia-src/development/tools/ffx/getting-started) |
| - [FDomain RFC](https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0228_fdomain) |
| |
| ## 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 |
| |
| | Stage | Phase 1 (Standalone Proxy) | Phase 2 (Embedded In-Situ Library) | |
| | :--- | :--- | :--- | |
| | **Node State** | Unclaimed dev-node (no driver bound) | Active driver bound and running | |
| | **Target Moniker** | `lab_proxy.cm` on synthetic or test node | Bound driver moniker (e.g. `/bootstrap/boot-drivers:sample-driver`) | |
| | **Transport Connection** | `connect_device_proxy(proxy_moniker, ...)` | `connect_device_proxy(driver_moniker, ...)` | |
| | **Hardware State** | Freshly initialized / unclaimed | Live in-flight state (preserves bugs) | |
| | **Teardown / Restore** | Stop `lab_proxy`; discard node | Close 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. |