Conductor Test Framework — Design Specification¶
This document defines conductor tests in tests/sweetests/: tests whose
assertions require direct zome calls or substrate inspection on a live
SweetConductor. Standalone async tests can also exercise client/runtime
contracts; they are not conductor-boundary tests merely because they omit the DSL.
For the first style, see the Dance Test Framework.
1. Why Two Styles Exist¶
The Dance Test Framework asserts client-side semantics: staging, token chaining, commit lifecycle, relationship graphs, saved-content equality. It reaches the guest only through the dance envelope, and it observes results as the client observes them.
Some behavior is invisible from there:
- which substrate action a write intent becomes — a
Create, or anUpdateaddressed at a lineage root - whether the Integrity callback actually fired, as opposed to a coordinator rejecting the same input earlier
- the exact consensus-visible rejection message a peer would see
- what is packaged into a DNA, and what a conductor will and will not dispatch
Conductor tests exist to assert exactly those things, at the boundary where they happen.
Routing rule. If the behavior under test is expressible as a sequence of client operations, is suitable for a dance test or a standalone runtime integration test. If the assertion requires direct evidence of Integrity execution or substrate records, use a conductor test.
Do not add a DanceTestStep solely to expose internal substrate evidence; see
Test Step Authoring Guide §7.
2. Shape of a Conductor Test¶
A conductor test is a plain async test function. There is no fixture, no TestReference, no
executor, and no registration in dance_tests.rs.
#[tokio::test(flavor = "multi_thread")]
async fn publish_version_is_rooted_at_the_create_and_round_trips() {
let backend = setup_test_conductor().await;
// ... call zome externs on backend.conductor / backend.cell, assert outcomes
}
Each test constructs its own backend. Backends are never shared or pooled between tests.
The suites currently organized this way:
| Suite | Asserts |
|---|---|
holon_storage_tests.rs |
Version-aware holon node storage: which action each write intent authors, record-derived metadata, positional batch-read behavior, lineage rejection rules |
smartlink_tests.rs |
SmartLink write/expand/delete outcomes that pure comparator unit tests cannot produce |
pvl_validation_tests.rs |
PVL enforcement, with coordinator preflight and Integrity validation asserted as separate paths |
coordinator_surface_tests.rs |
Runtime coordinator boundary: what a production install will and will not dispatch |
3. Conductor Setups¶
Two setups are provided by tests/sweetests/src/harness/helpers/mock_conductor.rs. Both return an
Arc<MockConductorConfig> holding the SweetConductor, the agent key, and the SweetCell.
3.1 setup_test_conductor()¶
Installs the production DNA. Use this default setup for assertions about the unaugmented production surface. Probe-enabled tests separately assert Integrity behavior while proving that augmentation preserves production DNA identity.
Its conductor config points every WAN endpoint — bootstrap, signal, relay — at an unroutable local address. Sweettests run a single in-process conductor and never peer, so their network dependency is accidental rather than designed; leaving an endpoint unassigned silently inherits a public Holochain server and costs a probe timeout on every conductor start.
3.2 setup_probe_enabled_conductor()¶
Installs the production DNA, proves the probe coordinator is unreachable, then appends the probe coordinator to that one conductor and proves production identity is unchanged. See §5.
Isolation rule. A coordinator update mutates the DNA registered within a conductor. A probe-enabled backend must never be pooled with, or reused by, production-only assertions. Each probe-dependent test creates its own.
4. The Test-Probe Coordinator¶
4.1 What it is¶
happ/zomes/coordinator/holons_test_probes is a coordinator zome containing only authoring
seams that the typed production APIs deliberately cannot construct. It is built as a loose WASM
artifact and is absent from every production DNA and hApp manifest by construction.
Probes exist because some Integrity rules reject operations that canonical coordinator APIs make unreachable. Proving through a real conductor that such a rule is wired into peer validation requires a way to author the rejected operation.
The rejection is the assertion. A negative-purpose probe whose call succeeds has failed its purpose.
4.2 Rules for probes¶
- Narrowest possible ingress. Each probe is fixed to the single Integrity rule its test needs to reach. No generic raw-entry ingress, no generic link ingress. A probe that resolves a target must refuse every action and link type other than the one under test.
- No dependency on the production coordinator crate. HDK extern symbols survive WASM linking from an rlib dependency. Depending on the production coordinator crate would reproduce its entire ingress surface inside the test artifact.
- Never called by production. The crate is the complete, grep-able inventory of unsupported authoring.
- Pair a rejection with an acceptance where the rule is topological. When a probe proves that an operation is rejected, a companion test should author the same seam in the accepted shape, so the rejection is demonstrably about the rule and not about the probe.
4.3 Artifact boundary enforcement¶
The boundary is enforced mechanically, not by convention:
happ/coordinator-surface.tomlis a source-controlled inventory of every export of both artifacts, including toolchain-generated symbols and callbacks. Each row carries anexposure_kind, aclassification, and a rationale. Exact accounting is the point — it prevents a generated symbol from being mistaken for application ingress.tools/happ-artifact-auditcompares the manifest against the WASM inside the bundles being shipped, in both directions, so an unclassified new export and a stale row for a removed one both fail.--deny-production-test-onlyfails the audit if any export classifiedtest_onlyappears in a packaged DNA or hApp.
Production application functions belong under a packaged zome classified supported or
legacy_ingress. Test-only functions belong only under the probe zome and are prohibited from
production artifacts.
5. Isolated Augmentation¶
setup_probe_enabled_conductor() performs the following sequence. Steps 2 and 7
are assertions inside the helper.
- Install the production DNA from the packed bundle.
- Prove the probe is unreachable — dispatch must fail with
ZomeError::ZomeNotFoundas the precise inner cause, not merely with some error. - Snapshot the pre-update DNA — coordinator name set, production coordinator WASM hash, Integrity zome definitions.
- Read the loose probe WASM by path, not inferred from a Cargo target directory. Build ordering (§6) is responsible for having produced that exact artifact.
- Append, never replace. The coordinator update is handed only the new zome name. Including a second definition under the production name would swap out the active production coordinator instead of augmenting the conductor. The probe definition declares its dependency on the production Integrity zome.
- Restart the conductor. Holochain 0.6.3 mutates the cached
DnaFileduring a coordinator update but does not rebuild the ribosome's derived zome-dependency index. Shutdown followed by startup reloads through the updated DNA cache; this is also the upstream coordinator-update test's persistence path. - Assert five invariants — the coordinator set is exactly production plus probe; the production WASM hash is unchanged; the probe entry references the freshly read WASM; Integrity definitions are unchanged; and rehashing the effective updated DNA definition still yields the installed DNA hash.
The surface integration test separately calls assert_probe_zome_dispatchable
after setup. The helper does not itself make a production extern call; preserved
WASM identity is not a substitute for an executed production behavior assertion.
Static artifact composition is not established here — that is the audit's job. These runtime checks pin the Holochain behavior that static bundle inspection cannot establish.
6. Build Ordering¶
Because §5 reads a loose artifact from a fixed path, the sweettest entrypoint must produce it, and the artifact boundary is re-audited before any test runs:
build:happ → build:probes → check:happ-artifacts → sweet:test
npm run sweetest performs this sequence. The probe build is a separate script from the production
zome build; only the production coordinator is ever packed into the DNA or hApp. The audit also runs
as its own CI gate, so the surface policy stays visible rather than buried inside a test run.
7. Asserting Rejections¶
Holochain buries a rejection several error layers deep, and the layers differ depending on where
the rejection happened. Three helpers in
tests/sweetests/src/harness/helpers/pvl_validation.rs encapsulate that plumbing, and their
separation is itself an assertion about which path fired.
| Helper | Recognizes | Use for |
|---|---|---|
assert_commit_rejected_with_pvl |
SourceChainError::InvalidCommit |
Integrity rejections carrying a MAP-PVL- code. Refuses a message without that prefix. |
assert_commit_rejected_with_message |
Same path, any exact message | Fixed-policy Integrity rejections that carry no PVL code |
assert_preflight_rejected_with_pvl |
WasmRuntimeError → WasmErrorInner::Guest |
Coordinator preflight rejections produced before a host write |
Both commit helpers strip Holochain's "Validation failed while committing: " prefix and compare
the remainder for exact equality. Consensus-visible messages are pinned, never matched by substring.
Keep the two enforcement paths distinct. Coordinator tests prove typed preflight behavior; narrowly scoped probes separately prove Integrity enforcement. Asserting them with one helper would let a coordinator guest error be mistaken for evidence of consensus validation coverage.
8. Conventions¶
- Read the substrate directly when asserting about the substrate. A test that claims a
particular action was authored must read the raw
Record, not the feature's own decoding — otherwise it validates the decoder against itself. - Construct identifiers through their real types. A well-formed-but-unpersisted id should be
built through
ActionHashso its multihash prefix is correct by construction; hand-written bytes fail conversion and test the wrong thing. - Publish real endpoints. Integrity rules that require action-hash identity cannot be exercised with synthetic ids; commit real holon nodes through the canonical ingress first.
- Prefer the public ingress when the rule is reachable from it. A probe is warranted only when the operation cannot be constructed through a production API.
9. Authority¶
This document owns conductor-test isolation and evidence requirements. The packaged coordinator policy remains owned by the Coordinator Surface Policy. Concrete helper names and build ordering describe the repository harness; changes to those helpers must preserve the stated isolation guarantees.