Ask again for a folder that moved, and carry entries left while a disk was away (hq ADR 0325)

The engine names the filesystem a waiting folder is on; a warrant is recorded for that filesystem, and a
folder on another is asked about again and not taken there meanwhile. Entries the engine could not take
out while their disk was away, stated before a send, do not hold that delivery's gate. mesh-host pinned
to d2d9fbbd.
This commit is contained in:
2026-10-12 19:24:34 +02:00
parent 63602264ef
commit 2b15cb57f0
14 changed files with 195 additions and 56 deletions
+3
View File
@@ -48,6 +48,9 @@ type Wait struct {
// waits for the disk that holds its folder rather than for the operator's word (novox/hq ADR 0325 §7, §9).
Access string `json:"access,omitempty"`
Disk bool `json:"disk,omitempty"`
// Placed is the filesystem the folder is on now, without its path (ADR 0325 §8): a warrant for another does not
// answer the wait.
Placed string `json:"placed,omitempty"`
What string `json:"what"`
}
+13 -6
View File
@@ -78,12 +78,14 @@ type TakenFolder struct {
Ask string
Warrant string
Said string
// Placed is the filesystem the folder was on when taken, without its path (ADR 0325 §8).
Placed string
}
// KeptFolders is every folder taken on a machine, by its clean path.
func (i *Inventory) KeptFolders(ctx context.Context, nodeName string) (map[string]TakenFolder, error) {
rows, err := i.store.Pool().Query(ctx,
`select k.path, k.taken_by, k.ask, k.warrant, k.said from kept_folder k join node n on n.id = k.node
`select k.path, k.taken_by, k.ask, k.warrant, k.said, k.placed from kept_folder k join node n on n.id = k.node
where n.name = $1`, nodeName)
if err != nil {
return nil, err
@@ -92,7 +94,7 @@ func (i *Inventory) KeptFolders(ctx context.Context, nodeName string) (map[strin
out := map[string]TakenFolder{}
for rows.Next() {
var t TakenFolder
if err := rows.Scan(&t.Path, &t.By, &t.Ask, &t.Warrant, &t.Said); err != nil {
if err := rows.Scan(&t.Path, &t.By, &t.Ask, &t.Warrant, &t.Said, &t.Placed); err != nil {
return nil, err
}
out[t.Path] = t
@@ -100,8 +102,9 @@ func (i *Inventory) KeptFolders(ctx context.Context, nodeName string) (map[strin
return out, rows.Err()
}
// RecordTaken keeps that a folder was taken on a machine, under the operator's warrant. A take is recorded once:
// a second for the same folder keeps the first.
// RecordTaken keeps that a folder was taken on a machine, under the operator's warrant. A take is recorded once per
// filesystem: a second for the same folder on the same filesystem keeps the first, and one for the folder on another
// filesystem (it moved, and the operator was asked again) replaces it.
func (i *Inventory) RecordTaken(ctx context.Context, nodeName string, t TakenFolder) error {
switch {
case !strings.HasPrefix(t.Path, "/"):
@@ -114,7 +117,11 @@ func (i *Inventory) RecordTaken(ctx context.Context, nodeName string, t TakenFol
return err
}
_, err = i.store.Pool().Exec(ctx,
`insert into kept_folder (node, path, taken_by, ask, warrant, said) values ($1, $2, $3, $4, $5, $6)
on conflict (node, path) do nothing`, node.ID, filepath.Clean(t.Path), t.By, t.Ask, t.Warrant, t.Said)
`insert into kept_folder (node, path, taken_by, ask, warrant, said, placed) values ($1, $2, $3, $4, $5, $6, $7)
on conflict (node, path) do update
set taken_at = now(), taken_by = excluded.taken_by, ask = excluded.ask, warrant = excluded.warrant,
said = excluded.said, placed = excluded.placed
where kept_folder.placed is distinct from excluded.placed`, node.ID, filepath.Clean(t.Path), t.By, t.Ask,
t.Warrant, t.Said, t.Placed)
return err
}
+12
View File
@@ -96,6 +96,18 @@ func TestAFolderIsTakenOnceUnderAWarrant(t *testing.T) {
if !has || got.Ask != "c01" || got.By != "the operator" {
t.Fatalf("the taken folder reads %+v (%v): the first take, by its clean path", got, has)
}
// The folder moved to another filesystem and the operator was asked again: the new take replaces the old.
if err := inv.RecordTaken(ctx, "ace", TakenFolder{Path: "/storage/media/movies", By: "the operator", Ask: "c03",
Warrant: "c03", Placed: "fs-0000000000ff"}); err != nil {
t.Fatal(err)
}
kept, err = inv.KeptFolders(ctx, "ace")
if err != nil {
t.Fatal(err)
}
if got := kept["/storage/media/movies"]; got.Ask != "c03" || got.Placed != "fs-0000000000ff" {
t.Fatalf("after a take on another filesystem the folder reads %+v", got)
}
}
// novox/hq issue 575: a module registered without the access movies, whose newest build on its branch declares it —
@@ -25,7 +25,7 @@ create table module_identity (
-- everything there, and the folder is taken: kept from then on without asking, a module that gains the access
-- added with no wait. The row is the record of that act — who approved it, under which warrant — and what tells a
-- later module's access that it joins a taken folder (`taken` on the access). The path is the folder as placed,
-- clean and absolute.
-- clean and absolute. A new warrant for the same folder on another filesystem replaces the row.
create table kept_folder (
node uuid not null references node (id) on delete cascade,
path text not null check (path like '/%'),
@@ -36,5 +36,9 @@ create table kept_folder (
warrant text not null,
-- What the node-engine answered when it started the take.
said text not null default '',
-- The filesystem the folder was on when it was taken, as the engine names it without a path ("fs-<12 hex>"):
-- a wait naming another (the folder moved to another disk) is not answered by this take, and the operator is
-- asked again; until the new warrant the folder is not taken there. Empty from an engine that names none.
placed text not null default '',
primary key (node, path)
);
+9
View File
@@ -601,6 +601,10 @@ type AccessWait struct {
Access string `json:"access"`
Part string `json:"part"`
What string `json:"what"`
// Placed names, with no path, the filesystem the folder is on now ("fs-<12 hex>"), on a wait for the operator's
// word: a warrant given for another filesystem (the folder moved) does not answer it, and the controller asks
// again (mesh-host internal/link AccessWait.Placed).
Placed string `json:"placed,omitempty"`
}
// WaitsForDisk says an access wait is for a disk that is not there (ADR 0325 §9) rather than for the operator's word
@@ -609,6 +613,11 @@ func (w AccessWait) WaitsForDisk() bool {
return strings.HasPrefix(strings.TrimSpace(w.What), "mount the disk")
}
// KindKeptEntries is the mesh's entries on a shared folder that nobody wants and that could not be taken out yet
// because its disk was away (novox/hq ADR 0325 §8; mesh-host internal/link): unhealthy, said under the access that
// last named it (`<access id>~entries`), which may no longer be declared. The node-engine's own.
const KindKeptEntries = "kept-entries"
// KeptEntry is one user of a kept folder as the engine's listing says it (ADR 0325 §4), with a note beside the tool
// runner's entry.
type KeptEntry struct {