Problem Statement
Slamcore engineers need a controlled test-farm harness for validating update broadcast reliability, but the project first needs a small, reliable foundation before adding impairment, timeline scenarios, Hawkbit, LXD, or CI. The immediate problem is proving that a developer can invoke the harness locally, provision the intended container topology, move a bundle from an Update Server to multiple clients through a Router Container, receive client-side Verified Receipts through the Controller, and produce a deterministic machine-readable Result File.
The first milestone must deliberately produce a baseline result with no intentional network impairment. This avoids conflating routing, orchestration, receipt collection, and artifact verification with tc behavior before the core lifecycle works.
Solution
Build the project in two initial milestones.
M0 establishes the Python project skeleton and quality gates: pyproject-based packaging, uv dependency management, pre-commit hooks, black, isort, mypy, pytest, and a local quality command equivalent to the expected gates.
M1 implements a one-shot test-farm run invocation that reads a minimal Scenario File containing only client count, allocates the next invocation_instance by scanning existing Result Files, starts the Controller Receipt Channel on an explicit Controller Bind Address, starts separate Router Container, Update Server, and client containers, configures explicit routing through the Router Container, performs a no-impairment bundle download, receives Verified Receipts, derives Client Status and Invocation Status, writes a JSON Result File, captures failure logs after completion, and tears down containers/networks by default unless debug preservation is requested.
User Stories
- As a Slamcore engineer, I want to install project dependencies with
uv, so that I can work on test-farm consistently with the repository's declared tooling.
- As a Slamcore engineer, I want pre-commit hooks to run formatting and static checks, so that basic quality problems are caught before review.
- As a Slamcore engineer, I want black configured with the project line length, so that formatting is consistent across contributors.
- As a Slamcore engineer, I want isort configured with the black profile, so that imports are stable and formatter-compatible.
- As a Slamcore engineer, I want mypy configured from the start, so that type contracts stay explicit as orchestration code grows.
- As a Slamcore engineer, I want pytest configured from the start, so that behavior can be protected as the harness becomes more complex.
- As a Slamcore engineer, I want a single local quality command, so that I can run the same gates before handing work to another agent or reviewer.
- As a Slamcore engineer, I want to run
test-farm run with a Scenario File, so that I can trigger a complete local invocation from one command.
- As a Slamcore engineer, I want the M1 Scenario File to contain only client count, so that baseline scenarios are easy to write and reason about.
- As a Slamcore engineer, I want the Controller to allocate invocation_instance from existing Result Files, so that result numbering is readable and does not depend on timestamps, UUIDs, or hidden state.
- As a Slamcore engineer, I want invocation_instance to start at 1 when no Result Files exist, so that first-run behavior is obvious.
- As a Slamcore engineer, I want Client IDs generated as client-001, client-002, and so on, so that receipts, logs, and reports use stable test-farm identities rather than Docker IDs.
- As a Slamcore engineer, I want the v1 Bundle ID to default to baseline, so that receipts and Result Files identify the expected update bundle even when there is only one bundle.
- As a Slamcore engineer, I want bundle size and checksum to come from the Update Server manifest, so that scenario authors do not have to copy artifact metadata into YAML.
- As a Slamcore engineer, I want the Controller to expose a Receipt Channel, so that client-side verification is collected independently of Update Server state.
- As a Slamcore engineer, I want clients to post receipts to a Controller Reportback URL, so that callbacks work from container networks instead of assuming localhost.
- As a Slamcore engineer, I want the Controller Bind Address to be explicit, so that the listening interface and port are not guessed.
- As a Slamcore engineer, I want the Controller Reportback URL to be explicit, so that the client-reachable address is chosen intentionally.
- As a Slamcore engineer, I want the Controller and Update Server to expose
GET /health, so that readiness and debugging use a simple common endpoint.
- As a Slamcore engineer, I want the Receipt Channel route to include invocation_instance and Client ID, so that late or invalid receipts can be correlated correctly.
- As a Slamcore engineer, I want the Update Server bundle routes to include Bundle ID, so that the artifact being verified is explicit.
- As a Slamcore engineer, I want client containers configured through environment variables, so that clients do not need to parse scenario files or discover their own identity.
- As a Slamcore engineer, I want separate runtime containers for Router Container, Update Server, and client, so that later replacements with Hawkbit and real clients are clean.
- As a Slamcore engineer, I want the Controller to remain a host-side process in M1, so that it can orchestrate Docker lifecycle and receive reports without being part of the impaired network path.
- As a Slamcore engineer, I want the Router Container present in the baseline topology, so that the baseline result validates the same routed shape later impairment will use.
- As a Slamcore engineer, I want M1 to use explicit routing rather than NAT, so that the Update Server can see real client IPs.
- As a Slamcore engineer, I want no
tc operations in M1, so that the first baseline result proves only orchestration, routing, download, verification, reporting, and teardown.
- As a Slamcore engineer, I want the toy client to verify byte count and checksum before reporting success, so that an HTTP 200 from the Update Server is not mistaken for successful receipt.
- As a Slamcore engineer, I want a Verified Receipt to include the invocation_instance, Client ID, Bundle ID, byte count, checksum, status, and error detail when relevant, so that the Result File can explain each client outcome.
- As a Slamcore engineer, I want a small Client Status vocabulary, so that reports are easy to consume by humans and scripts.
- As a Slamcore engineer, I want Invocation Status derived from Client Status values, so that there is no separate manual verdict path.
- As a Slamcore engineer, I want the invocation to fail if any expected client does not succeed, so that partial success does not hide reliability problems.
- As a Slamcore engineer, I want a JSON Result File named with invocation_instance, so that results are machine-readable and easy to inspect locally.
- As a Slamcore engineer, I want the Result File to include invocation timing, scenario file, overall status, expected bundle metadata, and per-client outcomes, so that I can understand what happened without scraping logs.
- As a Slamcore engineer, I want failed client logs captured after completion, so that I can debug failures without requiring live log streaming in M1.
- As a Slamcore engineer, I want containers and Docker networks removed by default after an invocation, so that local development machines are not polluted by stale resources.
- As a Slamcore engineer, I want a debug preservation option, so that I can keep containers and networks when I need to inspect routes or logs.
- As a Slamcore engineer, I want wrong Controller Reportback URLs to surface as timed-out or failed client outcomes in M1, so that the first implementation stays simple.
- As a future implementer, I want the later reportback preflight to run from the client network, so that it validates the same reachability path real clients use.
- As a future implementer, I want impairment Scenario File design deferred until after the baseline result, so that M1 is not blocked by premature timeline/action vocabulary.
- As a future implementer, I want Hawkbit replacement to preserve client-side Verified Receipt semantics, so that success remains based on what the client verified rather than only server-side state.
Implementation Decisions
- Build a project skeleton with
uv, Python packaging metadata, pre-commit, black, isort, mypy, and pytest before implementing the baseline result.
- Implement a host-side Controller as the stable orchestration boundary.
- Model an invocation as one
test-farm run execution from provisioning through final Result File and teardown.
- Allocate invocation_instance by scanning the results directory for filenames matching
result_<invocation_instance>*, incrementing the highest number, and starting at 1 when no prior Result Files exist.
- Do not add a hidden state file, timestamp ID, random ID, UUID, or lock for invocation_instance allocation in M1.
- The M1 Scenario File contains only client count.
- The v1 Bundle ID is required and defaults to baseline, but scenario authors do not specify bundle size or checksum.
- The Update Server manifest owns bundle verification metadata such as expected byte count and checksum.
- The Controller generates Client IDs deterministically from requested client count as client-001, client-002, and so on.
- The Controller passes invocation_instance, Client ID, Update Server URL, Controller Reportback URL, and Bundle ID to each client container through environment variables.
- The Controller exposes a Receipt Channel with
GET /health and POST /invocations/{invocation_instance}/clients/{client_id}/receipt.
- The toy Update Server exposes
GET /health, GET /bundles/{bundle_id}/manifest, and GET /bundles/{bundle_id}.
- The Controller requires an explicit Controller Bind Address and explicit Controller Reportback URL in M1.
- The Controller does not attempt host IP auto-detection in M1.
- The client builds its receipt target from the Controller Reportback URL plus the invocation/client receipt route.
- The toy client reads only environment variables for its configuration.
- The toy client fetches the manifest, downloads the bundle, verifies byte count and checksum, posts a receipt, and exits with an appropriate process result.
- Client Status values are limited to
success, download_failed, checksum_mismatch, receipt_rejected, timed_out, and container_failed.
- Invocation Status is
success only when every expected client has Client Status success; otherwise it is failed.
- The Result File is JSON, named
result_<invocation_instance>.json, and does not include schema_version.
- The Result File records invocation timing, scenario file, overall status, expected bundle metadata, and per-client outcomes.
- Live per-client log streaming is deferred out of M1.
- M1 captures client logs after completion for failures.
- Containers and Docker networks are removed by default after each invocation.
- A debug preservation option keeps containers and networks for inspection.
- M1 uses separate runtime images/containers for Router Container, Update Server, and client.
- The Controller remains a host-side process and is not itself containerized in M1.
- The Router Container is included in M1 topology for forwarding, but no
tc operations are performed in M1.
- The Router Container connects the Update Server network and client network using explicit routing, not NAT.
- The Update Server should be able to observe real client IPs because explicit routing preserves packet identity.
- The Router Container decision follows ADR-0001: use a Router Container with explicit routing for network impairment.
- Later impairment schema, timeline actions, and
tc privilege mechanics are future design tasks, not M1 design commitments.
Potential deep modules to build:
scenario: parses and validates the Scenario File into controller-consumable invocation input.
controller: owns invocation lifecycle orchestration from provisioning through Result File and teardown.
receipt_model: defines Verified Receipt validation, Client Status mapping, and Invocation Status derivation as pure domain logic.
runtime: owns container lifecycle, explicit routing setup, log capture, and teardown.
workloads: contains replaceable workload implementations for the toy Update Server and toy client.
- Docker-specific artifacts live close to the owning runtime or workload modules rather than in a top-level
docker/ directory.
Testing Decisions
- Tests should focus on externally observable behavior and stable contracts, not internal implementation details.
- Scenario parsing tests should verify that the M1 Scenario File accepts only client count and fails loudly on malformed or unknown fields.
- invocation_instance allocation tests should verify start-at-1 behavior, incrementing from existing Result Files, and ignoring unrelated filenames.
- Client ID generation tests should verify deterministic names from requested client count.
- Receipt validation tests should verify matching invocation_instance, Client ID, and Bundle ID, including rejection paths.
- Client Status mapping tests should verify success, download failure, checksum mismatch, rejected receipt, timeout, and container failure outcomes.
- Invocation Status tests should verify success only when every expected client succeeds.
- Result File writer tests should verify JSON shape, filename convention, per-client outcomes, expected bundle metadata, and absence of schema_version.
- Controller HTTP tests should verify
GET /health and receipt route behavior through HTTP-level requests.
- Toy Update Server tests should verify health, manifest, bundle serving, byte count, and checksum consistency.
- Toy client tests should run against a controllable local HTTP server and assert that it downloads, verifies, posts receipts, and fails correctly on bad manifests or corrupted bundles.
- Docker orchestration should have unit tests around command construction and error handling, with at least one integration test gated for environments where Docker is available.
- Explicit routing should be tested with an integration-level test when Docker is available, because route correctness is difficult to prove with unit tests alone.
- Failure log capture should be tested by running a client that fails and verifying that failure context is included or referenced in the result output.
- There is no prior implementation test suite in the repository; this PRD establishes the first testing structure.
Out of Scope
- Network impairment with
tc, including latency, jitter, loss, duplication, corruption, reordering, and bandwidth limits.
- Impairment Scenario File schema and timeline action vocabulary.
- Per-client independent impairment.
- Live per-client log streaming.
- Automatic Controller Reportback URL detection.
- Preflight reportback reachability checks from a probe container.
- Hawkbit server integration.
- LXD system containers.
- Multi-host Controller support.
- CI integration.
- Device flashing or installation.
- Service discovery via mDNS.
- Concurrent local invocations.
- Backward-compatible Result File schema migrations.
Further Notes
- Some older project notes describe broader v1 scope including per-client impairment and streamed logs. The current glossary and ADR narrow M1 to a baseline result through the Router Container with explicit routing and no
tc operations.
- Future work should revisit the project specs after this PRD is accepted so that mission and tech-stack documents reflect the narrowed M1 and the Router Container tradeoff.
- Before implementing impairment, create a separate design pass for the Scenario File language rather than committing prematurely to action names or timeline syntax.
Problem Statement
Slamcore engineers need a controlled test-farm harness for validating update broadcast reliability, but the project first needs a small, reliable foundation before adding impairment, timeline scenarios, Hawkbit, LXD, or CI. The immediate problem is proving that a developer can invoke the harness locally, provision the intended container topology, move a bundle from an Update Server to multiple clients through a Router Container, receive client-side Verified Receipts through the Controller, and produce a deterministic machine-readable Result File.
The first milestone must deliberately produce a baseline result with no intentional network impairment. This avoids conflating routing, orchestration, receipt collection, and artifact verification with
tcbehavior before the core lifecycle works.Solution
Build the project in two initial milestones.
M0 establishes the Python project skeleton and quality gates:
pyproject-based packaging,uvdependency management, pre-commit hooks, black, isort, mypy, pytest, and a local quality command equivalent to the expected gates.M1 implements a one-shot
test-farm runinvocation that reads a minimal Scenario File containing only client count, allocates the next invocation_instance by scanning existing Result Files, starts the Controller Receipt Channel on an explicit Controller Bind Address, starts separate Router Container, Update Server, and client containers, configures explicit routing through the Router Container, performs a no-impairment bundle download, receives Verified Receipts, derives Client Status and Invocation Status, writes a JSON Result File, captures failure logs after completion, and tears down containers/networks by default unless debug preservation is requested.User Stories
uv, so that I can work on test-farm consistently with the repository's declared tooling.test-farm runwith a Scenario File, so that I can trigger a complete local invocation from one command.GET /health, so that readiness and debugging use a simple common endpoint.tcoperations in M1, so that the first baseline result proves only orchestration, routing, download, verification, reporting, and teardown.Implementation Decisions
uv, Python packaging metadata, pre-commit, black, isort, mypy, and pytest before implementing the baseline result.test-farm runexecution from provisioning through final Result File and teardown.result_<invocation_instance>*, incrementing the highest number, and starting at 1 when no prior Result Files exist.GET /healthandPOST /invocations/{invocation_instance}/clients/{client_id}/receipt.GET /health,GET /bundles/{bundle_id}/manifest, andGET /bundles/{bundle_id}.success,download_failed,checksum_mismatch,receipt_rejected,timed_out, andcontainer_failed.successonly when every expected client has Client Statussuccess; otherwise it isfailed.result_<invocation_instance>.json, and does not include schema_version.tcoperations are performed in M1.tcprivilege mechanics are future design tasks, not M1 design commitments.Potential deep modules to build:
scenario: parses and validates the Scenario File into controller-consumable invocation input.controller: owns invocation lifecycle orchestration from provisioning through Result File and teardown.receipt_model: defines Verified Receipt validation, Client Status mapping, and Invocation Status derivation as pure domain logic.runtime: owns container lifecycle, explicit routing setup, log capture, and teardown.workloads: contains replaceable workload implementations for the toy Update Server and toy client.docker/directory.Testing Decisions
GET /healthand receipt route behavior through HTTP-level requests.Out of Scope
tc, including latency, jitter, loss, duplication, corruption, reordering, and bandwidth limits.Further Notes
tcoperations.