DLV-I12: the judge refuses a ledger type whose operations or indexes name what its schema lacks
Left by the bus-state review (ADR 0595 §8): a set-entry or remove names its into and key, an operation's fields and an index's on name schema fields, and a digest-of names a text field; each refusal names module, entry, type, operation or index, and the field. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -347,7 +348,9 @@ func workQueueProblems(at string, e catalogue.BusStateEntry) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ledgerTypeProblems judges a ledger's types: named locally and once, and every operation's effect one of the four.
|
||||
// ledgerTypeProblems judges a ledger's types: named locally and once, and every operation's effect one of the four; a
|
||||
// set-entry or remove names its `into` and `key`; an operation's `fields` and an index's `on` name fields of the type's
|
||||
// schema; a `digest-of` names a text field of it (DLV-I12, ADR 0595 §8, ADR 0597 §2, §4).
|
||||
func ledgerTypeProblems(at string, e catalogue.BusStateEntry) []string {
|
||||
var problems []string
|
||||
if len(e.Types) == 0 {
|
||||
@@ -374,6 +377,34 @@ func ledgerTypeProblems(at string, e catalogue.BusStateEntry) []string {
|
||||
"%s: the operation %q has the effect %q, outside the four (%s) (novox/hq ADR 0597 §2)",
|
||||
where, op.Name, op.Effect, strings.Join(ledgerEffects, ", ")))
|
||||
}
|
||||
if op.Effect == catalogue.EffectSetEntry || op.Effect == catalogue.EffectRemove {
|
||||
for field, v := range map[string]string{"into": op.Into, "key": op.Key} {
|
||||
if strings.TrimSpace(v) == "" {
|
||||
problems = append(problems, fmt.Sprintf("%s: the operation %q (%s) names no %s (novox/hq ADR 0597 §2)", where, op.Name, op.Effect, field))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range op.Fields {
|
||||
if _, ok := t.Schema[f]; !ok {
|
||||
problems = append(problems, fmt.Sprintf("%s: the operation %q names the field %q, which the type's schema does not hold", where, op.Name, f))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ix := range t.Indexes {
|
||||
for _, f := range ix.On {
|
||||
if _, ok := t.Schema[f]; !ok {
|
||||
problems = append(problems, fmt.Sprintf("%s: the index %q is on the field %q, which the type's schema does not hold (novox/hq ADR 0597 §4)", where, ix.Name, f))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range slices.Sorted(maps.Keys(t.Schema)) {
|
||||
of := t.Schema[name].DigestOf
|
||||
if of == "" {
|
||||
continue
|
||||
}
|
||||
if src, ok := t.Schema[of]; !ok || src.Type != "text" {
|
||||
problems = append(problems, fmt.Sprintf("%s: the field %q is the digest of %q, which is no text field of the type's schema", where, name, of))
|
||||
}
|
||||
}
|
||||
}
|
||||
return problems
|
||||
|
||||
@@ -52,6 +52,19 @@ func TestBusStateIsJudgedOnce(t *testing.T) {
|
||||
{"a bare stream with a wrong when full", manifest(`{"name":"x","storage":"stream","configuration":{"when-full":"crash","max-mib":10,"max-age-days":9}}`), []string{"\"x\"", "\"when-full\"", "\"refuse\" or \"drop-oldest\""}},
|
||||
{"a ledger effect outside the four", manifest(strings.Replace(ledger, `"set-fields"`, `"upsert"`, 1)), []string{"\"records\"", "effect \"upsert\"", "outside the four"}},
|
||||
{"a ledger whose history expires", manifest(strings.Replace(ledger, `"options"`, `"configuration":{"max-age-days":30},"options"`, 1)), nil},
|
||||
// DLV-I12 (ADR 0595 §8, ADR 0597 §2, §4): each refusal names the module, the entry, the type, the operation or index, and the field.
|
||||
{"a set-entry naming no into", manifest(strings.Replace(ledger, `"effect":"set-fields","fields":["title"]`, `"effect":"set-entry","key":"title"`, 1)),
|
||||
[]string{"\"records\"", "type \"decision\"", "operation \"propose\"", "no into"}},
|
||||
{"a remove naming no key", manifest(strings.Replace(ledger, `"effect":"set-fields","fields":["title"]`, `"effect":"remove","into":"links"`, 1)),
|
||||
[]string{"\"records\"", "operation \"propose\"", "no key"}},
|
||||
{"a set-entry naming both", manifest(strings.Replace(ledger, `"effect":"set-fields","fields":["title"]`, `"effect":"set-entry","into":"links","key":"title","fields":["title"]`, 1)), nil},
|
||||
{"an operation's field the schema lacks", manifest(strings.Replace(ledger, `"fields":["title"]`, `"fields":["title","body"]`, 1)),
|
||||
[]string{"\"records\"", "operation \"propose\"", "field \"body\"", "schema does not hold"}},
|
||||
{"an index on a field the schema lacks", manifest(strings.Replace(ledger, `"fields":["title"]}]`, `"fields":["title"]}],"indexes":[{"name":"by-owner","on":["owner"]}]`, 1)),
|
||||
[]string{"\"records\"", "index \"by-owner\"", "field \"owner\""}},
|
||||
{"a digest of a field that is no text", manifest(strings.Replace(ledger, `{"title":{"type":"text"}}`, `{"title":{"type":"text"},"n":{"type":"integer"},"sum":{"type":"text","digest-of":"n"}}`, 1)),
|
||||
[]string{"\"records\"", "field \"sum\"", "digest of \"n\"", "no text field"}},
|
||||
{"a digest of a text field", manifest(strings.Replace(ledger, `{"title":{"type":"text"}}`, `{"title":{"type":"text"},"sum":{"type":"text","digest-of":"title"}}`, 1)), nil},
|
||||
} {
|
||||
_, problems := Manifest([]byte(c.raw), nil)
|
||||
switch {
|
||||
|
||||
Reference in New Issue
Block a user