Feature Specification: Effect family effects

Created: 2026-10-07 Status: Implemented Input: Reconstructed from the code of core/render/ and core/effects/ at 14504f80d, PRs #1666, #1790, #1869.

A standalone effect is a program that is not a deploy per node. It does one job beside the deploys: it builds an image, publishes a zone, acts on a registrar or seeds NetBox. This spec covers what all standalone effects share. The program grammar and the verbs are in 004. The methods are in 009. Each effect has its own spec, listed in the Index.

User Scenarios & Testing (mandatory)

User Story 1 - Run a group effect by its generated name (Priority: P1)

An operator runs an effect of a group, with or without a verb, by a name that the catalog generates from the group and the effect. The catalog builds the effect from the same setup evaluation as the deploy of that group, and takes one part out of it. The effect thus acts with the same coordinates as the deploy.

Why this priority: An effect that reads other coordinates than the deploy publishes records or a delegation that do not match the nodes.

Independent Test: Unit tests that each effect reader reads only its declared keys, and a unit test of the key sets of the catalog. An integration test, integrations-effect-run, runs hosting-provider-registrar-domain from the catalog against a fake registrar API and a fake state backend, and asserts that the bound, the apex and the state address of the deploy arrive at the run. No test runs a generated effect against a live target.

Acceptance Scenarios:

  1. Given a group with DNS nodes, When the operator runs the zone effect of that group, Then the program runs the sync verb of the zone part of the tf-incus setup for that group.
  2. Given the zone effect or a registrar effect, When the catalog lists its key set, Then the set is the tf-incus key set plus the keys of the effect reader.

User Story 2 - Enumerate only the effects a group can run (Priority: P1)

The api, the flake listing and the docs site enumerate the effect names of each group. An effect that a group cannot run is not in the list and has no attribute.

Why this priority: An attribute that fails on evaluation takes down the enumeration for every group.

Independent Test: A unit test of the catalog. No test checks that the catalog drops an effect that a group cannot run.

Acceptance Scenarios:

  1. Given a group with no DNS nodes and DNS routing that is not external, When the catalog enumerates its effects, Then octodns-zone is not in the list.
  2. Given no registrar in the environment, When the catalog enumerates the effects of any group, Then no registrar effect is in the list.
  3. Given the dev group, When the catalog enumerates its effects, Then the generated image effect is not in the list, and the hand-bound dev-tf-incus-image answers to that name.

User Story 3 - Run a hand-bound standalone deployment (Priority: P2)

Some effects belong to no group. The catalog binds them by hand and exposes them under bare names: dev-tf-incus-image, dev-tf-incus-bootstrap and netbox-seed.

Why this priority: These effects come before a provider has nodes. The base image and the NetBox seed are inputs of the first deploy.

Independent Test: A unit test that the NetBox seed reads only its declared keys. A VM test runs dev-tf-incus-image from the catalog against its Incus host and checks the image alias before and after the run. integrations-effect-run runs netbox-seed against a fake NetBox. No test applies dev-tf-incus-bootstrap.

Acceptance Scenarios:

  1. Given the package listing, When the operator lists it, Then it has netbox-seed and one variant per OpenTofu verb.
  2. Given dev-tf-incus-bootstrap, When the operator lists its verbs, Then there are none, because it is a shell runner and not an OpenTofu graph.

User Story 4 - Pick an effect in the operator form (Priority: P3)

The form offers a branch for each effect that faces the operator. An effect that declares its effect metadata and deploys no nodes joins the effect settings of the form.

Why this priority: The form must not list effects by hand. A new effect with metadata gets its branch without a change to the front-end.

Independent Test: A unit test of the data model. A unit test that each effect declares metadata or is a building block, and not both. No test checks the content of the effect settings.

Acceptance Scenarios:

  1. Given octodns-zone and the image effect, which declare metadata and deploy no nodes, When the effect registry builds the metadata, Then both are effects in the form.
  2. Given the registrar effects and the NetBox building blocks, which declare no metadata, When the registry builds the metadata, Then they are not in it.
  3. Given an effect that has no metadata and is not a building block, or that has both, When the registry test runs, Then it fails.

