diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askagain_tool.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askagain_tool.go index 93529ea5..dcc18206 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askagain_tool.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/askagain_tool.go @@ -36,7 +36,7 @@ const ( func askAgainByHand(st store, ask func(string, any) (json.RawMessage, error), key, why, by string, now time.Time) (any, error) { switch { case key == "": - return nil, errors.New("name the ask's key, as assigner_read_asks lists it") + return nil, errors.New("name the ask's key, as the tool asks lists it") case why == "": return nil, errors.New("a hand ask again says why") case by == "" || by == stdio.Unproven: @@ -47,7 +47,7 @@ func askAgainByHand(st store, ask func(string, any) (json.RawMessage, error), ke return nil, err } if !found { - return nil, fmt.Errorf("the assigner holds no ask under %s: assigner_read_asks lists them", key) + return nil, fmt.Errorf("the assigner holds no ask under %s: the tool asks lists them", key) } _, routerAsk, err := assign.DecodeAsk(key, current) if err != nil { @@ -90,7 +90,7 @@ func askAgainTools(st store, ask func(string, any) (json.RawMessage, error), now "under key is written again under a new asking, which the teller asks on the operator channel, as when the channel says it " + "ended without an answer. Refused for an asking the operator answered, a proposal's authorising ask (the seat's verb again " + "asks it) and a record ask a put asked. Recorded as a hand act.", - Input: schema(map[string]string{"key": "the ask's key, as assigner_read_asks lists it", "why": "why it is asked again"}, + Input: schema(map[string]string{"key": "the ask's key, as the tool asks lists it", "why": "why it is asked again"}, []string{"key", "why"}), Run: func(a map[string]any) (any, error) { key, _ := a["key"].(string) diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/asksform_test.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/asksform_test.go new file mode 100644 index 00000000..67549841 --- /dev/null +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/asksform_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "encoding/json" + "testing" + + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants" +) + +// Design 47 G §717: the assigner's asks are the seat mesh-wants' state asks, so every key its asks package writes is one of +// that state's key forms, read back to the same parts, and each value is the seat's Ask. +func TestTheAssignersAsksAreTheSeatMeshWantsAsks(t *testing.T) { + const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + const fp, digest = "src2:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + must := func(k string, err error) string { + t.Helper() + if err != nil { + t.Fatal(err) + } + return k + } + three := func(parse func(string) (string, string, string, bool)) func(string) []string { + return func(k string) []string { + if a, b, c, ok := parse(k); ok { + return []string{a, b, c} + } + return nil + } + } + for _, row := range []struct { + key string + parse func(string) []string + want []string + }{ + {must(asks.HoldAskKey("web", fp)), func(k string) []string { + if a, b, ok := meshwants.ParseAsksByHoldModuleFingerprintKey(k); ok { + return []string{a, b} + } + return nil + }, []string{"web", hex}}, + {must(asks.RestoreAskKey("ace", "web", fp)), three(meshwants.ParseAsksByRestoreNodeModuleFingerprintKey), []string{"ace", "web", hex}}, + {must(asks.BackupAskKey("ace", "web", fp)), three(meshwants.ParseAsksByBackupNodeModuleFingerprintKey), []string{"ace", "web", hex}}, + {must(asks.OutsideAskKey("ace", "web", fp)), three(meshwants.ParseAsksByOutsideNodeModuleFingerprintKey), []string{"ace", "web", hex}}, + {must(asks.RevertAskKey("ace", "web", fp)), three(meshwants.ParseAsksByRevertNodeModuleFingerprintKey), []string{"ace", "web", hex}}, + {must(asks.RecordAskKey("ace", "web", digest)), three(meshwants.ParseAsksByRecordNodeModuleDigestKey), []string{"ace", "web", "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}}, + {must(asks.AssignmentsAskKey("p-20261028t183000-1a2b3c")), func(k string) []string { + if a, ok := meshwants.ParseAsksByAssignmentsProposalKey(k); ok { + return []string{a} + } + return nil + }, []string{"p-20261028t183000-1a2b3c"}}, + } { + got := row.parse(row.key) + if len(got) != len(row.want) { + t.Errorf("%s is no key form of mesh-wants' asks: read as %v", row.key, got) + continue + } + for i := range got { + if got[i] != row.want[i] { + t.Errorf("%s reads as %v, want %v", row.key, got, row.want) + break + } + } + } + key := must(asks.HoldAskKey("web", fp)) + raw, err := asks.EncodeAsk(key, asks.Ask{Ask: json.RawMessage(`{"id":"x"}`), Asking: 2}) + if err != nil { + t.Fatal(err) + } + if v, err := meshwants.DecodeAsksByHoldModuleFingerprintValue(raw); err != nil || v.Asking != 2 { + t.Errorf("the ask %s is not the seat's Ask: %+v, %v", raw, v, err) + } +} diff --git a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go index 4eb76b26..d45cba08 100644 --- a/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go +++ b/modules/mesh-delivery-assigner/cmd/mesh-delivery-assigner/views.go @@ -40,6 +40,8 @@ var firstViewTools = []viewedTool{ {"assignments", mma.StateAssignments, "where each module is placed: the seat mesh-module-assigner's assignments, by module."}, {"seat-holders", mma.StateSeatHolders, "who holds which seat on which node."}, {"wants", "wants", "what each node should run: want.., its fingerprint and the rule that gave it."}, + // The seat mesh-wants' asks (design 47 G §717): the asks the assigner put to a person about what a node runs. + {"asks", "asks", "the asks the assigner put to a person about what a node runs, and their answers."}, } // readTools are the assigner's ordinary read tools, one per state its readers read (design 25 § 12.2). @@ -48,7 +50,6 @@ var readTools = []viewedTool{ {"assigner_read_seat_holders", mma.StateSeatHolders, "who holds which seat on which node."}, {"assigner_read_wants", "wants", "what each node should run: want..."}, {"assigner_read_findings", assign.FindingsState, "the assigner's findings, which the condition keeper raises and clears."}, - {"assigner_read_asks", "asks", "the asks the assigner put to a person, and their answers."}, {"assigner_read_differences", "differences", "every difference between a want and what a node runs."}, {"assigner_read_copies", copiesState, "the copies the assigner made of a retired state: when the copy into assignments ended and how many keys it copied."}, } diff --git a/modules/mesh-delivery-assigner/manifest_test.go b/modules/mesh-delivery-assigner/manifest_test.go index a3e01885..141bce97 100644 --- a/modules/mesh-delivery-assigner/manifest_test.go +++ b/modules/mesh-delivery-assigner/manifest_test.go @@ -100,11 +100,11 @@ func TestManifest(t *testing.T) { t.Errorf("objects = %+v, want assignment-revisions, 512 MiB, form 1", m.Objects) } // Whether the seats' definitions let it hold them is the manifest validator's (the judge, at the seat list). - // ADR 0621 §1: the first-view tools assignments, seat-holders and wants and design 25 § 12.2's six read tools, of the + // ADR 0621 §1: the first-view tools assignments, seat-holders, wants and asks (§717) and design 25 § 12.2's six read tools, of the // program's bundle, which runs in a runtime of its own on the assigner's account; ADR 0618 §3: and assigner_engine_reads, // which lists and asks again what each node's engine reads. - if !slices.Equal(m.Tools, []string{"assigner_put", "assigner_release", "assigner_puts", "assigner_waits", "assigner_inputs", "assignments", "seat-holders", "wants", - "assigner_read_assignments", "assigner_read_seat_holders", "assigner_read_wants", "assigner_read_findings", "assigner_read_asks", "assigner_read_differences", "assigner_read_copies", + if !slices.Equal(m.Tools, []string{"assigner_put", "assigner_release", "assigner_puts", "assigner_waits", "assigner_inputs", "assignments", "seat-holders", "wants", "asks", + "assigner_read_assignments", "assigner_read_seat_holders", "assigner_read_wants", "assigner_read_findings", "assigner_read_differences", "assigner_read_copies", "assigner_first_views", "assigner_first_view_again", "assigner_engine_reads", "assigner_seat_list", "assigner_seat_list_again", "assigner_ask_again", "assigner_written", "assigner_pins", "assigner_unpin", "assigner_delivered_again"}) { t.Errorf("the assigner serves tools %v, want its three put tools, three first views, six read tools, assigner_engine_reads, the seat list's two (ADR 0636 §7) and assigner_ask_again (ADR 0668 §5)", m.Tools) } @@ -112,7 +112,10 @@ func TestManifest(t *testing.T) { t.Errorf("runs-as = %q: the bundle runs on the assigner's own account (ADR 0621 §1)", m.RunsAs) } // ADR 0621 §2: each first-view tool answers exactly its readers, never the assigner. - callers := map[string]string{"assignments": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker", "seat-holders": "node-tools,mesh-delivery-builder,mesh-delivery-view", "wants": "mesh-grants"} + // Design 47 G §717: the teller and the status view take their first views of the seat mesh-wants' asks and wants, and of + // the assignments, from the tools named for the states. + callers := map[string]string{"assignments": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker,mesh-delivery-view", + "seat-holders": "node-tools,mesh-delivery-builder,mesh-delivery-view", "wants": "mesh-grants,mesh-delivery-view", "asks": "mesh-delivery-teller,mesh-delivery-view"} env := m.Build.Artifacts[0].Env for tool, list := range callers { if env[views.CallersEnv(tool)] != list || views.ParseCallers(env[views.CallersEnv(tool)]).Allows("n1."+m.Module) { diff --git a/modules/mesh-delivery-assigner/module.json b/modules/mesh-delivery-assigner/module.json index 5efc0f95..969b18c8 100644 --- a/modules/mesh-delivery-assigner/module.json +++ b/modules/mesh-delivery-assigner/module.json @@ -115,11 +115,11 @@ "assignments", "seat-holders", "wants", + "asks", "assigner_read_assignments", "assigner_read_seat_holders", "assigner_read_wants", "assigner_read_findings", - "assigner_read_asks", "assigner_read_differences", "assigner_read_copies", "assigner_first_views", @@ -247,9 +247,10 @@ "mesh-delivery-assigner" ], "env": { - "MESH_VIEW_CALLERS_ASSIGNMENTS": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker", + "MESH_VIEW_CALLERS_ASKS": "mesh-delivery-teller,mesh-delivery-view", + "MESH_VIEW_CALLERS_ASSIGNMENTS": "mesh-grants,node-tools,mesh-delivery-indexer,mesh-delivery-builder,mesh-delivery-checker,mesh-delivery-view", "MESH_VIEW_CALLERS_SEAT_HOLDERS": "node-tools,mesh-delivery-builder,mesh-delivery-view", - "MESH_VIEW_CALLERS_WANTS": "mesh-grants" + "MESH_VIEW_CALLERS_WANTS": "mesh-grants,mesh-delivery-view" } } ] diff --git a/modules/mesh-delivery-contracts/go/seats/meshwants/contract.go b/modules/mesh-delivery-contracts/go/seats/meshwants/contract.go index f8dd9332..4f4eae93 100644 --- a/modules/mesh-delivery-contracts/go/seats/meshwants/contract.go +++ b/modules/mesh-delivery-contracts/go/seats/meshwants/contract.go @@ -121,6 +121,7 @@ const AcceptSubjectStep = "mesh.seat.mesh-wants.accept.step" const ( StateWants = "wants" + StateAsks = "asks" StateDifferences = "differences" StateCompositions = "compositions" StateAssignmentRevisions = "assignment-revisions" @@ -244,6 +245,318 @@ func DecodeWantsByHeldModuleFingerprintValue(raw []byte) (WantsByHeldModuleFinge return v, nil } +// AsksByHoldModuleFingerprintKey is the key of one value of the state asks under the form "hold..". +func AsksByHoldModuleFingerprintKey(module, fingerprint string) string { + return "hold." + module + "." + fingerprint +} + +// ParseAsksByHoldModuleFingerprintKey reads the parts of a key of the form "hold..", in the order of the form; the last result is false for a key of another form. +func ParseAsksByHoldModuleFingerprintKey(key string) (string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "hold.") { + return "", "", false + } + rest = rest[5:] + v1, after1, found1 := strings.Cut(rest, ".") + if !found1 || v1 == "" { + return "", "", false + } + rest = after1 + if rest == "" { + return "", "", false + } + v3 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v1, v3, true +} + +// DecodeAsksByHoldModuleFingerprintValue reads one value of the state asks under the form "hold..", refusing a field AsksByHoldModuleFingerprintValue does not know and any text after the value. +func DecodeAsksByHoldModuleFingerprintValue(raw []byte) (AsksByHoldModuleFingerprintValue, error) { + var v AsksByHoldModuleFingerprintValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// AsksByRestoreNodeModuleFingerprintKey is the key of one value of the state asks under the form "restore...". +func AsksByRestoreNodeModuleFingerprintKey(node, module, fingerprint string) string { + return "restore." + node + "." + module + "." + fingerprint +} + +// ParseAsksByRestoreNodeModuleFingerprintKey reads the parts of a key of the form "restore...", in the order of the form; the last result is false for a key of another form. +func ParseAsksByRestoreNodeModuleFingerprintKey(key string) (string, string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "restore.") { + return "", "", "", false + } + rest = rest[8:] + v1, after1, found1 := strings.Cut(rest, ".") + if !found1 || v1 == "" { + return "", "", "", false + } + rest = after1 + v3, after3, found3 := strings.Cut(rest, ".") + if !found3 || v3 == "" { + return "", "", "", false + } + rest = after3 + if rest == "" { + return "", "", "", false + } + v5 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v1, v3, v5, true +} + +// DecodeAsksByRestoreNodeModuleFingerprintValue reads one value of the state asks under the form "restore...", refusing a field AsksByRestoreNodeModuleFingerprintValue does not know and any text after the value. +func DecodeAsksByRestoreNodeModuleFingerprintValue(raw []byte) (AsksByRestoreNodeModuleFingerprintValue, error) { + var v AsksByRestoreNodeModuleFingerprintValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// AsksByBackupNodeModuleFingerprintKey is the key of one value of the state asks under the form "backup...". +func AsksByBackupNodeModuleFingerprintKey(node, module, fingerprint string) string { + return "backup." + node + "." + module + "." + fingerprint +} + +// ParseAsksByBackupNodeModuleFingerprintKey reads the parts of a key of the form "backup...", in the order of the form; the last result is false for a key of another form. +func ParseAsksByBackupNodeModuleFingerprintKey(key string) (string, string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "backup.") { + return "", "", "", false + } + rest = rest[7:] + v1, after1, found1 := strings.Cut(rest, ".") + if !found1 || v1 == "" { + return "", "", "", false + } + rest = after1 + v3, after3, found3 := strings.Cut(rest, ".") + if !found3 || v3 == "" { + return "", "", "", false + } + rest = after3 + if rest == "" { + return "", "", "", false + } + v5 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v1, v3, v5, true +} + +// DecodeAsksByBackupNodeModuleFingerprintValue reads one value of the state asks under the form "backup...", refusing a field AsksByBackupNodeModuleFingerprintValue does not know and any text after the value. +func DecodeAsksByBackupNodeModuleFingerprintValue(raw []byte) (AsksByBackupNodeModuleFingerprintValue, error) { + var v AsksByBackupNodeModuleFingerprintValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// AsksByRecordNodeModuleDigestKey is the key of one value of the state asks under the form "record...". +func AsksByRecordNodeModuleDigestKey(node, module, digest string) string { + return "record." + node + "." + module + "." + digest +} + +// ParseAsksByRecordNodeModuleDigestKey reads the parts of a key of the form "record...", in the order of the form; the last result is false for a key of another form. +func ParseAsksByRecordNodeModuleDigestKey(key string) (string, string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "record.") { + return "", "", "", false + } + rest = rest[7:] + v1, after1, found1 := strings.Cut(rest, ".") + if !found1 || v1 == "" { + return "", "", "", false + } + rest = after1 + v3, after3, found3 := strings.Cut(rest, ".") + if !found3 || v3 == "" { + return "", "", "", false + } + rest = after3 + if rest == "" { + return "", "", "", false + } + v5 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v1, v3, v5, true +} + +// DecodeAsksByRecordNodeModuleDigestValue reads one value of the state asks under the form "record...", refusing a field AsksByRecordNodeModuleDigestValue does not know and any text after the value. +func DecodeAsksByRecordNodeModuleDigestValue(raw []byte) (AsksByRecordNodeModuleDigestValue, error) { + var v AsksByRecordNodeModuleDigestValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// AsksByOutsideNodeModuleFingerprintKey is the key of one value of the state asks under the form "outside...". +func AsksByOutsideNodeModuleFingerprintKey(node, module, fingerprint string) string { + return "outside." + node + "." + module + "." + fingerprint +} + +// ParseAsksByOutsideNodeModuleFingerprintKey reads the parts of a key of the form "outside...", in the order of the form; the last result is false for a key of another form. +func ParseAsksByOutsideNodeModuleFingerprintKey(key string) (string, string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "outside.") { + return "", "", "", false + } + rest = rest[8:] + v1, after1, found1 := strings.Cut(rest, ".") + if !found1 || v1 == "" { + return "", "", "", false + } + rest = after1 + v3, after3, found3 := strings.Cut(rest, ".") + if !found3 || v3 == "" { + return "", "", "", false + } + rest = after3 + if rest == "" { + return "", "", "", false + } + v5 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v1, v3, v5, true +} + +// DecodeAsksByOutsideNodeModuleFingerprintValue reads one value of the state asks under the form "outside...", refusing a field AsksByOutsideNodeModuleFingerprintValue does not know and any text after the value. +func DecodeAsksByOutsideNodeModuleFingerprintValue(raw []byte) (AsksByOutsideNodeModuleFingerprintValue, error) { + var v AsksByOutsideNodeModuleFingerprintValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// AsksByRevertNodeModuleFingerprintKey is the key of one value of the state asks under the form "revert...". +func AsksByRevertNodeModuleFingerprintKey(node, module, fingerprint string) string { + return "revert." + node + "." + module + "." + fingerprint +} + +// ParseAsksByRevertNodeModuleFingerprintKey reads the parts of a key of the form "revert...", in the order of the form; the last result is false for a key of another form. +func ParseAsksByRevertNodeModuleFingerprintKey(key string) (string, string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "revert.") { + return "", "", "", false + } + rest = rest[7:] + v1, after1, found1 := strings.Cut(rest, ".") + if !found1 || v1 == "" { + return "", "", "", false + } + rest = after1 + v3, after3, found3 := strings.Cut(rest, ".") + if !found3 || v3 == "" { + return "", "", "", false + } + rest = after3 + if rest == "" { + return "", "", "", false + } + v5 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v1, v3, v5, true +} + +// DecodeAsksByRevertNodeModuleFingerprintValue reads one value of the state asks under the form "revert...", refusing a field AsksByRevertNodeModuleFingerprintValue does not know and any text after the value. +func DecodeAsksByRevertNodeModuleFingerprintValue(raw []byte) (AsksByRevertNodeModuleFingerprintValue, error) { + var v AsksByRevertNodeModuleFingerprintValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// AsksByAssignmentsProposalKey is the key of one value of the state asks under the form "assignments.". +func AsksByAssignmentsProposalKey(proposal string) string { return "assignments." + proposal } + +// ParseAsksByAssignmentsProposalKey reads the parts of a key of the form "assignments.", in the order of the form; the last result is false for a key of another form. +func ParseAsksByAssignmentsProposalKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "assignments.") { + return "", false + } + rest = rest[12:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodeAsksByAssignmentsProposalValue reads one value of the state asks under the form "assignments.", refusing a field AsksByAssignmentsProposalValue does not know and any text after the value. +func DecodeAsksByAssignmentsProposalValue(raw []byte) (AsksByAssignmentsProposalValue, error) { + var v AsksByAssignmentsProposalValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + // DifferencesByNodeModuleKey is the key of one value of the state differences under the form ".". func DifferencesByNodeModuleKey(node, module string) string { return node + "." + module } @@ -464,6 +777,15 @@ func DecodeInputsByTimeDigestValue(raw []byte) (InputsByTimeDigestValue, error) return v, nil } +// Ask is an ask the assigner puts to a person: the router's ask whole, as the assigner composes it, and how many times it was written at its key. +type Ask struct { + // the seat operator-channel's ask, whole + Ask map[string]any `json:"ask"` + // the times the assigner wrote the ask at this key: 0 or 1 for the first, n for the n-th + Asking int64 `json:"asking,omitempty"` + V int64 `json:"v,omitempty"` +} + // AssignmentEntry is one module version on a node of an assignment graph revision, stored once by its digest (ADR 0656). type AssignmentEntry struct { // the watched branch its placement names, whose catalog graph the entry comes from (design 47 lines 523-525) @@ -975,6 +1297,27 @@ type WantsByHeldModuleFingerprintValue struct { Words string `json:"words,omitempty"` } +// AsksByHoldModuleFingerprintValue is one value of the state asks under the key hold... +type AsksByHoldModuleFingerprintValue = Ask + +// AsksByRestoreNodeModuleFingerprintValue is one value of the state asks under the key restore.... +type AsksByRestoreNodeModuleFingerprintValue = Ask + +// AsksByBackupNodeModuleFingerprintValue is one value of the state asks under the key backup.... +type AsksByBackupNodeModuleFingerprintValue = Ask + +// AsksByRecordNodeModuleDigestValue is one value of the state asks under the key record.... +type AsksByRecordNodeModuleDigestValue = Ask + +// AsksByOutsideNodeModuleFingerprintValue is one value of the state asks under the key outside.... +type AsksByOutsideNodeModuleFingerprintValue = Ask + +// AsksByRevertNodeModuleFingerprintValue is one value of the state asks under the key revert.... +type AsksByRevertNodeModuleFingerprintValue = Ask + +// AsksByAssignmentsProposalValue is one value of the state asks under the key assignments.. +type AsksByAssignmentsProposalValue = Ask + // DifferencesByNodeModuleValue is one value of the state differences under the key .. type DifferencesByNodeModuleValue struct { Rule int64 `json:"rule,omitempty"` diff --git a/modules/mesh-delivery-contracts/go/seats/meshwants/contract_test.go b/modules/mesh-delivery-contracts/go/seats/meshwants/contract_test.go index 95ad5671..3daf1981 100644 --- a/modules/mesh-delivery-contracts/go/seats/meshwants/contract_test.go +++ b/modules/mesh-delivery-contracts/go/seats/meshwants/contract_test.go @@ -113,6 +113,12 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, + {name: "an ask about a held fingerprint, asked a second time", subject: "", given: "{\"ask\":{\"headline\":\"web 0123456789ab is held\",\"id\":\"hold.web.0123456789ab.2\"},\"asking\":2,\"v\":1}", wire: "{\"ask\":{\"headline\":\"web 0123456789ab is held\",\"id\":\"hold.web.0123456789ab.2\"},\"asking\":2,\"v\":1}", + decode: func(b []byte) (any, error) { + var v AsksByHoldModuleFingerprintValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, {name: "revision asks the trunk's newest", subject: "", given: "{}", wire: "{}", decode: func(b []byte) (any, error) { var v RevisionInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "revision asks a candidate's", subject: "", given: "{\"group\":\"feat/47-G\",\"heads\":\"0123456789ab\"}", wire: "{\"group\":\"feat/47-G\",\"heads\":\"0123456789ab\"}", diff --git a/modules/mesh-delivery-contracts/seats/mesh-wants/fixtures/states.json b/modules/mesh-delivery-contracts/seats/mesh-wants/fixtures/states.json index 85e5d74c..e49128ca 100644 --- a/modules/mesh-delivery-contracts/seats/mesh-wants/fixtures/states.json +++ b/modules/mesh-delivery-contracts/seats/mesh-wants/fixtures/states.json @@ -151,5 +151,17 @@ } ] } + }, + { + "name": "an ask about a held fingerprint, asked a second time", + "of": "state:asks/hold..", + "given": { + "v": 1, + "ask": { + "id": "hold.web.0123456789ab.2", + "headline": "web 0123456789ab is held" + }, + "asking": 2 + } } ] diff --git a/modules/mesh-delivery-contracts/seats/mesh-wants/seat.json b/modules/mesh-delivery-contracts/seats/mesh-wants/seat.json index 9a0f5535..b3c1c761 100644 --- a/modules/mesh-delivery-contracts/seats/mesh-wants/seat.json +++ b/modules/mesh-delivery-contracts/seats/mesh-wants/seat.json @@ -489,6 +489,7 @@ { "name": "wants", "template": "state", + "view": true, "key-forms": [ { "key": "want..", @@ -618,6 +619,64 @@ "history": 20, "description": "what each node should run, and the fingerprints held back" }, + { + "name": "asks", + "template": "state", + "view": true, + "history": 5, + "description": "the asks the assigner puts to a person about what a node runs, one key per ask, each the ask as the assigner composes it (design 47 G section 717)", + "key-forms": [ + { + "key": "hold..", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the ask about a module's held fingerprint whose health check failed: release it or keep it held" + }, + { + "key": "restore...", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the ask about a data module set back on a node: restore and lose newer writes, or leave it stopped (ADR 0429 section 6)" + }, + { + "key": "backup...", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the ask about a backup that failed before a node applies a fingerprint: back up again, or go on without one (ADR 0539 section 16)" + }, + { + "key": "record...", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the ask about a move a person must allow for a module of policy record, naming the composition's digest (ADR 0539 section 14)" + }, + { + "key": "outside...", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the ask about a moved module whose failing health check waited past its bound on an outside cause (design 47 G section 706)" + }, + { + "key": "revert...", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the ask about a held revert with no restore point, the reverted build's fingerprint (design 47 G section 710)" + }, + { + "key": "assignments.", + "value": { + "$ref": "#/defs/Ask" + }, + "description": "the authorising ask about a proposal of assignments (ADR 0567 section 5)" + } + ] + }, { "name": "differences", "template": "state", @@ -1409,6 +1468,26 @@ "branch", "revision" ] + }, + "Ask": { + "type": "object", + "description": "an ask the assigner puts to a person: the router's ask whole, as the assigner composes it, and how many times it was written at its key", + "properties": { + "v": { + "type": "integer" + }, + "ask": { + "type": "object", + "description": "the seat operator-channel's ask, whole" + }, + "asking": { + "type": "integer", + "description": "the times the assigner wrote the ask at this key: 0 or 1 for the first, n for the n-th" + } + }, + "required": [ + "ask" + ] } }, "acts": [ diff --git a/modules/mesh-delivery-contracts/ts/seats/meshwants/index.d.ts b/modules/mesh-delivery-contracts/ts/seats/meshwants/index.d.ts index 1380f032..e037cb56 100644 --- a/modules/mesh-delivery-contracts/ts/seats/meshwants/index.d.ts +++ b/modules/mesh-delivery-contracts/ts/seats/meshwants/index.d.ts @@ -74,6 +74,7 @@ export declare const stepSubject: "mesh.seat.mesh-wants.accept.step"; export declare const states: { readonly wants: "wants"; + readonly asks: "asks"; readonly differences: "differences"; readonly compositions: "compositions"; readonly assignmentRevisions: "assignment-revisions"; @@ -98,6 +99,48 @@ export declare function wantsByHeldModuleFingerprintKey(module: string, fingerpr /** The parts of a key of the form "held..", or undefined for a key of another form. */ export declare function parseWantsByHeldModuleFingerprintKey(_key: string): { module: string; fingerprint: string } | undefined; +/** The key of one value of the state asks under the form "hold..". */ +export declare function asksByHoldModuleFingerprintKey(module: string, fingerprint: string): string; + +/** The parts of a key of the form "hold..", or undefined for a key of another form. */ +export declare function parseAsksByHoldModuleFingerprintKey(_key: string): { module: string; fingerprint: string } | undefined; + +/** The key of one value of the state asks under the form "restore...". */ +export declare function asksByRestoreNodeModuleFingerprintKey(node: string, module: string, fingerprint: string): string; + +/** The parts of a key of the form "restore...", or undefined for a key of another form. */ +export declare function parseAsksByRestoreNodeModuleFingerprintKey(_key: string): { node: string; module: string; fingerprint: string } | undefined; + +/** The key of one value of the state asks under the form "backup...". */ +export declare function asksByBackupNodeModuleFingerprintKey(node: string, module: string, fingerprint: string): string; + +/** The parts of a key of the form "backup...", or undefined for a key of another form. */ +export declare function parseAsksByBackupNodeModuleFingerprintKey(_key: string): { node: string; module: string; fingerprint: string } | undefined; + +/** The key of one value of the state asks under the form "record...". */ +export declare function asksByRecordNodeModuleDigestKey(node: string, module: string, digest: string): string; + +/** The parts of a key of the form "record...", or undefined for a key of another form. */ +export declare function parseAsksByRecordNodeModuleDigestKey(_key: string): { node: string; module: string; digest: string } | undefined; + +/** The key of one value of the state asks under the form "outside...". */ +export declare function asksByOutsideNodeModuleFingerprintKey(node: string, module: string, fingerprint: string): string; + +/** The parts of a key of the form "outside...", or undefined for a key of another form. */ +export declare function parseAsksByOutsideNodeModuleFingerprintKey(_key: string): { node: string; module: string; fingerprint: string } | undefined; + +/** The key of one value of the state asks under the form "revert...". */ +export declare function asksByRevertNodeModuleFingerprintKey(node: string, module: string, fingerprint: string): string; + +/** The parts of a key of the form "revert...", or undefined for a key of another form. */ +export declare function parseAsksByRevertNodeModuleFingerprintKey(_key: string): { node: string; module: string; fingerprint: string } | undefined; + +/** The key of one value of the state asks under the form "assignments.". */ +export declare function asksByAssignmentsProposalKey(proposal: string): string; + +/** The parts of a key of the form "assignments.", or undefined for a key of another form. */ +export declare function parseAsksByAssignmentsProposalKey(_key: string): { proposal: string } | undefined; + /** The key of one value of the state differences under the form ".". */ export declare function differencesByNodeModuleKey(node: string, module: string): string; @@ -134,6 +177,15 @@ export declare function inputsByTimeDigestKey(time: string, digest: string): str /** The parts of a key of the form "