Help us shape the future of Go! Take the 2026 Go Developer Survey

strict

package
v0.32.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 29, 2026 License: MPL-2.0 Imports: 5 Imported by: 0

Documentation

Overview

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.

It is deliberately the same shape internal/live/policy has for the ownership matrix - a verb type, the valid set, a default, and no dependency on internal/configs. The decoder (internal/configs/live.go) records the literal string an author wrote; this package says which strings mean something; internal/live/lint refuses the ones that do not. Keeping the three apart is what lets the decoder stay a decoder.

What is implemented, and what is not

GitHub issue #365 is the schema for four toggles. This package carries marker_repair (grammar plus the two predicates that say which settings a build acts on - see Implemented), the `markers "record"` selection, and secrets (see Secrets).

The consolidated schema and the environment pin

Toggles is the single declared inventory of every toggle above: name, default, the refusal its non-default setting relaxes or tightens, and a doc line - registry.go, added by #365's consolidation slice so that inventory lives in one place instead of being reconstructed by reading three types and internal/configs/live.go's decode table side by side. EnvPin and Pinned are that same slice's other half: an environment variable that forces a Toggle.Pinnable toggle to its Toggle.SafeValue from outside the configuration, so a platform team can require behavior a configuration author cannot switch off in the same commit that would relax it (pin.go).

Index

Constants

View Source
const DefaultMarkerRepair = Repair

DefaultMarkerRepair is what an omitted marker_repair argument means, and therefore what every configuration written before the strict block existed keeps getting. "Compatible out of the box" is this constant, in HANDOFF's words: "markers are written on every taggable resource; marker repair is on."

View Source
const DefaultNoSourceCreate = NoSourceRefuse

DefaultNoSourceCreate is what an omitted no_source_create argument means, and therefore what every configuration written before this toggle existed keeps getting: NoSourceRefuse, today's behavior.

View Source
const DefaultProviderChange = ProviderChangeRefuse

DefaultProviderChange is what an omitted provider_change argument means: ProviderChangeRefuse.

Unlike this schema's other three, it is NOT what every configuration written before the toggle existed used to get - that was to proceed silently, which is the defect GitHub issue #906 records. "Compatible out of the box" is about configurations stock OpenTofu runs, and this default refuses a configuration stock does run; the trade is deliberate and the toggle is the escape hatch (maintainer ruling, 2026-09-06, on PR #914).

View Source
const DefaultSecrets = Store

DefaultSecrets is what an omitted secrets argument means, and therefore what every configuration written before this toggle existed now gets.

It is Store rather than Refuse, and that is a deliberate reversal of what this fork did up to GitHub issue #365 slice 3, not an accident of ordering. The old default refused, which made a configuration containing one random_password unrunnable here and runnable on stock - HANDOFF.md's first difference row ("choudoufu refuses where stock proceeds: a defect; fix it"). The principle did not change; it moved from being the default to being the toggle, which is what "the principles this fork exists for are toggles, and turning them on is the setup step" says.

View Source
const EnvPin = "CHOUDOUFU_STRICT_PIN"

EnvPin is the environment variable that pins this fork's strict profile from OUTSIDE the configuration.

The design note attached to GitHub issue #365's ruling 4 and 5 comment (the foundation-order ruling (#388)), itself following opentofu/opentofu#3016's configuration-tier idea, is the reason this exists at all: a `strict` block lives in the same commit as the resources it governs, so an author who wants to relax it can always do so in the same change that needs relaxing - the toggle protects nothing a platform team cannot be overruled on. EnvPin is read from the process that RUNS a plan or apply, never from configuration, so a commit relaxing a pinned toggle cannot approve its own relaxation; only whoever controls the environment can.

CHOUDOUFU_ rather than TOFU_: internal/command/live_mode.go's CHOUDOUFU_NODE_RESOLVE and internal/live/identity/located_test.go's CHOUDOUFU_LIVE_SCHEMAS are this fork's own switches, and this is another one - a governance lever with no stock equivalent - while the TOFU_ prefix (internal/command/live_plan.go's cloudControlEnvVar, guidedDiscoveryDisableEnvVar) is reserved for levers over behavior stock OpenTofu's own environment surface already has an opinion about.

Variables

