Skip to content

design: a choudoufu estate member — ownership as two tags, drift from live-plan, estate slicing as the same cut the carve lens draws #366

Description

@lex00

Design and tracking, in the shape of #230 and #234: what behold would show for a choudoufu estate, what it would read to show it, how choudoufu's carve relates to the chant carve lens behold already draws, and where the line stays. Nothing here starts a build; it records the assessment so the premise an earlier comparison note carried ("behold support is premised, not real") has a home now that the choudoufu half of it shipped.

What choudoufu is, for a renderer

An OpenTofu fork, AWS only, where ownership lives on the resource as two tags, tofu-estate and tofu-address (continuation tags for long addresses, tofu-slot for count). live/MARKERS.md is the one integration surface external tools may rely on. The state file is a cache that is allowed to be stale; values with no cloud home go to a record store, effects go to receipts. A cross-estate reference is an ordinary data source filtered on the producer's marker tags (live/OUTPUTS.md). Ownership moves by rewriting the estate tag: live-mv -from-estate.

Since v0.12.0 (2026-09-04) the four reads a renderer needs exist, filed from this assessment and shipped the same day:

read verb what behold would take from it
the live half live-ls -estate <e> -json [-consistent] [DIR] (choudoufu#789) every resource under the estate tag, ARN, type, decoded address, raw markers; with DIR, the declared instances the listing cannot see, named by rung (record, declaration-carried)
the declared half live-check -json (choudoufu#790) instances[] with type and rung; references[], the marker-filtered data sources, as {from, estate, address} plus the readers; no cloud call
drift live-plan -estate <e> -json (choudoufu#788) bound[] with binding source, omissions[] with reasons, unowned[] with the adopting tag write
a move live-mv -json [-dry-run] (choudoufu#791) resource, from/to, followers, a stable refusal code; request_id is in the shape but empty today

The joins, mapped to what exists

  1. The palette needs no new word. bound is good, unowned is warn, an instance in omissions because nothing carries its marker is accent, and a record or declaration-carried gap is neutral: behold's existing "did not look", which is the right claim for an instance whose identity has no tag to find. This is reclassifyOverlay's vocabulary (src/overlay.ts) fed from a different source.

  2. Estates are members. Each tofu-estate value is one box in the estate compose (src/estate.ts, graph: multi-project / multi-estate view (Argo app-of-apps) #31). live-check's references[] are the cross-member edges, stated by the tool rather than inferred; behold's value-match pass (src/value-match.ts) has the same shape and would not be needed here.

  3. The live read is one call under read-only IAM. live-ls replaces the resourcegroupstaggingapi get-resources plus per-role list-role-tags the workbench hand-rolled, with the tagging index's roughly one-minute lag handled by -consistent. behold shells the project's choudoufu the way it shells the project's chant, and holds no credentials of its own beyond the ambient read role.

  4. The declared half comes from the tool, not from HCL. behold parses no HCL (the carve lens made the advise report the contract for the same reason). live-check -json is the equivalent contract for a choudoufu estate: the roster is the node set, references[] the edge set, the rung a node attribute the inspect pane states.

  5. A move plan is a morph. The workbench's carve.json ({from, estates[], moves[{address, from, to}]}) is a set of cards changing boxes. renderCarveMorph (src/render.ts) already draws a card gliding from one box to another; a choudoufu move plan is the same picture with the ghosted destination the workbench's Preview phase draws. live-mv -json -dry-run is the per-move confirmation, and the real run's document is the receipt.

The two carves

chant's carve changes representation and owning tool: HCL becomes typed chant source, and ownership moves from a state file to chant's marker, through advise, emit, bridge and apply. choudoufu's carve changes owner only: the resource stays HCL, its block moves between directories, and one tag write moves the boundary; untaggable children follow the parent's live tag.

Structurally they are one operation on one graph: choose a cut, move nodes across it, patch every edge that crosses the cut into a data-source read. chant's advisor prices exactly that (12 points per inbound edge, each one a data-source patch). choudoufu's planner (tlmig/carve.py) fills rows by module, prefix, type or name and has no cost input; live-check's references[] now gives it one, and the advisor's arithmetic applies to it unchanged. "Leave in Terraform" reads as "leave in this estate".

The convergence worth recording: chant's AWS lexicon also puts an ownership marker on the resource as a tag. Both systems agree ownership lives on the resource. So a choudoufu resource graduating to chant is, in principle, one marker rewrite rather than the terraform state rm ceremony the carve Handoff step hands back today. That is a chant question (does carve apply accept a choudoufu-marked resource as its input), not a behold one, and it is noted here so it is not lost.

The line

behold never writes to a cloud. live-mv is a tags-only apply through the provider with its own five refusals, which makes it safer than state surgery, and it is still a cloud write. It stays a handoff: render the move plan, hand back the live-mv lines with copy buttons, read the -json document afterwards. No /api/choudoufu/mv, for the same reason there is no /api/carve/apply (AGENTS.md, "Invariant"). If the demo path ever boots a floci for this, it goes through assertScratch (src/scratch.ts) with its own name and port; the workbench's compose already publishes floci on 127.0.0.1:4650, not :4566.

The fixture

examples/live-mv-workbench in the choudoufu checkout. just up stands floci and the demo up in two containers; a run leaves runs/<id>/ with manifest.json, events.jsonl (inventory, preview, measure, verdict events), carve.json, and one working directory per estate. The demo seed applies the terralith with choudoufu directly (the live block is in the fixture HCL), so the stock-to-choudoufu migration is the adopt form (seed --config --estate --state --approve, via live-import) and not the button path. A first behold lens could read a run directory with no choudoufu call at all; the real member reads the four verbs above.

Proposed order, when something pulls it

  1. A choudoufu estate member kind: live-check -json for the roster and edges, live-ls -json for the live half, the palette join above. Comparable in size to src/helm-releases.ts or src/carve-lens.ts, with tests off recorded -json documents.
  2. live-plan -json as the per-node drift detail in the inspect pane, where /api/diff slices chant's today.
  3. The move-plan morph over carve.json, with the handoff as above.
  4. A demo entry pointing at the workbench, gated on docker and a choudoufu on PATH, if the walkthrough earns it.

What this deliberately does not decide

Whether behold shells choudoufu or reads its documents from disk; whether a choudoufu member can sit in the same estate as chant members (the tags say yes, the edge join says nothing yet); what a choudoufu estate's .behold.json would declare. Each is a build-time question against a shipped contract, and none needs a chant change.

Refs: choudoufu#788 #789 #790 #791 (all shipped v0.12.0); behold #230 (the chant carve lens this rhymes with), #234 (the operating loop's join discipline this follows).

🤖 Claude Code — https://claude.ai/code/session_01RWASk8sLh3pJAQX7TkYGuT

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions