declaration: an access is mounted, and the host owns nothing about it
The tenth shape (novox/hq ADR 0051). Shared, pre-existing data — a media library, a download spool several modules use — is the operator's, not the mesh's. A `directory` resource is the host's own: it creates it, chowns it, sets its mode and removes it when empty. An access is the opposite on every axis. Add the `access` type to the vocabulary. Its applier confirms the path is present and changes nothing: it does not create, chown, reconcile or set a mode. Absent is refused clearly — the operator must provide it — rather than created, because a bind mount whose source is missing is made as root by the container runtime with the wrong ownership (04-ISSUES/026). Undeclaring an access forgets the record and never touches the path, which is the data loss ADR 0030 prevents, on a directory the mesh never made. Full hosts speak it (it gates a bind mount, which needs the container runtime); the vocabulary guard test records the decision that made it the tenth shape. Unit tests cover present, absent-refused, and undeclared-left-alone. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -50,6 +50,15 @@ const (
|
||||
// reason it is a shape rather than an action, because an action leaves nothing the host can
|
||||
// undo and the network would outlive the module (novox/hq ADR 0029).
|
||||
TypeNetwork Type = "network"
|
||||
|
||||
// TypeAccess is a pre-existing, operator-owned path a module is granted use of but does not
|
||||
// own (novox/hq ADR 0051). The opposite of a directory on every axis the host acts on: the
|
||||
// host creates, chowns and reconciles a directory, and removes it when it is empty; it does
|
||||
// none of that to an access. It confirms the path is present — refusing clearly if the
|
||||
// operator has not provided it, rather than creating it as a bind mount source would
|
||||
// (04-ISSUES/026) — and leaves everything about it alone. Several modules declaring one
|
||||
// access is ordinary, because none of them owns it.
|
||||
TypeAccess Type = "access"
|
||||
)
|
||||
|
||||
// Resource is one thing that should be true of the machine.
|
||||
@@ -281,6 +290,48 @@ func (n *Network) validate(where string, _ bool) []string {
|
||||
return problems
|
||||
}
|
||||
|
||||
// Modes an access may be granted at. Plain words, not the octal a directory's mode is: an access
|
||||
// is not a thing the host chmods, it is a statement of how this module reaches what the operator
|
||||
// owns.
|
||||
const (
|
||||
AccessRead = "read"
|
||||
AccessReadWrite = "read-write"
|
||||
)
|
||||
|
||||
// Access is a pre-existing, operator-owned path this module is granted use of but does not own.
|
||||
//
|
||||
// **The distinction 04-ISSUES/036 and 026 turn on.** A `directory` resource is the mesh's own —
|
||||
// it creates it, sets its owner and mode, and removes it when empty ([ADR 0030](novox/hq)). A
|
||||
// media library, a download spool is the operator's: it existed before the mesh, several modules
|
||||
// read and write it at once, and the mesh must not create, chown, reconcile or remove it. The
|
||||
// host confirms it is there and mounts it; nothing else.
|
||||
type Access struct {
|
||||
ID string `json:"id"`
|
||||
Type Type `json:"type"`
|
||||
Path string `json:"path"`
|
||||
// Mode is how this module reaches the path: read or read-write. Absent narrows to read.
|
||||
Mode string `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Access) Identity() string { return a.ID }
|
||||
func (a *Access) Kind() Type { return TypeAccess }
|
||||
func (a *Access) Target() string { return a.Path }
|
||||
|
||||
func (a *Access) validate(where string, _ bool) []string {
|
||||
var problems []string
|
||||
if !strings.HasPrefix(a.Path, "/") {
|
||||
problems = append(problems, where+": an access needs an absolute path, and "+
|
||||
a.Path+" is not one")
|
||||
}
|
||||
switch a.Mode {
|
||||
case "", AccessRead, AccessReadWrite:
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: an access is %q or %q, not %q", where, AccessRead, AccessReadWrite, a.Mode))
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
func (u *User) Identity() string { return u.ID }
|
||||
func (u *User) Kind() Type { return TypeUser }
|
||||
func (u *User) Target() string { return u.Name }
|
||||
@@ -555,6 +606,8 @@ func newOf(t Type) Resource {
|
||||
return &User{}
|
||||
case TypeArchive:
|
||||
return &Archive{}
|
||||
case TypeAccess:
|
||||
return &Access{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -562,7 +615,7 @@ func newOf(t Type) Resource {
|
||||
// Vocabulary is every kind this host speaks.
|
||||
func Vocabulary() []Type {
|
||||
return []Type{
|
||||
TypeAction, TypeArchive, TypeContainer, TypeDirectory, TypeFile, TypeNetwork,
|
||||
TypeAccess, TypeAction, TypeArchive, TypeContainer, TypeDirectory, TypeFile, TypeNetwork,
|
||||
TypePackage, TypeService, TypeUser,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user