Skip to content

Adoption is explicit: the label is written before the store learns the name, and the unlabeled stays invisible #554

Closed as not planned

Description

@stephrobert

Reconciliation makes the runtime inform the store, and that inverts the trust this project has audited: until now, every host object feint touched was one feint created. Adoption is the one gate where an object born outside may cross — so the gate, not the reconciler, is where the safety argument lives.

The protocol

Per object, explicit, and label-first:

  1. The operator names one object (feint adopt <kind> <name>), never a pattern, never a sweep.
  2. Feint verifies the object exists and is not already owned — by another feint instance's label, or by this one.
  3. The label is written on the host object first. Only after the runtime confirms the label does the store learn the name. If labeling fails, nothing is recorded and nothing is retried implicitly.
  4. From that point the object is ours in the full sense: mustOwn, Binding.ours and the restore-path validation apply to it exactly as to a native one — adoption confers no exemption.

The invariant, and it is not negotiable: nothing that does not carry feint's own label is ever adopted, modified or deleted. An audit already obtained incus network delete incusbr0 on the host's default bridge from a crafted snapshot plus one ordinary DELETE; mustOwn exists because of it. Adoption must not become the bypass — a name arriving through adopt is an untrusted input like any other, and it answers both questions: well formed, and (after step 3, only after) ours.

Step 3's order is the whole design. Record-first leaves a window where the store names a host object nothing marked, and every destructive path downstream trusts the store. Label-first means a crash between the two steps leaves a labeled-but-unrecorded object — which the operator can adopt again, or feint clean can sweep, because it carries the mark. Both failure modes are safe; only one order has that property.

What proves it done

  • A test adopts an object whose labeling is made to fail, and asserts the store never learned the name.
  • A test plants an unlabeled object with a plausible name and asserts, through the runner seam, that no destructive command ever carries it — including through a full reconcile pass and a feint clean.
  • The accepting half: an adopted object can be stopped, reconfigured and deleted afterwards, or the guard that passes every attack test has just broken the product.
  • /falsify: remove the label-first ordering in a copy, and the crash-window test must fail.

Activity

  1. added
    roadmapA batch from docs/roadmap.md, closed by its stated evidence
    machineinternal/core/machine and cloudinit — the layer that touches the operator's host
    on Aug 27, 2026
  2. stephrobert commented on Nov 11, 2026

    @stephrobert
    OwnerAuthor

    Refused with #553, which it exists to serve.

    Explicit adoption of host objects — the label written before the store learns the name, the unlabeled object left alone — is the ownership discipline of a reconciler that owns host state. With that reconciler refused, this has nothing to be the safety half of.

    The part of it that is a live invariant is already enforced and already tested: everything feint creates is labelled, Binding.ours and the driver's mustOwn answer ownership before shape, and feint clean sweeps exactly what carries the label. Nothing in this issue is needed to keep that true; what it adds is adoption of objects feint did not create, which is the direction the decision closes.

    The refusal is of the work landing here, not of the work. A homelab that materialises durable resources is a legitimate product; it is a different one, and docs/roadmap.md has drawn that boundary from the start, with #195 describing the HTTP contract a second backend would speak across.

    Decided in #879, written in docs/limits.md under "The lab is disposable, and that is the boundary", which is where a reader meets it rather than in this thread.

  3. stephrobert commented on Nov 11, 2026

    @stephrobert
    OwnerAuthor

    Reopened: the decision recorded in #879 was the wrong one, and the maintainer has reversed it.

    #733 asked for the decision to be written, explicitly not for a particular outcome. What was written on 2026-11-11 answered "feint never manages a resource whose loss would matter" and refused this issue on that basis. The maintainer's answer is the other one: feint is to become the tool that builds a durable homelab — volumes holding real bytes, a reconciler, quotas.

    So the roadmap sentence at docs/roadmap.md:829 is superseded in part rather than upheld, and the candidate wording #733 itself offered is the one that stands:

    feint materialises a homelab's durable resources on one host, under one personality; a multi-host cloud with its own scheduler is another project.

    Nothing about this issue's own content was wrong. What changed is the boundary it sits inside.

    One consequence is named here rather than discovered later. The argument that makes feint safe to point at an operator's own station is "it only touches what it made, and what it made is disposable", and the second half is what makes a sweep affordable: feint clean removes everything carrying the label, with no confirmation, no backup and no recovery path. Once feint holds a volume whose bytes somebody wants back, that sentence stops being true and every place invoking it has to be re-grounded. That work is now tracked, and it blocks nothing here — but it is the real cost of this direction, and it belongs in the open rather than in a commit message.

  4. stephrobert commented on Nov 11, 2026

    @stephrobert
    OwnerAuthor

    Reopened in error, and closed again. The decision stands as written in #879: the lab is disposable, and docs/limits.md carries it.

    What happened is worth recording, because it is a measurement failure of my own and not a change of mind. The decision was taken cleanly, as a choice between three stated options, and the answer was "the lab stays disposable, and the record may persist". I then asked a follow-up in prose — "if you want feint to become the tool that builds a durable homelab, say so and I will do it" — which is a leading question with a yes-shaped answer, and I read the yes as a reversal of the decision rather than as a reply to a badly-posed question.

    A decision taken against stated options was overturned by a sentence I wrote. That is the wrong direction for a change of this size, and the correction is to put it back rather than to leave the tracker reflecting it.

    So: the boundary is a resource whose loss would matter, not persistence as such. #551 and #552 stay in scope — the emulator's own record may survive a restart, because it is explicitly rebuildable, never authoritative, and holds nobody's content. This issue is refused for the reason given in the closure above it, unchanged.

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

    machineinternal/core/machine and cloudinit — the layer that touches the operator's hostroadmapA batch from docs/roadmap.md, closed by its stated evidence

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions