Each node has an object bucket of its own for files by digest (issue 1694, ADR 0486)

This commit is contained in:
2026-10-21 15:39:38 +02:00
parent 85b5a2687e
commit 36d9eba51d
3 changed files with 49 additions and 5 deletions
+1 -1
View File
@@ -326,7 +326,7 @@ type File struct {
// Digest and Size say the content is not carried but fetched: a file over FileReferenceThreshold travels as
// the sha256 digest of its content, "sha256:<64 hex>", and its size in bytes, in place of Content or Bytes
// (novox/hq ADR 0486 §1). The node-engine fetches the object of that name from FileBucket, checks what it
// (novox/hq ADR 0486 §1). The node-engine fetches the object of that name from FileBucketFor(node), checks what it
// received against Digest and Size, and only then writes the file. Exclusive with Content, Sealed, Bytes,
// VaultSealed and Secrets; sent only to an engine that reports FileReferencesCapability, as an older engine
// refuses a field it does not know, whole.
+27 -3
View File
@@ -14,13 +14,37 @@ const (
// FileReferenceThreshold is the size above which a file travels by digest: a file of this many bytes or fewer
// stays in the declaration as it is.
FileReferenceThreshold = 64 * 1024
// FileBucket is the object bucket of the bus that holds the contents, each under the hex of its sha256 digest
// (ObjectName). The controller alone writes it; a node-engine reads an object by name.
FileBucket = "declaration-files"
// FileBucketPrefix starts the name of a node's object bucket, FileBucketFor(node), which holds the contents of the
// files of that node's declaration, each under the hex of its sha256 digest (ObjectName). One bucket per node,
// so a grant names it and a literal `*` is never needed (ADR 0486 §2). The controller alone writes it; the
// node's engine reads an object by name.
FileBucketPrefix = "declaration-files-"
// FileStreamPrefix starts the name of the stream behind such a bucket, which the bus's grants name.
FileStreamPrefix = "OBJ_"
// FileReferencesCapability is what a node-engine reports when it reads a file given as a digest and size.
FileReferencesCapability = "file-references"
)
var nodeToken = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
// FileBucketFor is the name of the object bucket of a node's files. The node name must be a name the bus accepts in a
// bucket and in a subject token: letters, digits, '_' and '-', so no '.', '*', '>' or space.
func FileBucketFor(node string) (string, error) {
if !nodeToken.MatchString(node) {
return "", fmt.Errorf("node %q; a node's bucket is named by letters, digits, '_' and '-' only", node)
}
return FileBucketPrefix + node, nil
}
// FileStreamFor is the name of the stream behind a node's bucket, "OBJ_" and the bucket's name.
func FileStreamFor(node string) (string, error) {
b, err := FileBucketFor(node)
if err != nil {
return "", err
}
return FileStreamPrefix + b, nil
}
var digestForm = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
// DigestOf is the digest a reference names for a content: "sha256:<64 lower-case hex>".
+21 -1
View File
@@ -127,10 +127,30 @@ func TestWhichFilesTravelByDigest(t *testing.T) {
}
func TestTheSharedNames(t *testing.T) {
if FileBucket != "declaration-files" || FileReferencesCapability != "file-references" || FileReferenceThreshold != 65536 {
if FileBucketPrefix != "declaration-files-" || FileReferencesCapability != "file-references" || FileReferenceThreshold != 65536 {
t.Fatal("a name changed")
}
if _, err := ObjectName("sha256:zz"); err == nil {
t.Fatal("bad digest named an object")
}
}
// 1694/AC6: each node has a bucket of its own, so a grant names it and never needs a `*` (ADR 0486 §2).
func TestEachNodeHasABucketOfItsOwn(t *testing.T) {
b, err := FileBucketFor("g14")
if err != nil || b != "declaration-files-g14" {
t.Fatalf("%q %v", b, err)
}
s, err := FileStreamFor("g14")
if err != nil || s != "OBJ_declaration-files-g14" {
t.Fatalf("%q %v", s, err)
}
for _, bad := range []string{"", "a.b", "a*", "*", ">", "a b", "a>", "a/b", "ä"} {
if _, err := FileBucketFor(bad); err == nil {
t.Errorf("bucket for %q accepted", bad)
}
if _, err := FileStreamFor(bad); err == nil {
t.Errorf("stream for %q accepted", bad)
}
}
}