Commit Graph

33305 Commits

Author SHA1 Message Date
James Humphries
c488bfbb48 [OTel] Add resource type trace attribute to all resource instance spans
Signed-off-by: James Humphries <james@james-humphries.co.uk>
2026-02-12 16:20:12 +00:00
Martin Atkins
8da8e5e216 go.mod: Upgrade to Go 1.26
This is just a routine upgrade, with the intention that the forthcoming
OpenTofu v1.12 series will be based on this Go release series and so will
be under security support until February 2027 when this Go release will
cease to be supported.

As with the v1.11 series, we cannot predict exactly which day of February
Go 1.28 will be released on and so we'll be conservative and promise
support until the first day of that month, but in practice we're likely to
continue adopting relevant Go 1.26 minor releases for additional weeks of
February until the Go team stops publishing them.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-12 07:57:45 -08:00
Andrei Ciobanu
b108d3fb53 Add universe_domain to the gcs backend (#3758)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-12 11:46:18 +00:00
Andrei Ciobanu
92ada6a5c0 [RFC]Proposal to remove Meta struct, rework flags handling and pave the way for a possible new CLI layer (#3471)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-12 08:46:31 +02:00
Andrei Ciobanu
5603b8a27c Refactor init command to use View instead of Ui (#3749)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-11 16:29:31 +02:00
Martin Atkins
1e0a920a34 planning: Sketch of even more managed resource instance actions
This provides at least a partial implementation of every resource instance
action except the ones involving "forget" actions.

However, we don't really quite have all of the building blocks needed to
properly model "delete" yet, because properly handling those actions means
we need to generate "backwards" dependency edges to preserve the guarantee
that destroying happens in reverse order to creating. Therefore the main
outcome of this commit is to add a bunch of FIXME and TODO comments
explaining where the known gaps are, with the intention of then filling
those gaps in later commits once we devise a suitable strategy to handle
them.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-10 07:07:01 -08:00
Martin Atkins
ac9a26d5b9 exec: ManagedDepose and ManagedChangeAddr take object as operand
These both effectively had the behavior of ResourceInstancePrior embedded
in them, reading something from the state and change its address as a
single compound operation.

In the case of ManagedDepose we need to split these up for the
CreateThenDestroy variant of "replace", because we want to make sure the
final plans are valid before we depose anything and we need the prior state
to produce the final plan. (Actually using that will follow in a subsequent
commit.)

This isn't actually necessary for ManageChangeAddr, but splitting it keeps
these two operations consistent in how they interact with the rest of the
operations.

Due to how the existing states.SyncState works we're not actually making
good use of the data flow of these objects right now, but in a future world
where we're no longer using the old state models hopefully the state API
will switch to an approach that's more aligned with how the execgraph
operations are modeled.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-10 07:07:01 -08:00
Martin Atkins
766be2c7fb planning: Initial handling of "orphan" resource instance objects
This is just enough to make it possible to destroy previously-created
objects by removing them from the configuration. For now this intentionally
duplicates some of the logic from the desired instance planning function
because that more closely matches how this was dealt with in the
traditional runtime. Once all of the managed-resource-related planning
functions are in a more complete state we'll review them and try to extract
as much functionality as possible into a common location that can be shared
across all three situations. For now though it's more important that we be
able to quickly adjust the details of this code while we're still nailing
down exactly what the needed functionality is.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-10 07:07:01 -08:00
Andrei Ciobanu
da0f45bf69 Move all implementation of flag.Value in one package (#3725)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-10 16:26:09 +02:00
Andrei Ciobanu
25d652dece -chdir unification with the workdir logic. Removal of workdir proxy methods from Meta (#3713)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-10 15:31:06 +02:00
Martin Atkins
db8c872def planning: Initial unit tests for managed resource instance execgraph
We're currently being intentionally cautious about adding too many tests
while these new parts of the system are still evolving and changing a lot,
but the execGraphBuilder behavior is hopefully self-contained enough for
a small set of basic tests to be more helpful than hurtful.

We should extend this test, and add other test cases that involve more
complicated interactions between different resource instances of different
modes, once we feel that these new codepaths have reached a more mature
state where we're more focused on localized maintenance than on broad
system design exploration.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
Martin Atkins
96c6382d48 planning: DeleteThenCreate execution subgraph
A DeleteThenCreate action decomposes into two plan-then-apply sequences,
representing first the deletion and then the creation. However, we order
these in such a way that both plans need to succeed before we begin
applying the "delete" change, so that a failure to create the final plan
for the "create" change can prevent the object from being destroyed.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
Martin Atkins
d6d7412d4b execgraph: ManagedApply operation needs "waitFor" argument
Because this operation represents the main externally-visible side effects
in the apply phase, in some cases we'll need to force additional
constraints on what must complete before executing it. For example, in
a DestroyThenCreate operation we need to guarantee that the apply of the
"destroy" part is completed before we attempt the apply for the "create"
part.

(The actual use of this for DestroyThenCreate will follow in a later
commit.)

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
Martin Atkins
b2dfdf147b planning: Different execgraph subgraph shapes per change action
Previously the generated execgraph was naive and only really supported
"create" changes. This commit has some initial work on generalizing
that, though it's not yet complete and will continue in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
Martin Atkins
a1aec0e920 execgraph: opManagedChangeAddr for handling "moved" blocks
Whenever we plan for a resource instance object to switch from one instance
address to another, we'll use this new op instead of ResourceInstancePrior
to perform the state modification needed for the rename before returning
the updated object.

We combine the rename and the state read into a single operation to ensure
that they can appear to happen atomically as far as the rest of the system
is concerned, so that there's no interim state where either both or neither
of the address bindings are present in the state.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
Martin Atkins
2a9dfed739 execgraph: Constant DeposedKey values
In order to describe operations against deposed objects for managed
resource instances we need to be able to store constant states.DeposedKey
values in the execution graph.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
Martin Atkins
fdd992cfa7 planning: Build manage resource instance execgraph from planned change
The temporary placeholder code was relying only on the
DesiredResourceInstance object, which was good enough for proving that this
could work at all and had the advantage of being a new-style model with
new-style representations of the provider instance address and the other
resource instances that the desired object depends on.

But that isn't enough information to plan anything other than "create"
changes, so now we'll switch to using plans.ResourceInstanceChange as the
main input to the execgraph building logic, even though for now that means
we need to carry a few other values alongside it to compensate for the
shortcomings of that old model designed for the old language runtime.

So far this doesn't actually change what we do in response to the change
so it still only supports "create" changes. In future commits we'll make
the execGraphBuilder method construct different shapes of graph depending
on which change action was planned.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-09 07:35:50 -08:00
James Humphries
4cd169b625 Add nightly build info, clean up README.md (#3744)
Signed-off-by: James Humphries <james@james-humphries.co.uk>
2026-02-09 15:34:39 +00:00
Andrei Ciobanu
fc17142315 Add logger to the registry client (#3736)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-06 09:45:29 +02:00
Martin Atkins
6ec04c1227 planning: Further simplify provider instance items in execgraph
The previous commit split the handling of provider instance items into
separate dependency-analysis and execgraph construction steps, with the
intention of avoiding the need for the execGraphBuilder to directly
interact with the planning oracle and thus indirectly with the evaluator.
Overall the hope is that execGraphBuilder will be a self-contained object
that doesn't depend on anything else in the planning engine so that it's
easier to write unit tests for it that don't require creating an entire
fake planning context.

However, on reflection that change introduced a completely unnecessary
extra handoff from the execGraphBuilder to _another part of itself_, which
is obviously unnecessary complexity because it doesn't serve to separate
any concerns.

This is therefore a further simplification that returns to just doing the
entire handling of a provider instance's presence in the execution graph
only once we've decided that at least one resource instance will
definitely use the provider instance during the apply phase.

There is still a separation of concerns where the planGlue type is
responsible for calculating the provider dependencies and then the
execGraphBuilder is only responsible for adding items to the execution
graph based on that information. That separation makes sense because
planGlue's job is to bridge between the planning engine and the evaluator,
and it's the evaluator's job to calculate the dependencies for a provider
instance, whereas execGraphBuilder is the component responsible for
deciding exactly which low-level execgraph operations we'll use to describe
the situation to the apply engine.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-05 13:06:39 -08:00
Martin Atkins
22e5168af1 planning: Split responsibility for provider instance execgraph building
Previously we did _all_ of the work related to preparing execgraph items
for provider instances as a side-effect of the ProviderInstance method
of execGraphBuilder.

Now we'll split it into two parts: the first time we encounter each
provider instance during planning we'll proactively gather its dependencies
and stash them in the graph builder. However, we'll still wait until the
first request for the execution subgraph of a provider instance before
we actually insert that into the graph, because that then effectively
excludes from the apply phase any provider instances that aren't needed for
the actual planned side-effects. In particular, if all of the resource
instances belonging to a provider instance turn out to have "no-op" plans
then that provider instance won't appear in the execution graph at all.

An earlier draft of this change did the first dependency capturing step via
a new method of planGlue called by the evaluator, but after writing that
I found it unfortunate to introduce yet another inversion of control
situation where readers of the code just need to know and trust that the
evaluator will call things in a correct order -- there's already enough
of that for resource instances -- and so I settled on the compromise of
having the ensureProviderInstanceDependencies calls happen as part of the
linear code for handling each resource instance object, which makes it far
easier for a future maintainer to verify that we're upholding the contract
of calling ensureProviderInstanceDependencies before asking for an
execgraph result, while still allowing us to handle that step generically
instead of duplicating it into each resource-mode-specific handler.

While working on this I noticed a slight flaw in our initial approach to
handling ephemeral resource instances in the execution graph, which is
described inline as a comment in planDesiredEphemeralResourceInstance.
We'll need to think about that some more and deal with it in a future
commit.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-05 13:06:39 -08:00
Andrei Ciobanu
bb086453a9 Exclude v1.8 from govulncheck (#3733)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-05 17:54:44 +02:00
Christian Mesh
8ce780b4e0 engine/ephemeral: Wire together basic ephemeral functionality (#3710)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2026-02-04 16:44:20 -05:00
Martin Atkins
b19a648984 planning: Split off a higher-level execgraph builder
As we've continue to build out the execution graph behavior during the plan
and apply phases we've found that the execgraph package isn't really the
best place for having the higher-level ideas like singleton provider
client operations because that package doesn't have enough awareness about
the planning process to manage those concerns well.

The mix of both high- and low-level concerns in execgraph.Builder was also
starting to make it have a pretty awkward shape where some of the low-level
operations needed to be split into two parts so that the higher-level parts
could call them while holding the builder's mutex.

In response to that here we split the execgraph.Builder functionality so
that the execgraph package part is concerned only with the lowest-level
concern of adding new stuff to the graph, without any attempt to dedupe
things and without care for concurrency. The higher-level parts then live
in a higher-level wrapper in the planning engine's own package, which
absorbs the responsibility for mutexing and managing singletons.

For now the new type in the planning package just has lightly-adapted
copies of existing code just to try to illustrate what concerns belong to
it and how the rest of the system ought to interact with it. There are
various FIXME/TODO comments describing how I expect this to evolve in
future commits as we continue to build out more complete planning
functionality.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-04 07:29:01 -08:00
Martin Atkins
6760b02f65 addrs: Set.All for iterating the set elements
Since an addrs.Set is really just a map with some special usage rules, this
is just a thin wrapper around maps.Values but (as with many of the other
methods on this type) avoids exposing that implementation detail in calling
code.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-04 07:29:01 -08:00
Christian Mesh
08ba66ab6e Move new engine integration point into tofu.Context (#3718)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2026-02-03 09:07:54 -05:00
Andrei Ciobanu
67d602eb17 Add -show-sensitive to the commands docs (#3720)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-02-03 14:55:22 +02:00
Collin Funk
351e38e24f Update .devcontainer.json to Go 1.25.6 (#3719)
Signed-off-by: Collin Funk <collin.funk1@gmail.com>
2026-02-03 07:28:18 -05:00
Martin Atkins
478d86d214 engine/applying: Use "apply phase" prefix in trace logs
These were previously using "applying:" as the common prefix, but I found
that confusing in practice because only the "ManagedApply" operation is
_actually_ applying changes.

Instead then we'll identify these trace logs as belonging to the apply
phase as a whole, to try to be a little clearer about what's going on.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
0e422bc546 engine/applying: Managed resource plan and apply
Here we ask the provider to produce a plan based on the finalized
configuration taken from the "desired state", and return it as a final plan
object only if the provider request succeeds and returns something that is
valid under our usual plan consistency rules.

We then ask the provider to apply that plan, and deal with whatever it
returns. The apply part of this is not so complete yet, but there's enough
here to handle the happy path where the operation succeeds and the provider
returns something valid.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
25d910c2ef engine/applying: Log the provider instance for each resource instance
This'll make it clearer which provider we're using to ask each question,
without making a human reader try to work backwards through the execution
graph data flow.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
6b5a5369a8 execgraph: Builder.ProviderInstance no redundant ProviderInstanceConfig
The statements were in the wrong order so we were adding the
ProviderInstanceConfig operation before checking whether we already added
the operations for a particular provider instance, and thus the generated
execution graph had redundant unused extra ProviderInstanceConfig ops.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
ac617719de execgraph: Remove chatty operation logs
These were useful during initial development, but the execgraph behavior
now seems to be doing well enough that these noisy logs have become more
annoying than helpful, since the useful context now comes from the logging
within engine/apply's implementation of the operations.

However, this does introduce some warning logs to draw attention to the
fact that resource instance postconditions are not implemented yet.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
9497e6eab3 eval: ApplyOracle.DesiredResourceInstance returns config value
Previously this was calling "Value", which is incorrect because that
function resolves only after we've already planned and applied the resource
instance and so this was causing a promise self-dependency error.

Instead, we need to have the configgraph package expose the configuration
value directly and then use just that as part of this result.

(We do still want to eventually unify the two codepaths that produce these
DesiredResourceInstance objects, but this commit is focused only on fixing
this bug as directly as possible.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
9b35d84dd1 engine/applying: Track grapheval/workgraph requests
There is currently a bug in the apply engine that's causing a
self-dependency error during promise resolution, which was a good reminder
that we'd not previously finished connecting all of the parts to be able
to unwind such problems into a useful error message.

Due to how the work is split between the apply engine, the evaluator, and
the execgraph package it takes some awkward back-and-forth to get all of
the needed information together into one place. This compromise aims to
do as little work as possible in the happy path and defer more expensive
analysis until we actually know we're going to report an error message.

In this case we can't really avoid proactively collecting the request IDs
because we don't know ahead of time what (if anything) will be involved in
a promise error, but only when actually generating an error message will
we dig into the original source execution graph to find out what each of
the affected requests was actually trying to do and construct a
human-friendly summary of each one.

This was a bit of a side-quest relative to the current goal of just getting
things basically working with a simple configuration, but this is useful
in figuring out what's going on with the current bug (which will be fixed
in a future commit) and will probably be useful when dealing with future
bugs too.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
c1690a0ccf exec: ResourceInstanceObject represents an object and its address
During execution graph processing we need to carry addressing information
along with objects so that we can model changes of address as data flow
rather than as modifications to global mutable state.

In previous commits we arranged for other relevant types to track this
information, but the representation of a resource instance object was not
included because that was a messier change to wire in. This commit deals
with that mess: it introduces exec.ResourceInstanceObject to associate a
resource instance object with addressing information, and then adopts that
as the canonical representation of a "resource instance object result"
throughout all of the execution graph operations.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Martin Atkins
2af3eff7b5 execgraph: A more complete set of supported opcodes
The first pass of this was mainly just to illustrate how the overall model
of execution graphs might work, using just a few "obvious" opcodes as a
placeholder for the full set.

Now that we're making some progress towards getting this to actually work
as part of a real apply phase this reworks the set of opcodes in a few
different ways:

- Some concepts that were previously handled as special cases in the
  execution graph executor are now just regular operations. The general
  idea here is that anything that is fallible and/or has externally-visible
  side-effects should be modelled as an operation.
- The set of supported operations for managed resources should now be
  complete enough to describe all of the different series of steps that
  result from different kinds of plan. In particular, this now has the
  building blocks needed to implement a "create then destroy" replace
  operation, which includes the step of "deposing" the original object
  until we've actually destroyed it.
- The new package "exec" contains vocabulary types for data flowing between
  operations, and an interface representing the operations themselves.
  The types in this package allow us to carry addressing information along
  with objects that would not normally carry that directly, which means
  we don't need to propagate that address information around the execution
  graph through side-channels.

  (A notable gap as of this commit is that resource instance objects don't
  have address information yet. That'll follow in a subsequent commit just
  because it requires some further rejiggering.)
- The interface implemented by the applying engine and provided to the
  execution graph now more directly relates to the operations described
  in the execution graph, so that it's considerably easier to follow how
  the graph built by the planning phase corresponds to calls into that
  interface in the applying phase.
- We're starting to consider OpenTofu's own tracking identifiers for
  resource instances as separate from how providers refer to their resource
  types, just so that the opinions about how those two related can be
  more centralized in future, vs. the traditional runtime where the rules
  about how those related tend to be spread haphazardly throughout the
  codebase, and in particular implementing migration between resource
  types was traditionally challenging because it creates a situation where
  the desired resource type and the current resource type _intentionally_
  differ.

This is a large commit because the execution graph concepts are
cross-cutting between the plan and apply engines, but outside of execgraph
the changes here are mainly focused on just making things compile again
with as little change as possible, and then subsequent commits will get
the other packages into a better working shape again.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-02-02 10:27:07 -08:00
Larry Bordowitz
5f8ce97dcd azurerm: add client ID to MSI auth
Signed-off-by: Larry Bordowitz <laurence.bordowitz@gmail.com>
2026-01-28 11:22:18 -06:00
Andrei Ciobanu
d24b85fc68 Typo fix in the pg backend. Fix for #3700 (#3704)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-01-27 13:22:40 +02:00
Andrei Ciobanu
8e808f3cc2 Make it clear for the external key providers when it needs to generate decryption key (#3672)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-01-27 12:20:23 +02:00
Andrei Ciobanu
c7f46c713a Enhance the documentation and the integration tests for the pg backend (#3700)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-01-27 10:17:07 +02:00
Andrei Ciobanu
d088667ed7 Fix a hard to catch race condition in providers installation process (#3702)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2026-01-23 15:34:25 +02:00
James Humphries
c4f49afc10 Use explicit NoKey check instead of assuming nil in v3 upgrade (#3696)
Signed-off-by: James Humphries <james@james-humphries.co.uk>
2026-01-20 14:25:40 -05:00
Christian Mesh
6280691025 Support dual output streams in most commands (#3606)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
Co-authored-by: Diógenes Fernandes <diofeher@gmail.com>
2026-01-20 14:08:10 -05:00
Diógenes Fernandes
0a631be1d6 Provider validation for enabled at module calls (#3680)
Signed-off-by: Diogenes Fernandes <diofeher@gmail.com>
2026-01-20 13:06:22 -03:00
Martin Atkins
9ddd3c1128 Remove last remnants of ".go-version" file
Before Go 1.21 we relied on third-party and custom tooling to manage which
version of Go we were using for development, testing, and release builds.

However, modern Go toolchains have built-in support for selecting an
appropriate toolchain based on metadata in the go.mod file, and so we had
previously removed most uses of the .go-version file and were maintaining
it just as a remnant of the old state of things.

This replaces our last remaining use of the ".go-version" file with a small
tool that extracts the Go version from the go.mod file, and then removes
the ".go-version" file completely. The "go" and "toolchain" directives in
go.mod are now our single source of truth about which version of Go we're
currently using for OpenTofu.

(It may be possible to rework the Dockerfile for the consul backend to
handle Go version selection in a different way so that we'd no longer need
even this "selected-go-version" tool, but that's beyond the scope of this
commit which aims to leave it unmodified to make sure the effective testing
behavior for that backend is unchanged for now.)

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-01-20 07:50:18 -05:00
Denis O
9dbb5fcb9c Error checking simplifications (#3694)
Signed-off-by: Denis O <denis.o@linux.com>
2026-01-20 09:27:14 +00:00
Martin Atkins
f5d5cdf166 go.mod: Upgrade to Go 1.25.6
This minor revision includes a fix of a problem where a crafted zip file
can cause denial of service by taking a very long time to open the first
file in the archive, which can potentially be triggered by a
maliciously-crafted provider or module distribution archive fetched during
"tofu init".

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-01-19 08:41:51 -08:00
yy
4a86a1233c fix: typos in comments (#3691)
Signed-off-by: Yohei Yamamoto <yhymmt123@gmail.com>
2026-01-19 13:46:12 +00:00
Martin Atkins
2e09323694 engine/applying: First somewhat-working implementation
This is just enough to connect all of the apply engine parts together into
a functional whole which can at least attempt to apply a plan. There are
various gaps here that make this not fully work in practice, but the goal
of this commit is to just get all of the components connected together in
their current form and then subsequent commits will gradually build on this
to make it work better.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-01-16 09:50:36 -08:00