View Source
var Toggles = []Toggle{
	{
		Name:    "marker_repair",
		Default: string(DefaultMarkerRepair),
		Relaxes: `"never" gives up automatic repair of a drifted ownership marker on the resources a paired ` +
			`markers "record" selection covers, trading marker-based governability for tolerance of an estate ` +
			`where something else owns the tags. "report" is grammar this fork's decoder still parses and Valid ` +
			`still recognizes, but no build implements it, and unlike "never" it has no markers "record" ` +
			`selection - or any other mechanism - that would make it usable (see [Implemented] and ` +
			`[ImplementedWithSelection]); it is refused unconditionally and is not counted among this toggle's ` +
			`declared Values below.`,
		Doc:      `live/LIMITATIONS.md, "strict-marker-repair"`,
		Pinnable: false,
		Values:   []string{string(Repair), string(Never)},
		Meaning: `What a run does about an ownership marker on a live object that disagrees with the marker ` +
			`this configuration declares. "repair" writes the declared value over it, as the plan's ordinary ` +
			`in-place tags update. "never" leaves it silently, for an estate where something else owns the ` +
			`tags, and only once a markers "record" selection gives the resource an identity source that is ` +
			`not the marker.`,
	},
	{
		Name:    "secrets",
		Default: string(DefaultSecrets),
		Relaxes: `"refuse" tightens the compatible-by-default answer (store secret material the way stock's ` +
			`state file does) into HANDOFF.md's first principle: a secret-generating logical type is refused ` +
			`outright and a sensitive settable argument is never recorded. "ssm" keeps the same material ` +
			`"store" keeps and relaxes that principle too - it moves the values to Parameter Store under a ` +
			`customer managed key rather than keeping none - but no build implements the write path yet ` +
			`(GitHub issue #1515), so it is refused unconditionally and is not counted among this toggle's ` +
			`declared Values below, for the reason [MarkerRepair]'s "report" is not. Everything AROUND it is ` +
			`built: the nested ssm block decodes, an arrangement that could not work is refused by name, and ` +
			`the state cache is already off under the setting.`,
		Doc:       `live/LIMITATIONS.md, "strict-secrets"`,
		Pinnable:  true,
		SafeValue: string(Refuse),
		Values:    []string{string(Store), string(Refuse)},
		Meaning: `What a run does with the secret material a configuration generates or sets. "store" keeps ` +
			`it the way stock OpenTofu keeps it. "refuse" is two refusals: a secret-generating type is ` +
			`refused outright, and a sensitive settable argument is left out of its record. It does not ` +
			`reach the cache file, or a terraform_data or null_resource the configuration hands a secret.`,
	},
	{
		Name:    "no_source_create",
		Default: string(DefaultNoSourceCreate),
		Relaxes: `"create" relaxes the default refusal of a no-record, no-marker, no-derivable-identity instance ` +
			`into stock OpenTofu's own behavior for a resource with no prior state: plan a create, and accept the ` +
			`risk that a genuinely new instance and a real one this run cannot see yet are indistinguishable here.`,
		Doc:       `live/LIMITATIONS.md, "strict-no-source-create"`,
		Pinnable:  true,
		SafeValue: string(NoSourceRefuse),
		Values:    []string{string(NoSourceRefuse), string(NoSourceCreateOn)},
		Meaning: `What a run does with an instance that has no record, no live marker and no identity ` +
			`anything can derive from configuration. "refuse" reports it, by name, and names both remedies: ` +
			`"choudoufu live-import" from a stock state that already holds it, or this toggle. "create" ` +
			`selects stock OpenTofu's own behavior for a resource with no prior state: plan a create.`,
	},
	{
		Name:    "provider_change",
		Default: string(DefaultProviderChange),
		Relaxes: `"recreate" relaxes the default refusal of a resource block that has moved to a different ` +
			`provider configuration while a live object in the one it left still carries this estate's marker ` +
			`for its address, into stock OpenTofu's own behavior: plan the create under the new configuration ` +
			`and leave the old one's object where it is. What is given up is that object - it stays live, keeps ` +
			`this estate's tofu-estate and tofu-address tags, and no plan will ever propose anything for it, ` +
			`because the sweep looks where a provider configuration points and none points there for that ` +
			`address any more. The run says so out loud, by name, every time.`,
		Doc:       `live/LIMITATIONS.md, "strict-provider-change"`,
		Pinnable:  true,
		SafeValue: string(ProviderChangeRefuse),
		Values:    []string{string(ProviderChangeRefuse), string(ProviderChangeRecreate)},
		Meaning: `What a run does when a resource block names a different provider configuration than the one ` +
			`whose account or region still holds a live object carrying this estate's marker for that block's ` +
			`address - a region or account change. "refuse" reports the object, by name, with the provider ` +
			`configuration that found it and the one its address now belongs to, and names both remedies: ` +
			`destroying or disowning that object, or this toggle. "recreate" selects stock OpenTofu's own ` +
			`behavior - plan the create under the new configuration - and warns, by name, that the old one's ` +
			`object is abandoned and nothing will find it again.`,
	},
}

