OpenTofu Main Tracks Desired Instances and Package Credentials


OpenTofu on main took seven commits from 30 September through 8 October 2026: 38 files, 543 insertions, 893 deletions, mostly the experimental planner dropping per prefix orphan callbacks and letting the engine drive the walk. Release builds still use the graph walker. The new engine runs only in an experiments enabled build when TOFU_X_EXPERIMENTAL_RUNTIME is set, while the normal CLI changes in two places: provider package credentials, and a fix for a panic while reading lifecycle.destroy.

DrivePlanning is gone. The planning responsibility split replaces it with ConfigInstance.BuildPlanningOracle in config_plan.go. The function builds a PlanningOracle, wraps the caller PlanGlue in planningEvalGlue, and returns. It does not walk the configuration.

PlanGlue lost PreProcess and PostProcess. The commit says those hooks smelled wrong. Targeting is PlanningOracle.CheckTarget in config_plan_oracle.go, the walk is CheckAll, and root outputs come from PlanningResult. Eval answers questions. The engine decides when to ask them.

normalPlan and destroyPlan share one order. Build the glue, build the oracle, then store the oracle on the glue. The comment calls that a chicken and egg. Then CheckTargets, CheckAll, and Finalize. CheckAll still blocks until PlanDesiredResourceInstance has run for each visible instance. Finalize also closes providers, which the mode functions used to do after planCtx.Close.

If BuildPlanningOracle returns errors, both modes return a plan with Errored set and nil diagnostics. A caller that only reads diagnostics will not see why setup stopped.

The orphan handling change deletes PlanResourceOrphans, PlanResourceInstanceOrphans, PlanModuleCallOrphans, and PlanModuleCallInstanceOrphans, and drops announcePlanOrphans on the eval side. The message says a conversation with apparentlymart made that channel redundant, because PlanDesiredResourceInstance already visits every configured instance.

The old methods rescanned previous state at each prefix. A comment wanted a tree shaped state instead. This commit scans once: 9 files, 254 insertions, 637 deletions.

PlanDesiredResourceInstance stores inst.Addr on planContext.desired. After the walk, plan_eval_glue.go walks resourceInstancesObjects. Deposed objects, left when the delete half of a create before destroy replace failed, are planned alone. Every other object is an orphan unless a desired address equals it, or a placeholder covers it through PlaceholderContains.

That helper is how unknown count and for_each avoid deletes the engine cannot predict. On a module step it is true when the desired step is a wildcard or the keys match, and it never compares the module call name. A placeholder in one call can cover state in another call at the same depth when the resource type and name match. That suppresses a delete. It does not add one.

validateForceReplace now uses the desired set. It still only warns when a force replace address has no instance key and the resource is keyed.

The store happens only if the glue is called. Desired instances are reported even when evaluation fails changes resource_instance.go. ResourceInstance.Value used to return before Glue.ResultValue on a config or provider error, so the address was missing and the instance could be planned as a destroy. It now forwards an eval error around cty.DynamicVal and still calls ResultValue. desiredResourceInstanceMustBeDeferred treats that as a deferral. plan_managed.go no longer bails out when the provider value has marks. That path logged BUG %s is not orphaned and skipped the orphan. plan_data.go still unmarks the provider and drops the marks. The TODO does not say what a data resource should do with them.

The shipping walker changed the next day. Ignored destroy diagnostics are now kept, one line in internal/tofu/node_resource_abstract.go, outside the experimental runtime. skipDestroyValueFromConstantExpression evaluated lifecycle.destroy and discarded diagnostics from Value. Test InvalidConfigFlag sets destroy = var.input in destroy mode. That is not a constant, so the test panicked. It now returns a plan error. The commit is a follow up to issue 3409. On the normal engine, lifecycle.destroy still has to be a boolean constant.

The 30 September commits are for private registries. The provider field rename in internal/getproviders/registry_client.go switches download metadata from use_mirror_credentials to use_registry_credentials. Main had copied the mirror name. A registry is not a mirror, and the module protocol already said registry. PackageMeta treats a missing field as false. False and absent send the zip with no Authorization header. True wraps the download in registryCredentialTransport, and the test looks for that header on the zip.

The protocol docs cover modules, provider registries, and network mirrors. For modules, OpenTofu v1.13 and later accept the flag on the download response. If the property is present, location must be an HTTP or HTTPS zip, or a tar compressed with gzip, xz, or bzip2. True reuses the metadata credentials. For providers, true also applies to shasums_url and shasums_signature_url. Mirrors keep use_mirror_credentials.

The default is intentional. The index and the bytes might live on different hosts. Forwarding the registry token would leak it. A registry that hosts the zip itself has to opt in inside the JSON. The CLI does not grow a switch for this.

Goroutine identity annotations do not change plans. The patch is 35 lines in 12 files. grapheval.Once workers look alike, so a panic stack often fails to name the object. The commit says Go 1.27 prints runtime/pprof labels in the panic header. Labels travel on the context. once.go calls pprof.SetGoroutineLabels on the worker. once_valuer.go picks the shape.

func withDebugAddr(ctx context.Context, addr fmt.Stringer, methodName string) context.Context {
    return pprof.WithLabels(ctx, pprof.Labels(methodName, addr.String()))
}

The key is the method name and the value is the address. Sites are Value on variables, locals, outputs, modules, and resources, plus ModuleCall.Instances, SourceArguments, and InputsValue. The commit stops there. Profiles see the labels. Plan output does not.

The experimental planner is still a shim. internal/tofu/context_temp_runtime.go allows it only in an experiments enabled build, and only when TOFU_X_EXPERIMENTAL_RUNTIME is a value other than empty. Dropped diagnostics on oracle setup failure, and PlaceholderContains ignoring module call names, are both on that path. A green experimental plan does not prove that an unknown for_each module will delete the right objects.

Registry code tested against main should send use_registry_credentials on provider package metadata. use_mirror_credentials is the mirror field. Leave the provider field unset unless the zip, the checksum file, and the signature sit behind the same credentials as the registry API.

On the shipping engine, lifecycle.destroy remains a boolean constant. A reference such as var.input should fail a destroy plan with a diagnostic instead of panicking in skipDestroyValueFromConstantExpression. Provider instance marks on data resources in the new engine are still discarded.