Let the tree readers decode, give the judge the manifest's data it reads, and stop judging outside the catalogue (ADR 0578 §3, §4)
Issue 1585, S6-S8: ReadTreeManifestWritten and WithTemplatesFrom decode (the judge in mesh-delivery judges what they answer); Manifest.Predefined, SetPredefined and FormsWritten expose what the moved judge reads; buscompose names its removals without the catalogue's allow-list; tests outside the catalogue decode, two tree-judging tests moved. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -33,7 +33,7 @@ const KeptBucket = "mesh-controller_states-announced"
|
||||
var name = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`)
|
||||
|
||||
// Key is module's key in KeptBucket: the module itself, which must be one plain name, since a module's name is part of
|
||||
// its buckets' names (catalogue.StateProblems).
|
||||
// its buckets' names (the indexer's judge, novox/hq ADR 0578 §1).
|
||||
func Key(module string) (string, error) {
|
||||
if !name.MatchString(module) {
|
||||
return "", fmt.Errorf("announce: %q is not a module that keeps state: lower-case letters, digits and hyphens", module)
|
||||
|
||||
@@ -75,20 +75,17 @@ func TestModuleTreesFailsNamingANamedPathAbsentAtTheCommit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A manifest whose config value falls back to a predefined key is narrowed here as the indexer narrows it: both read it
|
||||
// with catalogue.ParseManifestForInputs (issue 1751), so a build module holding no published list computes the same
|
||||
// tree (ADR 0499 clause 2).
|
||||
// A manifest whose config value falls back to a predefined key is narrowed here: the merge preview decodes it and judges
|
||||
// nothing (novox/hq ADR 0578 §3), so a build module holding no published list computes the tree the indexer does (ADR
|
||||
// 0499 clause 2; issue 1751).
|
||||
func TestModuleTreesNarrowsAManifestFallingBackToAPredefinedKey(t *testing.T) {
|
||||
was := catalogue.ReplacePublished(nil) // the build modules' case: no published list
|
||||
defer catalogue.ReplacePublished(was)
|
||||
root := t.TempDir()
|
||||
raw := `{"module":"xterm","version":"1","config":{"size":{"type":"number","description":"The text size.","predefined":"font-size","default":12,"runtime":true}},` +
|
||||
`"build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript","paths":["src"]}]}}`
|
||||
if _, err := catalogue.ParseManifest([]byte(raw)); err == nil {
|
||||
t.Fatal("ParseManifest reads the manifest with no list; the test proves nothing")
|
||||
}
|
||||
if _, err := catalogue.ParseManifestForInputs([]byte(raw)); err != nil {
|
||||
t.Fatalf("the indexer's read refuses the manifest: %v", err)
|
||||
if _, err := catalogue.Decode([]byte(raw)); err != nil {
|
||||
t.Fatalf("the preview's read refuses the manifest: %v", err)
|
||||
}
|
||||
writeTreeFile(t, root, "module.json", raw)
|
||||
run := func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||
|
||||
@@ -42,7 +42,7 @@ var (
|
||||
// write's step, clause 5); the node-engine's acts bucket read (STREAM.INFO with a subject filter lists what a removal
|
||||
// would take, so the tools read before they remove) and its keys removed, which nothing else may empty when it fills; the ASSIGNMENTS stream read and deleted whole (the lease
|
||||
// write, research 072 row 33). A
|
||||
// removal outside the set is refused, as the catalogue refuses it (catalogue.BusRemovals).
|
||||
// removal outside the set is refused, as the indexer's judge refuses it in a manifest (novox/hq ADR 0578 §1).
|
||||
func BusRemovalGrants(removes []string) ([]string, error) {
|
||||
var out []string
|
||||
for _, r := range removes {
|
||||
@@ -56,7 +56,8 @@ func BusRemovalGrants(removes []string) ([]string, error) {
|
||||
out = append(out, "$JS.API.STREAM.INFO."+AssignmentsStream, "$JS.API.STREAM.DELETE."+AssignmentsStream)
|
||||
default:
|
||||
return nil, fmt.Errorf("a bus account removes %q, which is not one of %s (novox/hq ADR 0564 Limits 4)", r,
|
||||
strings.Join(catalogue.BusRemovals, ", "))
|
||||
strings.Join([]string{catalogue.RemovesControllerDeadLetterConsumer, catalogue.RemovesEngineActs,
|
||||
catalogue.RemovesAssignmentsStream}, ", "))
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
|
||||
@@ -108,7 +108,7 @@ func (s ModuleStream) Stream() Stream {
|
||||
|
||||
// QueueOwner is the one process of a module that is the durable consumer of one of its own work-queue
|
||||
// streams (novox/hq ADR 0436 §1, §2, §6): named in that process's bus.queues, and checked at registration
|
||||
// to be the only one (catalogue.WorkQueueStreamProblems).
|
||||
// to be the only one (the indexer's judge, novox/hq ADR 0578 §1).
|
||||
type QueueOwner struct {
|
||||
// Process is the process resource's id — its bus account's name, with the node and module (novox/hq ADR
|
||||
// 0436 §2).
|
||||
|
||||
@@ -108,7 +108,7 @@ type ProcessPrincipal struct {
|
||||
Emits []string
|
||||
// Queues are the module's own work-queue streams this process is the one consumer of.
|
||||
Queues []QueueOwner
|
||||
// Removes are what its account may remove from the bus, as its bus declares them (catalogue.BusRemovals; novox/hq
|
||||
// Removes are what its account may remove from the bus, as its bus declares them (the indexer's judge refuses any other; novox/hq
|
||||
// ADR 0564 Limits 4): a tools bundle's account, whose whole authority is then BusRemovalGrants.
|
||||
Removes []string
|
||||
// State is the module's own state, by local name, this process reads and writes.
|
||||
|
||||
@@ -29,3 +29,14 @@ func (m Manifest) ReadNoFurther() bool { return strings.Contains(m.unknown, read
|
||||
|
||||
// readNoFurther marks, in what a manifest set aside, that it was not read past it.
|
||||
const readNoFurther = "(read no further: "
|
||||
|
||||
// Predefined is the list of predefined keys the manifest was read with (ReadTreeManifestWritten, or the judge's
|
||||
// SetPredefined), nil when none: data of the contract a judge reads, never a rule.
|
||||
func (m Manifest) Predefined() PredefinedList { return m.predefined }
|
||||
|
||||
// SetPredefined keeps the list of predefined keys a manifest is read with, for what reads it after (the judge).
|
||||
func (m *Manifest) SetPredefined(list PredefinedList) { m.predefined = list }
|
||||
|
||||
// FormsWritten is the fields the manifest's config was written with, in the config form (`config`, `takes-config`,
|
||||
// `${config:…}`) and in the settings form (novox/hq ADR 0348 §1): data a judge reads to say a manifest mixes them.
|
||||
func (m Manifest) FormsWritten() (config, settings []string) { return m.forms.config, m.forms.settings }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -103,32 +101,3 @@ func TestAProvidersConsumersFileIsLeftOutWhereItIsNotHanded(t *testing.T) {
|
||||
t.Fatalf("handed: said %q, filled %v", c.HandedOnly, c.Filled)
|
||||
}
|
||||
}
|
||||
|
||||
// What `module check` and the merge gate judge: the provider read from its tree, its text inline. Judged by the same
|
||||
// rules, a provider naming a provision it does not provide is refused there too, and one that does composes.
|
||||
func TestAProviderReadFromItsTreeIsJudgedByTheSameRules(t *testing.T) {
|
||||
read := func(text string) (Manifest, error) {
|
||||
dir := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(dir, "files"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "files", "consumers.json"), []byte(text), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m, err := ParseManifest([]byte(handedProvider))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return WithTemplatesFrom(m, dir)
|
||||
}
|
||||
filled, err := read(consumersText)
|
||||
if err != nil {
|
||||
t.Fatalf("a provider read from its tree is refused: %v", err)
|
||||
}
|
||||
if _, err := notifierOn(t, filled).Compose(handingTo("store:5000")); err != nil {
|
||||
t.Fatalf("a provider read from its tree does not compose where it would be handed: %v", err)
|
||||
}
|
||||
if _, err := read("${consumers:cache}"); err == nil || !strings.Contains(err.Error(), "does not provide") {
|
||||
t.Fatalf("a provider read from its tree naming a provision it does not provide: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +236,9 @@ func ReplacePublished(list PredefinedList) (was PredefinedList) {
|
||||
}
|
||||
|
||||
// ReadTreeManifestWritten is the manifest in a module's directory of a repository tree as written, still naming its
|
||||
// templates, judged against the list of predefined keys at the tree's root as the merge gate judges it
|
||||
// (cmd/mesh-controller). What a build resolves, given the text it reads back from the archive it packs the templates
|
||||
// into (Built.Texts, ReadTemplates).
|
||||
// templates, decoded and judged by nothing (novox/hq ADR 0578 §3: the indexer's judge judges manifests), the tree's
|
||||
// list of predefined keys read so a tree whose list does not read is still refused. What a build resolves, given the
|
||||
// text it reads back from the archive it packs the templates into (Built.Texts, ReadTemplates).
|
||||
func ReadTreeManifestWritten(root, dir string) (Manifest, error) {
|
||||
list, err := ReadPredefined(root)
|
||||
if err != nil {
|
||||
@@ -248,7 +248,12 @@ func ReadTreeManifestWritten(root, dir string) (Manifest, error) {
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
return ParseManifestWith(raw, list)
|
||||
m, err := Decode(raw)
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
m.predefined = list
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ReadTreeManifest is ReadTreeManifestWritten with each template's text read from the module's directory as the
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -181,33 +179,3 @@ func TestAnOwnersKeyIsTheTerminals(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A key asked only in a template is not refused while the manifest still names its templates (issue 817), and
|
||||
// is judged once they are filled: WithTemplatesFrom over a tree whose template does not ask for it refuses it.
|
||||
func TestAnOwnerSettingAskedOnlyInATemplateIsJudgedOnceFilled(t *testing.T) {
|
||||
raw := `{"module":"sonarr","version":"1",
|
||||
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
|
||||
"resources":[{"id":"recycle-bin","type":"directory","path":"/srv/sonarr/recycle","mode":"0755","owner":"${setting:puid}:${setting:pgid}"},
|
||||
{"id":"identity","type":"file","path":"/srv/sonarr/identity.env","mode":"0644","template":"files/identity.env"}]}`
|
||||
m, err := ParseManifest([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("a templated manifest was refused: %v", err)
|
||||
}
|
||||
write := func(text string) string {
|
||||
dir := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(dir, "files"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "files", "identity.env"), []byte(text), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
if _, err := WithTemplatesFrom(m, write("PUID=${setting:puid}\nPGID=${setting:pgid}\n")); err != nil {
|
||||
t.Errorf("a template asking for both keys was refused: %v", err)
|
||||
}
|
||||
_, err = WithTemplatesFrom(m, write("NOTHING=1\n"))
|
||||
if err == nil || !strings.Contains(err.Error(), "asks for \"puid\" nowhere else") {
|
||||
t.Errorf("a template not asking for puid was not refused: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,10 +422,12 @@ func WithTemplatesFrom(m Manifest, dir string) (Manifest, error) {
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
again, err := parseManifest(raw, m.predefined)
|
||||
// Decoded again, never judged (novox/hq ADR 0578 §3): the judge judges what this answers where it judges.
|
||||
again, err := Decode(raw)
|
||||
if err != nil {
|
||||
return Manifest{}, err
|
||||
}
|
||||
again.predefined = m.predefined
|
||||
again.Moved = m.Moved
|
||||
return again, nil
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestTheCommandAnswersWhatTheLinkedCompositionAnswers(t *testing.T) {
|
||||
in.Catalogue = map[string]catalogue.Manifest{}
|
||||
for name, m := range r.Cases[0].Manifests {
|
||||
// A request without a list of predefined keys reads a fallback as the indexer does (Request.Predefined).
|
||||
parsed, err := catalogue.ParseManifestForInputs(m.Raw)
|
||||
parsed, err := catalogue.Decode(m.Raw)
|
||||
got := v.Cases[0].Manifests[name]
|
||||
if (err == nil) != got.Accepted || (err != nil && err.Error() != got.Refused) {
|
||||
t.Errorf("%s: the manifest of %s: linked %v, command %+v", depth, name, err, got)
|
||||
|
||||
@@ -282,7 +282,7 @@ func answerNamesEveryRefusalAndLeavesAnOldBuildToTheNode(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f1, err := catalogue.ParseManifest(f1raw)
|
||||
f1, err := catalogue.Decode(f1raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func TestTheInputTravelsAsJSON(t *testing.T) {
|
||||
|
||||
// The seats a node's assigned modules claim are held there when the request says the holdings come from the claims.
|
||||
func TestHoldingsComeFromTheClaims(t *testing.T) {
|
||||
m, err := catalogue.ParseManifest([]byte(demo))
|
||||
m, err := catalogue.Decode([]byte(demo))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ func TestThePrivateNetworksClaimIsASeatTheMeshDefines(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := catalogue.ParseManifest(raw); err != nil {
|
||||
t.Errorf("%s, composed by the control plane, is refused: %v", composed["module"], err)
|
||||
// Decoded here; the indexer's judge judges it (mesh-delivery's judge tests hold it to every rule).
|
||||
if _, err := catalogue.Decode(raw); err != nil {
|
||||
t.Errorf("%s, composed by the control plane, does not decode: %v", composed["module"], err)
|
||||
}
|
||||
}
|
||||
seat, defined := catalogue.SeatNamed(TheNetwork)
|
||||
|
||||
@@ -109,10 +109,6 @@ func TestAProvisionIsAReferenceUnderItsConsumersKey(t *testing.T) {
|
||||
if got := DependsOn(keycloak, vaultEntry, nil); len(got) != 0 {
|
||||
t.Errorf("the vault's secret depends on %v", got)
|
||||
}
|
||||
if _, err := catalogue.ParseManifest([]byte(`{"module":"x","version":"1","provides":[{"name":"secret-db","scope":"mesh"}]}`)); err == nil ||
|
||||
!strings.Contains(err.Error(), "secret-") {
|
||||
t.Errorf("a provision named secret-*: %v", err)
|
||||
}
|
||||
if ProvisionsBucket != "mesh-provisioner_provisions" || PlansRead != "mesh-provisioner.plans" {
|
||||
t.Errorf("the states are %s and %s", ProvisionsBucket, PlansRead)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user