Toggles is the whole schema: every toggle a `strict` block accepts today, in the order internal/configs/live.go's decodeStrictBlock reads its arguments. GitHub issue #365's `markers "record"` selection is not an entry here - it is a list of types and addresses, not a setting with a default and an opposite, so "the refusal it relaxes or tightens" is not a single sentence the way it is for the other three (see [LiveStrictMarkers] in internal/configs/live.go, and live/LIMITATIONS.md's "strict-markers" / "strict-markers-unrecordable" for what it does refuse).

Functions

func CreatesFromNoSource

func CreatesFromNoSource(v NoSourceCreate) bool

CreatesFromNoSource reports whether v is the setting under which a no-source instance is planned as a create instead of refused.

A function over the type rather than a `v == NoSourceCreateOn` at each call site for StoresSecrets's own reason: the zero value, NoSourceCreate(""), answers false here while DefaultNoSourceCreate answers true, so a layer holding no configuration never concludes the operator asked to relax the refusal. Every layer that CAN read the configuration resolves an omitted argument to DefaultNoSourceCreate first.

func Implemented

func Implemented(v MarkerRepair) bool

Implemented reports whether a build acts on v.

Today only Repair is implemented, and the gap is deliberate rather than an oversight, because of where marker repair actually happens. Nothing in this fork writes a marker tag onto a live object directly: internal/live/ stamp rewrites the CONFIGURATION, injecting the markers into a resource's tags argument, and the repair of a drifted live tag is then emergent - the provider's ordinary tags diff between what the configuration now declares and what the object carries. stamp itself never overwrites a marker (see its verify: "It never returns 'overwrite': there is no such verdict"), so there is no repair path inside it for a toggle to gate.

Suppressing the repair therefore means suppressing that diff for the marker keys, which is what lifecycle { ignore_changes } does and what internal/live/lint's RuleIgnoreChanges refuses today. Lifting that refusal safely needs somewhere else for the identity to live, because a needs-discovery resource whose marker is neither written nor repaired is the "created unfindable" failure the safety rule exists to prevent - which is the per-type and per-address `markers = record` toggle, the next slice of #365.

So the honest state is: the grammar is here, the unconditional mechanism is not, and a setting whose mechanism does not exist is refused loudly rather than accepted silently. A silent no-op would tell an operator their estate's tags are not being touched while the ordinary plan carried on touching them, which is precisely the false "you are fine" this fork keeps finding in itself.

Never is where the paragraph above stopped being the whole story, and it is worth being exact about why, because "the bar moved" and "the mechanism arrived" look the same from a distance. Nothing changed about repair. What arrived is the OTHER identity source that paragraph names as the precondition: the per-type and per-address markers = record selection. A resource holding its identity in the estate's record store has no marker to repair and no marker to lose, so ignoring its tags costs nothing - and ignoring its tags is the entire observable content of "never". See ImplementedWithSelection, which is the predicate that says so.

func ImplementedNames

func ImplementedNames() string

ImplementedNames renders just the settings a build acts on unconditionally, in the same shape Names uses.

func ImplementedWithSelection

func ImplementedWithSelection(v MarkerRepair) bool

ImplementedWithSelection reports whether a build acts on v for a configuration whose strict block carries a non-empty markers "record" selection.

It is a superset of Implemented and differs on exactly one setting, Never. See that constant for why the selection is what gives it a mechanism.

The half it does not cover, and why that is not a silent no-op

A selection covers some resources and not others, so "never" honoured through it reaches some resources and not others - which would be the misleading half-truth Implemented exists to refuse, if an operator could only find the boundary by reading this comment. They cannot miss it: "never"'s whole effect is that internal/live/lint stops refusing lifecycle { ignore_changes } over the marker tags, and it stops refusing it per resource, only for the ones the selection covers. An estate-wide "never" therefore meets its limit at the first resource the selection does not reach, as a refusal naming that resource, on the first run.

func Names

func Names() string

Names renders the whole vocabulary for a diagnostic, sorted so the message is stable: `"never", "repair", "report"`.

func NoSourceCreateNames

func NoSourceCreateNames() string

NoSourceCreateNames renders the vocabulary for a diagnostic, sorted so the message is stable: `"create", "refuse"`.

func NoSourceCreateValid

func NoSourceCreateValid(v NoSourceCreate) bool

NoSourceCreateValid reports whether v is one of the two settings this fork's schema defines.

func NoStateCache added in v0.19.0

func NoStateCache(v Secrets) bool

NoStateCache reports whether v is a setting under which the local state cache is neither written nor read - as if CHOUDOUFU_STATE_CACHE=off, and still overridable by naming a path in that variable, which is a person asking for the file on purpose.

Refuse is the maintainer's ruling on GitHub issue #1375: the cache is a stock state file written unencrypted, so it holds every sensitive attribute in clear on each machine that applies, and "refuse" is an operator saying the tool keeps no secret material.

SSM is #1515's ruling 4, and it is the same sentence pointed at a different arrangement. Moving the values out of the bucket and leaving them in clear in .terraform/choudoufu-cache.tfstate on every laptop and CI runner that applied would buy the estate a KMS key policy on the copy nobody was worried about while leaving the copy on disk exactly where it was. An operator who wants the file anyway names a path, the same door "refuse" leaves open.

The zero value answers false - the cache stays on for a caller holding no configuration - which is what every run did before either setting existed.

func ParseSelection

func ParseSelection(types, addresses []string) (*Selection, []AddressProblem)

ParseSelection builds a Selection from the two literal lists a `markers "record"` block carries.

Types are taken as written: whether a name is a real provider resource type is a question for the schemas, which this package does not hold, and a type nothing declares selects nothing anyway.

Addresses go through addrs.ParseTargetStr, the `-target` grammar, which gives module qualification and rejects wildcards for free. Three further shapes are refused here, each because honouring it would mean guessing:

  • A module address with no resource ("module.net"). Selecting every resource under a module is a wider blast radius than either list names, and widening a marker-withholding selection by guess is the wrong direction.
  • A data address. There is no marker on one and no identity to record.
  • An instance key, on the resource or on any module step ("aws_instance.web[0]", "module.net[\"a\"].aws_subnet.this"). See this file's header: the unit is the block, and a per-instance selection cannot be honoured by a pass that rewrites one shared body.

Every problem is reported and the usable entries still build a selection, so an operator fixing a list sees every entry that needs fixing rather than one per run. A caller that must not act on a partial selection - the resolver, the stamp pass - checks that the problems slice is empty, which internal/live/lint has already turned into refusals by the time either runs.

func PinRefusal added in v0.5.0

func PinRefusal(name, value string) string

PinRefusal returns the refusal detail for a `strict.<name>` argument whose written value relaxes a Toggle.Pinnable toggle away from the value Pinned forces, or "" when nothing here needs refusing: the pin is not active, name is not a toggle this schema defines or is not pinnable, or value already equals the toggle's Toggle.SafeValue (the safe setting written out by hand, GitHub issue #101's own lesson applied here too, must lint exactly as leaving the pin to supply it silently does).

