Documentation
¶
Overview ¶
Package stateless implements OpenTofu's stateless mode: a run mode with no authoritative state file, no backend, and no lock. Identity is recovered from the live system on every operation instead of being read from a stored record.
Projections ¶
The state data structure does not disappear; its authority does. A projection is an ephemeral prior-state, built at the start of an operation by reading the live system and discarded once the operation completes. It is fed to the unchanged plan engine in place of a state read and is never persisted. Same shape as a states.State, none of the authority: nothing downstream trusts a projection to be correct, because nothing depends on it surviving past the run that built it.
Contrast with authoritative state, whose test is that a wrong record makes OpenTofu do the wrong thing to the world. A wrong projection costs at most a re-plan.
Admission ¶
A resource type participates in stateless mode only if its identity is recoverable with no memory. Four paths admit a resource, strongest first:
- Client-assigned identity: the name is already in the configuration (a bucket name, a role name, a log group name). Nothing to recover.
- Marker: the resource carries ownership tags, defined in live/MARKERS.md, and is found by a tag-filtered list call.
- Parent-derived: identity is a composite key built from already-admitted parents, such as a route keyed by route table and destination, or an association keyed by subnet and route table.
A resource type with none of these three paths is out of the stateless subset and is rejected by lint before a projection is ever built.
A fourth exists, narrowly: "list and content match" (issue #272, discovery/contentmatch.go). It applies only to the handful of types in identity.ContentMatchTypes - types with no tags argument at all, admitted on a two-source uniqueness proof - and it binds a SINGLE listed candidate whose content matches the declared instance, refusing ambiguity. For every other type the old statement still holds: the Cloud Control leg lists an object, refines it with GetResource, and DISCARDS it when neither carried tags (cloudcontrol.go's ProblemNoTags, an error), and the foreign-resources paragraph below - a content match "surfaced for explicit adoption and never bound automatically" - describes that general case, not the ContentMatchTypes carve-out. (An earlier revision of this comment denied the path outright while its own subpackage implemented it; the 2026-09-02 architecture review caught the contradiction.)
Enumerability is still a real and separately useful fact about a type: it is what a sweep needs to notice an undeclared object at all. It is not an admission path, because admission is about naming the object this configuration means, and a listing that cannot read a marker names nothing. tools/survey-gen records the enumeration fact under its own label for that reason.
Foreign resources ¶
A live resource of an in-scope type with no admission path binding it to configuration is foreign: unrecognized, unmanaged, and never auto-deleted. Stateless mode reports it for review rather than guessing whether it should be adopted or destroyed. A foreign resource that happens to match a declared-but-unbound resource is a bind candidate, surfaced for explicit adoption and never bound automatically.
Count ¶
count survives stateless mode as cardinality over a fungible set rather than as a positional index. Each instance of such a set carries a tofu-slot marker: a stable, opaque identifier assigned once at creation and never reused. Binding N declared instances against M live, owned instances is set matching, not index matching: a deficit creates new slots, a surplus deletes the highest slots.
A count block is a fungible set only when the configuration does not itself say which live resource is which instance. lint admits count.index in an identity-bearing argument where it can prove each instance renders a distinct, scale-down-stable value (see internal/live/lint's analyzeCountIndexSafety), and a block that names its members that way resolves identity.ClassConcrete per instance: discovery never indexes it as a count set, no slot is minted, and its instances carry tofu-estate and tofu-address alone. See live/MARKERS.md, "Which count instances carry one" - and GitHub issue #969, which is what reading the old wording here and in the spec ("each instance carries a tofu-slot marker") led an operator to expect. The count = var.enabled ? 1 : 0 idiom keeps working unchanged, and is one of the shapes that carries no slot.
Where the phases land ¶
The projection is built and consumed at three seams in the existing codebase, not in a parallel state system:
- internal/states/statemgr: Filesystem is the model for a projection manager. A projection manager's Refresh/State builds its snapshot from live reads instead of a file, and Persist writes nothing (or, when a record_store is configured, guided discovery's plan-cost hint - a type roster and a timestamp that no code path reads back as truth; issue #109).
- Import machinery in internal/tofu: resource identity is merged upstream (opentofu#2854); the same import-by-identity path that backs tofu import is how a projection materializes a states.State for the client-named and parent-derived admission paths.
- The provider list protocol: the mechanism behind admission path 4 and behind tag-filtered marker discovery. Core-side client support tracks the shape of opentofu#3787 to stay mergeable with upstream.
This package holds the model. Concrete implementations live in its subpackages: lint, identity, discovery, stamp, projection, and the rest. The operator-facing specs are in live/.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package absent answers one question for any caller that has just asked a provider to import a resource and been handed an error: is the provider saying "there is no such object", or failing to answer at all.
|
Package absent answers one question for any caller that has just asked a provider to import a resource and been handed an error: is the provider saying "there is no such object", or failing to answer at all. |
|
Package acceptance is GitHub issue #108's tier: every cohort estate is applied against the floci emulator, its state file is deleted, and the plan rebuilt from ownership markers alone is asserted empty - the definition of done a user can check for themselves, run per cohort as a measurement.
|
Package acceptance is GitHub issue #108's tier: every cohort estate is applied against the floci emulator, its state file is deleted, and the plan rebuilt from ownership markers alone is asserted empty - the definition of done a user can check for themselves, run per cohort as a measurement. |
|
Package approval is the comparison behind GitHub issue #878's approval gate: does the plan an apply is about to execute say the same thing as the plan a human approved?
|
Package approval is the comparison behind GitHub issue #878's approval gate: does the plan an apply is about to execute say the same thing as the plan a human approved? |
|
Package check answers "would this configuration move under live resource markers, and if not, what stops it" using only the configuration: no backend, no state, no cloud reads, and no provider process beyond reading schemas.
|
Package check answers "would this configuration move under live resource markers, and if not, what stops it" using only the configuration: no backend, no state, no cloud reads, and no provider process beyond reading schemas. |
|
Package cloudcontrol is a client for AWS's Cloud Control API: the transport the registry-backed discovery plane (#40) reads live resources through.
|
Package cloudcontrol is a client for AWS's Cloud Control API: the transport the registry-backed discovery plane (#40) reads live resources through. |
|
Package cohorts is the roster of the per-cohort verification estates tools/estate-gen renders - what used to be the 32 committed directories under live/e2e/estates (issue #699).
|
Package cohorts is the roster of the per-cohort verification estates tools/estate-gen renders - what used to be the 32 committed directories under live/e2e/estates (issue #699). |
|
Package dataread is issue #179's pre-resolution data-read phase: provider data sources whose values identity resolution needs, read before resolution runs instead of refused as non-static.
|
Package dataread is issue #179's pre-resolution data-read phase: provider data sources whose values identity resolution needs, read before resolution runs instead of refused as non-static. |
|
The parent-scoped Cloud Control leg.
|
The parent-scoped Cloud Control leg. |
|
Package docsref parses and resolves the documentation references the live path's refusals carry.
|
Package docsref parses and resolves the documentation references the live path's refusals carry. |
|
Package flocitest holds the gate, the fixture paths and the container bookkeeping that the floci integration tests share.
|
Package flocitest holds the gate, the fixture paths and the container bookkeeping that the floci integration tests share. |
|
Package foreign classifies the live resources an estate does not own, and is the safety property of stateless mode: a live resource nobody claims is surfaced, and is never a deletion candidate.
|
Package foreign classifies the live resources an estate does not own, and is the safety property of stateless mode: a live resource nobody claims is surfaced, and is never a deletion candidate. |
|
Package harness holds the two registries this repository's numbers live in: what it is driving down (the burndown), and what it believes while it does (the assumptions).
|
Package harness holds the two registries this repository's numbers live in: what it is driving down (the burndown), and what it believes while it does (the assumptions). |
|
Package identity classifies the identity of every managed resource instance in a configuration, using nothing but the configuration itself.
|
Package identity classifies the identity of every managed resource instance in a configuration, using nothing but the configuration itself. |
|
Package kubesweep is the Kubernetes estate sweep (GitHub issue #1065, under #1016's ruling): which live objects carry this estate's label, so that one whose block the configuration no longer declares can be proposed for removal, exactly as the AWS tagging sweep does for a tagged resource.
|
Package kubesweep is the Kubernetes estate sweep (GitHub issue #1065, under #1016's ruling): which live objects carry this estate's label, so that one whose block the configuration no longer declares can be proposed for removal, exactly as the AWS tagging sweep does for a tagged resource. |
|
Package lifecycle holds five integration tests and nothing else: the lifecycle test itself (P4.1), the guided-discovery hint test (issue #109), the exactness test (P5.1), the crash-mid-apply test (the concurrency taxonomy's crash row, run rather than argued), and the existence-flavor receipt test (RA.6).
|
Package lifecycle holds five integration tests and nothing else: the lifecycle test itself (P4.1), the guided-discovery hint test (issue #109), the exactness test (P5.1), the crash-mid-apply test (the concurrency taxonomy's crash row, run rather than argued), and the existence-flavor receipt test (RA.6). |
|
Package lint is the stateless-mode subset check: the pass that decides whether a configuration can be planned with no authoritative state at all.
|
Package lint is the stateless-mode subset check: the pass that decides whether a configuration can be planned with no authoritative state at all. |
|
Package listclient is stateless mode's client for the provider list protocol: the ListResource server-streaming RPC and the list resource schemas that parameterize it.
|
Package listclient is stateless mode's client for the provider list protocol: the ListResource server-streaming RPC and the list resource schemas that parameterize it. |
|
Package liveimport is the bulk migration path from a state-backed estate to ownership markers (issue #61): read an existing tfstate file once, verify every resource it names against the live system, and - only when the operator says so a second time - stamp this estate's markers onto everything that verified.
|
Package liveimport is the bulk migration path from a state-backed estate to ownership markers (issue #61): read an existing tfstate file once, verify every resource it names against the live system, and - only when the operator says so a second time - stamp this estate's markers onto everything that verified. |
|
Package markerkey holds the for_each instance key rule that lint and identity both enforce.
|
Package markerkey holds the for_each instance key rule that lint and identity both enforce. |
|
Package markers is live/MARKERS.md in code: the ownership tag keys, the escaping rule that lets a resource address live in a tag value, and the reading of those tags off a live object.
|
Package markers is live/MARKERS.md in code: the ownership tag keys, the escaping rule that lets a resource address live in a tag value, and the reading of those tags off a live object. |
|
markerstest
Package markerstest holds the two resource schemas every package that decides "may a marker be written here" has to agree about, so that the packages asserting it do not each rebuild the block and thereby rebuild the disagreement the assertion exists to catch.
|
Package markerstest holds the two resource schemas every package that decides "may a marker be written here" has to agree about, so that the packages asserting it do not each rebuild the block and thereby rebuild the disagreement the assertion exists to catch. |
|
Package markerstrip answers one question about a plan built from a state file: would applying it remove this fork's ownership markers from live resources that carry them?
|
Package markerstrip answers one question about a plan built from a state file: would applying it remove this fork's ownership markers from live resources that carry them? |
|
Package marksafe is the lockstep check behind GitHub issue #240: a cty.Value accessor that panics on a marked value cannot be called in a live package on a value nothing proves is unmarked.
|
Package marksafe is the lockstep check behind GitHub issue #240: a cty.Value accessor that panics on a marked value cannot be called in a live package on a value nothing proves is unmarked. |
|
Package mdspan rewrites named regions of a committed markdown document in place.
|
Package mdspan rewrites named regions of a committed markdown document in place. |
|
Package moved carries a configuration's `moved` blocks into the live-marker world, where there is no state entry to rewrite.
|
Package moved carries a configuration's `moved` blocks into the live-marker world, where there is no state entry to rewrite. |
|
Package mv performs the rename operation stateless mode has instead of `moved` blocks and state surgery: it rewrites the tofu-address ownership marker on one live resource.
|
Package mv performs the rename operation stateless mode has instead of `moved` blocks and state surgery: it rewrites the tofu-address ownership marker on one live resource. |
|
Package noimporter answers one question, generically, for any caller that has just asked a provider to import a resource type and been told it cannot: is this a provider erroring, or the provider correctly answering that ImportResourceState is not implemented for this type at all - and if it is the latter, and this run already has a resolved identity for the instance, what stub would ImportResourceState itself have returned had it existed.
|
Package noimporter answers one question, generically, for any caller that has just asked a provider to import a resource type and been told it cannot: is this a provider erroring, or the provider correctly answering that ImportResourceState is not implemented for this type at all - and if it is the latter, and this run already has a resolved identity for the instance, what stub would ImportResourceState itself have returned had it existed. |
|
Package onboard computes the source edit that turns a state-backed module into a live one, from the module's own text and from nothing else.
|
Package onboard computes the source edit that turns a state-backed module into a live one, from the module's own text and from nothing else. |
|
Package passthrough is the registry of refusals the live path shows a user without having written them.
|
Package passthrough is the registry of refusals the live path shows a user without having written them. |
|
Package pins holds the provider release the measurement instruments pin, as one constant instead of one per tool.
|
Package pins holds the provider release the measurement instruments pin, as one constant instead of one per tool. |
|
Package plugincache answers one question: does a terraform/tofu plugin cache directory already hold an unpacked provider release for this platform, so that an init can install from it with -plugin-dir and never ask a registry anything.
|
Package plugincache answers one question: does a terraform/tofu plugin cache directory already hold an unpacked provider release for this platform, so that an init can install from it with -plugin-dir and never ask a registry anything. |
|
Package pluginschema reads a provider's schemas by launching its plugin in-process, with no cloud calls and no provider configuration.
|
Package pluginschema reads a provider's schemas by launching its plugin in-process, with no cloud calls and no provider configuration. |
|
Package policy is GitHub issue #67's ownership-policy matrix, given a settled shape: a verb per quadrant (declared-in-source x carries-the-tag), the tag those quadrants read, and the delete quadrant's safety rails.
|
Package policy is GitHub issue #67's ownership-policy matrix, given a settled shape: a verb per quadrant (declared-in-source x carries-the-tag), the tag those quadrants read, and the delete quadrant's safety rails. |
|
Package projection materializes an ephemeral prior state by reading the live system.
|
Package projection materializes an ephemeral prior state by reading the live system. |
|
Package providerscope resolves a resource's true root-level provider configuration address by walking every ancestor module call's `providers = { ...
|
Package providerscope resolves a resource's true root-level provider configuration address by walking every ancestor module call's `providers = { ... |
|
Package providerversion answers issue #63: a configuration's provider pin and the admission table's evidence basis are two independent facts that can drift apart with nobody told.
|
Package providerversion answers issue #63: a configuration's provider pin and the admission table's evidence basis are two independent facts that can drift apart with nobody told. |
|
Package recordtrips holds one measurement: how many times a migrated estate's plan goes to the record store, and where from.
|
Package recordtrips holds one measurement: how many times a migrated estate's plan goes to the record store, and where from. |
|
Package refusalscan is the lockstep check behind GitHub issue #110's first acceptance criterion: a refusal cannot exist in a live package without an entry in that package's registry.
|
Package refusalscan is the lockstep check behind GitHub issue #110's first acceptance criterion: a refusal cannot exist in a live package without an entry in that package's registry. |
|
Package registry loads the two committed join artifacts - live/mapping.json (issue #43, the TF-to-CFN type join) and live/registry.json (issue #42, the CloudFormation Registry's per-type roster) - and answers the one question discovery's Cloud Control fallback (#47) needs of them: given a TF resource type with no native provider list resource, is there a mapped CFN type that Cloud Control can enumerate on its own, with no per-call parsing of either file?
|
Package registry loads the two committed join artifacts - live/mapping.json (issue #43, the TF-to-CFN type join) and live/registry.json (issue #42, the CloudFormation Registry's per-type roster) - and answers the one question discovery's Cloud Control fallback (#47) needs of them: given a TF resource type with no native provider list resource, is there a mapped CFN type that Cloud Control can enumerate on its own, with no per-call parsing of either file? |
|
Package retry is the vocabulary behind a live block's "retry" block: which mode spellings mean anything, what bounds an attempt count has, and how the resolved settings reach the aws-sdk-go-v2 clients every live command builds.
|
Package retry is the vocabulary behind a live block's "retry" block: which mode spellings mean anything, what bounds an attempt count has, and how the resolved settings reach the aws-sdk-go-v2 clients every live command builds. |
|
Package servicetags is GitHub issue #1131: the per-service tag-read leg, for a live object that an enumeration route reaches and no tag route can read a marker off.
|
Package servicetags is GitHub issue #1131: the per-service tag-read leg, for a live object that an enumeration route reaches and no tag route can read a marker off. |
|
Package slots is the set matcher for count instances: the rule that turns "N declared instances and M live resources" into a binding, without any index participating in identity.
|
Package slots is the set matcher for count instances: the rule that turns "N declared instances and M live resources" into a binding, without any index participating in identity. |
|
Package stamp is the marker-stamping LAYER's refusal registry and the one sentence that layer says to an operator.
|
Package stamp is the marker-stamping LAYER's refusal registry and the one sentence that layer says to an operator. |
|
Package statefulcost holds one measurement and nothing else: what a plan costs when this fork's binary runs a configuration that has NO live block.
|
Package statefulcost holds one measurement and nothing else: what a plan costs when this fork's binary runs a configuration that has NO live block. |
|
Package staterecord is a small, versioned key/value Store with first-class conditional writes: Get, PutIfVersion, PutIfAbsent, Delete, List — nothing else.
|
Package staterecord is a small, versioned key/value Store with first-class conditional writes: Get, PutIfVersion, PutIfAbsent, Delete, List — nothing else. |
|
Package staticeval is the config-subset evaluator the live path shares: the one place that says what "statically evaluable" means, the one place that evaluates it, and the one place the count/for_each key derivation and the read-one-argument-off-a-resource-body walk live.
|
Package staticeval is the config-subset evaluator the live path shares: the one place that says what "statically evaluable" means, the one place that evaluates it, and the one place the count/for_each key derivation and the read-one-argument-off-a-resource-body walk live. |
|
Package strict holds the vocabulary of the live block's "strict" block: HANDOFF.md's principles, each expressed as a toggle with a default that is today's behavior.
|
Package strict holds the vocabulary of the live block's "strict" block: HANDOFF.md's principles, each expressed as a toggle with a default that is today's behavior. |
|
Package uniquename reads one claim out of a piece of API documentation: that the name the CLIENT supplies for a resource is unique within the account and region the run is pointed at.
|
Package uniquename reads one claim out of a piece of API documentation: that the name the CLIENT supplies for a resource is unique within the account and region the run is pointed at. |
|
Package untag is GitHub issue #67's apply-time half of the undeclared_tagged = "untag" verb: releasing one tag key from a live resource this estate owns but no configuration declares.
|
Package untag is GitHub issue #67's apply-time half of the undeclared_tagged = "untag" verb: releasing one tag key from a live resource this estate owns but no configuration declares. |