Edge Cases

  • The availability of an effect is computed before any setup evaluates. It reads only group data and environment coordinates, so an enumeration forces no setup.
  • The glue effects are keyed by the union of DNS labels across all groups. The availability then narrows each one to the groups that run that label.
  • The image effect has no HTTP state backend on any provider. It keeps a keyed local state, because the image is uploaded before the state backend exists. The delete of the image before each bootstrap upload makes a lost or foreign state harmless (image.md).
  • A registrar step is offered only when the registrar has that step and every coordinate that the step needs is set.
  • The top-level selection by name has dev-tf-incus-image and its verbs. It does not have netbox-seed or dev-tf-incus-bootstrap, which are packages only.

Requirements (mandatory)

Functional Requirements

  • FR-001: The catalog MUST define each generated effect with its availability, its setup and setup arguments, its key set, its projection, its verbs and its descriptions.
  • FR-002: A generated effect MUST be a projection of one setup evaluation. It MUST NOT have its own setup.
  • FR-003: Every consumer of the generated effects MUST go through one enumeration per group, so that the enumerated names and the buildable attributes are the same set.
  • FR-004: The availability of an effect MUST NOT evaluate a setup.
  • FR-005: The catalog MUST name a generated effect by its group and effect, with one name per verb of the effect.
  • FR-006: The catalog MUST describe each hand-bound standalone deployment with its deployment, its run, its effect, its group, its source, its verbs, its key set and its description, and MUST expose it under a bare name.
  • FR-007: A hand-bound standalone deployment that is the instance of a generated effect MUST name that effect and group. The generated effect MUST then not be available for that group.
  • FR-008: Each standalone reader (image, registrar, NetBox seed, Incus bootstrap and zone) MUST declare its coordinates with a description and a default. The readers of the Incus bootstrap and of the zone effect read their keys at run time.
  • FR-009: The standalone key sets MUST NOT join the deploy key sets, and MUST be exposed apart from them.
  • FR-010: Each effect reader MUST read only its declared keys. A run-time reader MUST read exactly its declared keys in its program sources.
  • FR-011: The catalog MUST list, for the docs site, the generated effects of each group and the hand-bound standalone deployments, with their setups, verbs, key sets and descriptions.
  • FR-012: An effect that the form offers MUST declare its internal effect metadata. The registry MUST put it in the effect settings when it deploys no nodes. Each other effect MUST be a building block. An effect MUST NOT be both.
  • FR-013: The source of each hand-bound standalone deployment MUST name a file of the repository outside the tests.
  • FR-014: The name of each package and the description of each app MUST evaluate when the apex is not set, so that the flake listing runs in a pure eval. To build or run an attribute still needs the apex.

Key Entities

  • Generated effect: One catalog entry, instantiated for each group that can run it.
  • Hand-bound standalone deployment: One catalog entry with a bare name and no group of its own.
  • Standalone reader: The declared key set of one standalone effect: image, registrar, NetBox seed, Incus bootstrap or zone.
  • Effect settings: The metadata of the effects that the form offers.
  • Building block: An effect that declares no metadata and that the form does not offer.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: The flake listing and the api enumeration evaluate no setup to list the effects.
  • SC-002: Every environment variable that an effect reader reads is in its declared key set.
  • SC-003: Every effect name that the catalog lists for a group builds.

Assumptions

  • The operator runs an effect after the deploy that it depends on, or before the deploy that depends on it. The catalog does not order effects.
  • The registrar credentials and the NetBox token come from the environment of the caller.
  • core reads the coordinates of the methods and of the standalone effects, the pins, HOSTING_CONFIG, PIN_EFFECT and the environment of a node through its getEnv argument. The default reads the environment. A check gives the coordinates through this argument, because a pure evaluation has no environment.

Known Deviations

  • No test runs dev-tf-incus-bootstrap against a target, and hosting-provider-registrar-domain is the only generated effect that a test runs from the catalog (principle VII, #2129).
  • DEPLOYMENT and the OPERATION_* variables stay on the environment. The effects layer reads them, and getEnv does not reach this layer, because an operator pin can supply it.

Index

  • octodns.md: octodns-zone, which publishes the DNS records of a group to its authoritative service.
  • registrars.md: the registrar effects, which register the apex and delegate it to the nameservers of the group.
  • registrar-providers.md: the capabilities that a registrar must have for the operator path.
  • netbox-seed.md: netbox-seed, which seeds NetBox with the cluster, the cluster type and the tags that the Incus deploy reads.
  • image.md: the image effect and dev-tf-incus-image, which build the base Incus image and upload it.
  • incus-bootstrap.md: dev-tf-incus-bootstrap, which provisions the Incus credentials and the image of a local deployer.