It says nothing about a value outside the toggle's vocabulary altogether - that is a typo, and internal/live/lint's own checkStrictSecrets and checkStrictNoSourceCreate already refuse it before they reach this function, under RuleStrictSecrets and RuleStrictNoSourceCreate respectively; PinRefusal only has an opinion once a value is confirmed valid.

The returned string names both sides on purpose - the pin (EnvPin and the value it forces) and the offending line's own construct and setting - because a reader of this message may control neither: an author whose commit set the argument needs to see the pin exists at all, and an operator investigating a refused run needs to see which line to remove.

func Pinned added in v0.5.0

func Pinned() bool

Pinned reports whether the execution environment has pinned the strict profile: EnvPin set to exactly "1" - an opt-IN grammar, since Pinned's default (unset) has to stay "not pinned" - rather than "any non-empty value", so a CI environment that exports the variable empty (a common shell idiom for "unset for this job") reads as unset rather than as a typo nobody can see. internal/command/live_mode.go's CHOUDOUFU_NODE_RESOLVE is the opposite shape, an opt-OUT grammar (exactly "0" turns it off, since IT defaults on) - the two switches read their on/off values from opposite defaults, so their grammars are deliberately mirror images, not the same rule.

func ProviderChangeNames added in v0.14.0

func ProviderChangeNames() string

ProviderChangeNames renders the vocabulary for a diagnostic, sorted so the message is stable: `"recreate", "refuse"`.

func ProviderChangeValid added in v0.14.0

func ProviderChangeValid(v ProviderChange) bool

ProviderChangeValid reports whether v is one of the two settings this fork's schema defines.

func RecreatesOnProviderChange added in v0.14.0

func RecreatesOnProviderChange(v ProviderChange) bool

RecreatesOnProviderChange reports whether v is the setting under which a resource whose provider configuration changed is planned as a create, leaving the old configuration's object behind, instead of refused.

A function over the type rather than a comparison at each call site, for CreatesFromNoSource's own reason: the zero value, ProviderChange(""), answers false here, so a layer holding no configuration never concludes the operator asked to relax the refusal.

func SecretsImplemented added in v0.19.0

func SecretsImplemented(v Secrets) bool

SecretsImplemented reports whether a build acts on v.

Store and Refuse are each somebody's whole behavior today and neither can be accepted-and-ignored. SSM is the one this fork's schema defines and this build does not act on yet: GitHub issue #1515 settles the grammar, the four rulings and every refusal around the setting, and the write path that would put a value into Parameter Store and a reference into the record is a later unit.

It is refused rather than accepted silently, for Implemented's reason pointed at a worse outcome. Accepting it would tell an operator their secret values were in Parameter Store under their own KMS key while every one of them went on being written into the estate's records in clear, and the only evidence would be the absence of parameters nobody was watching for. A refusal is loud and reversible; a setting that silently does nothing about secrets is the "you are fine" this fork keeps finding in itself.

Everything around the setting is built and is reached BEFORE this: the nested block decodes, internal/live/lint's strict-secrets-ssm refuses an arrangement that could not work, and the state cache is already off under it. So the day the write path lands, this entry flips and nothing else has to move.

func SecretsImplementedNames added in v0.19.0

func SecretsImplementedNames() string

SecretsImplementedNames renders just the settings a build acts on, in the shape SecretsNames uses: `"refuse", "store"`.

func SecretsInSSM added in v0.19.0

func SecretsInSSM(v Secrets) bool

SecretsInSSM reports whether v is the setting under which a kept secret value goes to AWS Systems Manager Parameter Store rather than into the record itself. GitHub issue #1515.

