package main import ( "context" "fmt" ) // The things the mesh can be asked to do, separated from how it was asked. // // **A surface is an adapter with no decisions in it** (novox/hq ADR 0035). The command line and // the command API call the same functions here, so an assignment refused at one is refused at the // other for the same reason and in the same words. The moment a surface can accept something // another would reject, the mesh has two answers to one question and people learn which to trust. // // Each returns what happened as text a person can read and a caller can pass on. Neither surface // composes its own explanation, because two explanations of one refusal drift. // assign puts a module on a node, and says at once whether the whole set still resolves. // // The assignment is kept even when it does not: it is what a person meant, and the refusal is // about the set rather than about this one. That is a decision, so it lives here rather than in // whichever surface asked. func assign(ctx context.Context, open *stores, node, module string) (string, error) { if err := open.inventory.Assign(ctx, node, module); err != nil { return "", err } said := fmt.Sprintf("%s is assigned %s", node, module) if _, _, err := planFor(ctx, open, node); err != nil { // Kept, and still refused. Both halves are the answer. return said, err } return said + fmt.Sprintf("\n run `push %s` to send it", node), nil } // unassign takes a module off a node. What it leaves behind is the host's business: a directory // holding anything the mesh did not put there is kept (novox/hq ADR 0030). func unassign(ctx context.Context, open *stores, node, module string) (string, error) { if err := open.inventory.Unassign(ctx, node, module); err != nil { return "", err } return fmt.Sprintf("%s no longer runs %s — run `push %s` to make it so", node, module, node), nil }