strict

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 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 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.`,
		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" keeps none of it: a secret-generating type is ` +
			`refused outright, and a sensitive settable argument is never recorded.`,
	},
	{
		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.`,
	},
}

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 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 SecretsNames

func SecretsNames() string

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

func SecretsValid

func SecretsValid(v Secrets) bool

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

func StoresSecrets

func StoresSecrets(v Secrets) bool

StoresSecrets reports whether v is the setting under which a run may keep secret material.

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 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"
)

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 [site/content/docs/use/reference.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