It is the narrow question StoresSecrets deliberately does not answer. Every layer that asks "may this run keep this value at all" - the lint rules that refuse a secret-generating type, the residue writer that decides whether a sensitive settable argument is remembered - gets the same answer under Store and under SSM, because the value IS kept either way and an operator who chose SSM did not choose to stop generating passwords. Only the layer that writes the value has to know where it goes, and that layer asks this.

The zero value answers false, for StoresSecrets's own reason: a caller holding no configuration must not conclude the operator asked for a second service in the write path.

func SecretsNames

func SecretsNames() string

SecretsNames renders the vocabulary for a diagnostic, sorted so the message is stable: `"refuse", "ssm", "store"`.

func SecretsValid

func SecretsValid(v Secrets) bool

SecretsValid reports whether v is one of the three settings this fork's schema defines. It says nothing about whether a build acts on it - see SecretsImplemented.

func StoresSecrets

func StoresSecrets(v Secrets) bool

StoresSecrets reports whether v is a setting under which a run may keep secret material at all - Store, which keeps it in the estate's record store the way stock keeps it in a state file, and SSM, which keeps the same values in Parameter Store and leaves a reference in the record.

"At all" is the whole of what it asks, and the two callers it has are why it is phrased that way. A lint rule refusing a secret-generating type and a residue writer dropping a sensitive settable argument are both acting on "this run keeps nothing", which is Refuse alone; neither has any business behaving differently because the value's destination changed. The layer that writes the value asks SecretsInSSM instead.

It is a function over the type rather than a `v == Store` at each call site because the call sites are in four packages (internal/live/lint, internal/live/identity, internal/live/projection, internal/live/liveimport) and the zero value has to answer the same way everywhere. Secrets("") is what a caller holding no configuration has, and it answers FALSE here while DefaultSecrets answers true - the two are different questions and the difference is the fail-safe: a layer that could not read the configuration must not conclude the operator asked for storage. Every layer that CAN read it resolves the omitted argument to DefaultSecrets first; see identity.SecretsFor, which is the one place that resolution happens.

func Valid

func Valid(v MarkerRepair) bool

Valid reports whether v is one of the three settings this fork's schema defines. It says nothing about whether a build acts on it - see Implemented.

Types

type AddressProblem

type AddressProblem struct {
	// Raw is the string as written in the configuration.
	Raw string

	// Detail is one sentence naming what is wrong with it, aimed at an
	// operator and phrased as the fix where there is one.
	Detail string
}

AddressProblem is one entry of a `markers "record"` block's addresses list that cannot be used, with the sentence saying why.

It is a value rather than a diagnostic because this package holds no source ranges: internal/configs recorded where the list was written and internal/live/lint turns these into [lint.Issue]s pointing at it. The Raw field is what the author typed, so the message can quote it back.

type MarkerRepair

type MarkerRepair string

MarkerRepair is what a run does about an ownership marker on a live object that disagrees with the marker this configuration would write.

The three settings are HANDOFF.md's, and the one that matters for reading this package is that NONE of them is about creating a marker. A resource this run creates is stamped whatever the setting: the safety rule ("never write a wrong marker") has no converse permitting an unmarked create, and a create writes a marker that is new rather than one that disagrees with anything. Only an existing, already-well-formed marker whose value differs is in scope here.

const (
	// Repair writes the marker this configuration declares over the one the
	// live object carries, as an ordinary in-place tags update in the plan.
	// This is what every run does today and what a configuration with no
	// strict block keeps doing.
	//
	// live/MARKERS.md is why it is the default: tofu-address is mandatory
	// on every managed resource and "there is exactly one tofu-address
	// value on a resource at any time", so a stale value is a history the
	// spec does not allow, and a mandatory tag that is allowed to be wrong
	// is worse than no tag at all - any tool honoring the three keys would
	// bind the wrong resource.
	Repair MarkerRepair = "repair"

	// Report leaves the live object's marker as it is and says so, naming
	// the address and the value the run would have written. The run
	// proceeds.
	Report MarkerRepair = "report"

	// Never leaves the live object's marker as it is, silently. It is the
	// setting for an estate where something else owns the tags, and
	// HANDOFF.md pairs it with honouring lifecycle { ignore_changes } the
	// way stock honours it.
	//
	// That pairing is not a consequence of the setting, it IS the setting.
	// Nothing in this fork writes a marker onto a live object directly (see
	// [Implemented]), so the only thing "never repair" can name is the
	// ordinary tags diff, and the only way to suppress that diff is
	// lifecycle { ignore_changes } - which internal/live/lint refuses,
	// because a resource whose marker is neither written nor repaired has no
	// identity left. Give it one, with a markers "record" selection, and the
	// refusal has nothing to protect: that is what makes this setting
	// implementable, and it is why it is implemented WITH such a selection
	// and refused without one. See [ImplementedWithSelection].
	Never MarkerRepair = "never"
)

type NoSourceCreate

type NoSourceCreate string

NoSourceCreate is what a run does when an instance has no record, no live marker, and an identity nothing - neither the static evaluator nor GitHub issue #388's plan-node seam - can derive from its configuration. Ruling 4 of the foundation-order ruling (#388).

