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).
Index ¶
- Constants
- func CreatesFromNoSource(v NoSourceCreate) bool
- func Implemented(v MarkerRepair) bool
- func ImplementedNames() string
- func ImplementedWithSelection(v MarkerRepair) bool
- func Names() string
- func NoSourceCreateNames() string
- func NoSourceCreateValid(v NoSourceCreate) bool
- func ParseSelection(types, addresses []string) (*Selection, []AddressProblem)
- func SecretsNames() string
- func SecretsValid(v Secrets) bool
- func StoresSecrets(v Secrets) bool
- func Valid(v MarkerRepair) bool
- type AddressProblem
- type MarkerRepair
- type NoSourceCreate
- type Secrets
- type Selection
Constants ¶
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."
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.
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.
Variables ¶
This section is empty.
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 SecretsNames ¶
func SecretsNames() string
SecretsNames renders the vocabulary for a diagnostic, sorted so the message is stable: `"refuse", "store"`.
func SecretsValid ¶
SecretsValid reports whether v is one of the two settings this fork's schema defines.
func StoresSecrets ¶
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 rfc/20260823-foundation-order-ruling.md.
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" )
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" )
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 ¶
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 ¶
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 ¶
SelectsType reports whether the selection names resourceType outright, so that every resource of that type in the configuration is covered.