HANDOFF.md's safety rule is why the default refuses rather than creates: a genuinely new instance and a real one this run simply cannot see yet are indistinguishable from where this toggle is read, and creating a second copy of a real object is the "wrong marker" failure the rule exists to prevent, wearing a create's clothes instead of a marker's.

const (
	// Refuse is the default: the instance is reported, by name, as unable
	// to be planned, naming both remedies - running `choudoufu live-import`
	// from the stock state that already holds it, or setting this toggle.
	NoSourceRefuse NoSourceCreate = "refuse"

	// Create selects stock OpenTofu's own behavior for a resource with no
	// prior state: plan a create. It is the toggle, not the default,
	// because HANDOFF.md's principles are toggles and this is the first
	// one of them - "never write a wrong marker" - pointed at a create
	// instead of at a marker.
	NoSourceCreateOn NoSourceCreate = "create"
)

func NoSourceCreateDefault added in v0.5.0

func NoSourceCreateDefault() NoSourceCreate

NoSourceCreateDefault is SecretsDefault's twin for internal/live/identity.NoSourceCreateFor.

func PinnedNoSourceCreate added in v0.5.0

func PinnedNoSourceCreate() NoSourceCreate

PinnedNoSourceCreate is PinnedSecrets's twin for the no_source_create toggle. It equals DefaultNoSourceCreate today - the default was already the safety-first setting before GitHub issue #365's environment pin existed - so pinning only ever changes behavior for a configuration that explicitly asks for "create", and that case is PinRefusal's, not this function's.

type ProviderChange added in v0.14.0

type ProviderChange string

ProviderChange is what a run does when a resource block has moved to a different provider configuration while a live object in the one it left still carries this estate's ownership marker for that block's address. GitHub issue #906.

Repointing `provider = aws.west` at `aws.east` is a replace in stock terms, and only half of it is expressible here: a resource address carries exactly one provider configuration in the plan graph, taken from its own block, so the destroy of the object left behind cannot be planned at that address at all. That leaves proceeding or refusing, and HANDOFF.md's safety rule is why the default refuses: proceeding creates a second live resource carrying one address's marker, which live/MARKERS.md forbids outright and which internal/live/discovery's own crossProviderOrphanCollisions refuses a plan over once both objects exist. A refusal here is that refusal one step earlier, before the run manufactures the state rather than after.

const (
	// ProviderChangeRefuse is the default: the run reports the live object,
	// by name, with the provider configuration that found it and the one
	// its address now belongs to, and names both remedies - removing or
	// disowning the object, or setting this toggle.
	ProviderChangeRefuse ProviderChange = "refuse"

	// ProviderChangeRecreate selects stock OpenTofu's own behavior for a
	// resource whose provider configuration changed: plan the create under
	// the new one and leave the old configuration's object where it is.
	// The object stays live, keeps this estate's markers, and no plan will
	// ever propose anything for it, because the sweep looks where a
	// provider configuration points and none points there for that address
	// any more. It is the toggle, not the default, for the reason
	// [NoSourceCreateOn] is: a real object this run can see is about to be
	// duplicated, and only an operator can say that is what they meant.
	ProviderChangeRecreate ProviderChange = "recreate"
)

func PinnedProviderChange added in v0.14.0

func PinnedProviderChange() ProviderChange

PinnedProviderChange is PinnedSecrets's twin for the provider_change toggle. It equals DefaultProviderChange, the same way PinnedNoSourceCreate equals its own default: the safety-first setting is already what an omitted argument resolves to, so pinning only ever changes behavior for a configuration that explicitly asks for "recreate", and that case is PinRefusal's.

func ProviderChangeDefault added in v0.14.0

func ProviderChangeDefault() ProviderChange

ProviderChangeDefault is SecretsDefault's twin for internal/live/identity.ProviderChangeFor.

type Secrets

type Secrets string

Secrets is what a run does with the secret material a configuration generates or sets: the values stock OpenTofu writes into its state file and reads back out of it on the next run.

HANDOFF.md states both halves. The default is the compatibility half - "secrets the configuration generates are stored there the way stock stores them" - and the principle this fork exists for is the toggle: "no secrets stored by the tool (secret-generating types refused, sensitive settable arguments never recorded)".

What "the way stock stores them" means here

A stock run keeps one secret in one place: the state file, in clear, as an ordinary attribute value, with a "sensitive_attributes" list beside it saying which paths were marked. This fork has no state file, so the equivalent place is the estate's record store - namespaced per estate, under IAM, written with compare-and-swap - and the sensitivity travels with the value (internal/live/projection's sensitivepaths.go persists the state file's own encoding of it). That is the whole of what Store turns on: nothing new is computed, nothing extra is written, and no value that was not already in a stock state file becomes recordable.

What no setting can turn on

A WRITE-ONLY attribute, and this is a protocol rule rather than a policy one. The plugin protocol forbids a provider ever returning a write-only value (internal/plugin6/validation/write_only.go refuses one that does), so a recorded write-only value could never be checked against the object it claims to describe, and stock does not store one either - it nulls them out before the state is written. Recording it is not a stricter or laxer choice; it is a wrong one. Store does not reach it and must not be made to.

Nor an effect RECEIPT's value. A receipt is a published breadcrumb whose whole purpose is that other tools can read it (live/RECEIPTS.md), which is the opposite of a record store's IAM boundary, and stock has no equivalent of it to be compatible with. internal/live/lint's RuleReceiptSecret is outside this toggle's reach for that reason.

const (
	// Store admits a secret-bearing type or argument and keeps its value the
	// way stock OpenTofu keeps it. It is the default, and it is what
	// HANDOFF.md's "compatible out of the box" means for this dimension: a
	// configuration that works on stock works here with a live block added
	// and nothing else, and a configuration that generates a password is
	// exactly such a configuration.
	Store Secrets = "store"

	// Refuse keeps no secret material anywhere this run can write: a
	// secret-generating logical type is refused outright, and a sensitive
	// settable argument is never recorded as residue. It is HANDOFF.md's
	// first principle, and it is what every run did before this toggle
	// existed.
	//
	// It is a REFUSAL and not a silent omission, which is the part worth
	// reading twice. Quietly dropping a sensitive argument from a record
	// would leave the estate proposing an update to it on every run forever,
	// with nothing saying why; refusing the type says so once, loudly, at
	// the configuration.
	Refuse Secrets = "refuse"

	// SSM admits a secret-bearing type or argument, keeps its value, and
	// keeps it somewhere the estate's record store is not: AWS Systems
	// Manager Parameter Store, as a SecureString under a customer managed
	// KMS key, with the record carrying a reference in place of the value.
	// GitHub issue #1515, ruling 2, splitting #1244's section 3.
	//
	// It is a third answer to the question the other two share, not a
	// variant of either. [Store] answers "keep them, here"; [Refuse]
	// answers "keep none"; this one answers "keep them, over there", for an
	// organisation whose rotation, audit and access review already live in
	// SSM. What an operator buys is a second access boundary on the values
	// alone - reading a record no longer reads a private key, because the
	// record does not hold one - and what they pay for it is a second
	// service in the write path and SSM's own quotas.
	//
	// It is NOT the retired record_store "ssm" backend (#1346), which put
	// whole records into Parameter Store as plain String parameters and was
	// retired for its capacity ceiling and its weak compare-and-swap.
	// Records stay in S3 here, and S3's If-Match stays the only thing that
	// decides a concurrent write: every SSM write is a create, with
	// Overwrite false, of a name no other write uses, so PutParameter's
	// read-compare-write weakness has nothing to decide (#1515, ruling 1).
	//
	// It pairs with record_store "s3" and with nothing else. The local and
	// Kubernetes stores refuse it by name rather than ignoring it, because
	// an estate whose records are in a local directory has no If-Match to
	// commit a reference with, and a reference that is not committed
	// atomically is a record pointing at a parameter that may not exist.
	SSM Secrets = "ssm"
)

func PinnedSecrets added in v0.5.0

func PinnedSecrets() Secrets

PinnedSecrets is what [SecretsFor]-shaped resolution answers for an OMITTED secrets argument while Pinned - see [SecretsFor]'s own doc comment in internal/live/identity for why an explicit, invalid, or relaxing argument is not folded into this function and is left to PinRefusal and the lint rule that calls it instead.

func SecretsDefault added in v0.5.0

func SecretsDefault() Secrets

SecretsDefault is what internal/live/identity.SecretsFor resolves an omitted, absent, or unrecognised secrets argument to: DefaultSecrets unless Pinned, in which case PinnedSecrets - the pin's silent half. The loud half, refusing a configuration that explicitly sets a valid but relaxing value while pinned, is PinRefusal's.

type Selection

type Selection struct {
	// contains filtered or unexported fields
}

Selection is the set of resources one `markers "record"` block covers: resources whose identity lives in the estate's record store instead of in an ownership marker tag.

The zero value, and a nil *Selection, select nothing. That is what every configuration written before this block existed gets, and it is what makes HANDOFF.md's "compatible out of the box" true here by construction rather than by review.

func (*Selection) Empty

func (s *Selection) Empty() bool

Empty reports whether s narrows nothing: no type and no address.

A `markers "record" {}` block naming neither is indistinguishable from no block at all, exactly as an empty scope block is indistinguishable from no scope block in a policy (see [configs.LivePolicyScope] and internal/live/lint's scopeIsSet), so internal/live/lint refuses it rather than reading it as a selection of everything or of nothing.

func (*Selection) Resources

func (s *Selection) Resources() []string

Resources is the address list this selection was built from, sorted and rendered in the canonical addrs.ConfigResource spelling.

func (*Selection) Selects

func (s *Selection) Selects(addr addrs.ConfigResource) bool

Selects reports whether addr is covered, by its type or by its address.

addr is the CONFIG resource - a module path with no instance keys - for the reason this file's header gives. A caller holding an addrs.AbsResourceInstance reduces with its ConfigResource method, which drops both the module instance keys and the resource instance key; that widening is deliberate and is the same block-level coarsening internal/live/stamp's PolicyUntag already documents.

func (*Selection) SelectsType

func (s *Selection) SelectsType(resourceType string) bool

SelectsType reports whether the selection names resourceType outright, so that every resource of that type in the configuration is covered.

func (*Selection) Types

func (s *Selection) Types() []string

Types is the type list this selection was built from, sorted, for a diagnostic or a report.

type Toggle added in v0.5.0

type Toggle struct {
	// Name is the argument's spelling inside a `strict { ... }` block, the
	// same string internal/configs/live.go's decoder records - "secrets",
	// not "Secrets" or "strict.secrets".
	Name string

	// Default is the literal spelling an omitted argument resolves to,
	// rendered from this package's own DefaultXxx constant so the two
	// cannot drift apart (see TestTogglesDefaultsMatchConstants).
	Default string

	// Relaxes is one sentence naming the refusal a setting other than
	// Default trades away (a compatibility toggle moving further from
	// stock's own behavior) or adds back (a safety toggle moving closer to
	// HANDOFF.md's principles). It is written from the toggle's own
	// non-default setting's point of view, not from Default's.
	Relaxes string

	// Doc is the live/LIMITATIONS.md heading an operator reads for the
	// rule this toggle's typo case fires, the same string ruleInfo's
	// docsRef carries for the lint rule that checks it.
	Doc string

	// Pinnable is whether GitHub issue #365's environment pin ([EnvPin],
	// [Pinned]) can force this toggle to SafeValue from outside the
	// configuration. See [PinRefusal].
	//
	// [MarkerRepair] is not pinnable: its three settings are not a single
	// safety axis the way [Secrets] and [NoSourceCreate] are (a plain
	// "repair" is not less safe than "never", it is a different mechanism
	// paired with a `markers "record"` selection - see
	// internal/live/strict.Implemented's doc comment), so there is no one
	// setting a platform team pinning "the strict profile" could mean by
	// it. A future setting joins the pinnable set only when it has that
	// same single safety axis [Secrets] and [NoSourceCreate] do.
	Pinnable bool

	// SafeValue is the literal spelling [Pinned] forces this toggle to, and
	// the one setting an operator's own `strict.<Name>` argument may not
	// move away from while the pin is active (see [PinRefusal]). Empty
	// when !Pinnable.
	SafeValue string

	// Values is the settable spellings this registry declares for the
	// toggle, in the order live/STRICT.md's
	// generated table renders them (tools/toggles-gen). Default is always
	// a member (see TestToggleValuesContainDefault).
	//
	// This is deliberately NOT the same set as this package's own Valid
	// (or SecretsValid / NoSourceCreateValid) predicate for every toggle.
	// [MarkerRepair]'s Values is the one place the two differ: [Report] is
	// grammar the decoder still parses and [Valid] still recognizes - so a
	// configuration that writes it gets [unimplementedRepairDetail]'s
	// specific "not yet implemented" refusal rather than a generic typo
	// one - but no build implements it and, unlike [Never], it has no
	// conditional path through a `markers "record"` selection either (see
	// [ImplementedWithSelection]): there is no mechanism this registry can
	// honestly tell an operator to reach for. A 2026-08-24 audit of GitHub
	// issue #365 found the registry declaring it anyway, described the
	// same way as [Never]'s selection-gated case, which overstated report
	// mode into looking like ordinary unfinished-but-reachable work. It is
	// therefore left out of Values here - not out of the language, only
	// out of what this registry advertises as usable - until it either
	// gets a real mechanism (report mode implemented) or [Valid] drops it
	// too. See Relaxes below for the same point made in the rendered doc.
	Values []string

	// Meaning is the table cell an operator reads to know what the
	// argument controls, independent of Relaxes (which is written from
	// the non-default setting's point of view and is also read outside
	// the table, by [PinRefusal]).
	Meaning string
}

Toggle is one entry in this fork's behavior-toggle schema: a `strict` block argument's name, what an omitted argument resolves to, and the refusal its non-default setting relaxes or tightens relative to stock OpenTofu's own behavior. GitHub issue #365's consolidation is this type existing at all - before it, a toggle's name lived in internal/configs/live.go's decode table, its default and valid spellings lived in this package's own constants (MarkerRepair, Secrets, NoSourceCreate), and nothing in the tree said in one place what a given toggle traded away or whether it could be pinned. Toggles is that one place: every toggle this schema defines has exactly one Toggle entry, and live/LIMITATIONS.md cites the same Doc string this type carries.

It deliberately does not replace the typed constants above. Those stay the compile-time-checked vocabulary a resolver switches on (StoresSecrets, CreatesFromNoSource, Implemented); this type is the runtime-inspectable INVENTORY of that vocabulary, read by the doc reference, by PinRefusal's generic lookup, and by anything that needs to enumerate every toggle without a hand-written switch over the three types.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL