GATE A-18: the gate's record type drops the remove operation, and table, recheck and close go with their handlers and tools
Builds: design 47 G §587, §305, A-18 (G-20) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -87,9 +87,7 @@ func (h *Holder) askAgainForConfig(id, why string) {
|
||||
h.mu.Unlock()
|
||||
switch {
|
||||
case refused == nil:
|
||||
if _, err := h.Recheck(id, why, byOwner); err != nil {
|
||||
h.Logf("[mesh-delivery] %s: its check could not be asked again for a config value: %v", id, err)
|
||||
}
|
||||
h.Logf("[mesh-delivery] %s: its check could not be asked again for a config value: %v", id, checkerRefusal(id))
|
||||
case proposed:
|
||||
h.askProposedAgain(id, why)
|
||||
}
|
||||
|
||||
@@ -329,15 +329,12 @@ func (f *fakeLinks) Linked(repository string, number int) ([]int, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Stalled: a delivery past its state's bound is listed with what H2 may do; close takes only that. Only a proposed
|
||||
// Stalled: a delivery past its state's bound is listed with what H2 may do. Only a proposed
|
||||
// delivery has a bound now: its check was asked and not answered.
|
||||
func TestStalledAndCloseWorkFromTheTable(t *testing.T) {
|
||||
func TestStalledWorksFromTheTable(t *testing.T) {
|
||||
w := newWorld(t)
|
||||
id := IDOf("novox/app", head)
|
||||
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
|
||||
if _, err := w.h.Close(id, "too early"); err == nil {
|
||||
t.Fatal("closed within its bound")
|
||||
}
|
||||
w.later(3 * time.Hour)
|
||||
stalled := w.h.Stalled()
|
||||
if len(stalled) != 1 || stalled[0].ID != id || stalled[0].State != Proposed {
|
||||
@@ -352,16 +349,10 @@ func TestStalledAndCloseWorkFromTheTable(t *testing.T) {
|
||||
t.Fatalf("a merged delivery is stalled: %+v", s)
|
||||
}
|
||||
}
|
||||
if _, err := w.h.Close(id, "x"); err == nil {
|
||||
t.Fatal("H2 closed a merged delivery")
|
||||
}
|
||||
// A merge without a passing check is held: a person's, with no transition for H2.
|
||||
w.h.PullUpdated(pr("novox/app", 8, "ffffffffffff0000", "feat/q"))
|
||||
w.h.PullMerged(merged("novox/app", 8, "ffffffffffff0000", "eeeeeeeeeeee0000"))
|
||||
w.later(48 * time.Hour)
|
||||
if _, err := w.h.Close(IDOf("novox/app", "ffffffffffff0000"), "x"); err == nil {
|
||||
t.Fatal("H2 closed a held delivery")
|
||||
}
|
||||
}
|
||||
|
||||
// novox/hq issue 867, ADR 0382 §1 (contract A): each member of a group in `groups` says its pull request number, full
|
||||
|
||||
@@ -298,9 +298,6 @@ func boolArg(a map[string]any, k string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// aPerson is who an act through the seat is by: the seat does not name its caller.
|
||||
const aPerson = "a person, through the mesh-delivery seat"
|
||||
|
||||
func need(a map[string]any, keys ...string) error {
|
||||
for _, k := range keys {
|
||||
if strArg(a, k) == "" {
|
||||
@@ -367,36 +364,10 @@ func tools(h *Holder, l *listening) []stdio.Tool {
|
||||
}
|
||||
return h.Checks(strArg(a, "repository"), number, strArg(a, "commit"))
|
||||
}},
|
||||
{Name: seat + "table",
|
||||
Description: "The state table every delivery runs by: each transition with its guard, each state's bound " +
|
||||
"and what healer H2 may do once it has passed; ",
|
||||
Run: func(map[string]any) (any, error) { return TableText(), nil }},
|
||||
{Name: seat + "stalled",
|
||||
Description: "Every delivery held past its state's bound, with the transition the table lets healer H2 take. " +
|
||||
"Also a merge refused or not made past its bound, a group half merged, and a head with no merge check.",
|
||||
Run: func(a map[string]any) (any, error) { return h.Stalled(), nil }},
|
||||
{Name: seat + "recheck",
|
||||
Description: "Refused for every delivery: the checker checks a head again on every move of the head, its trunk or its " +
|
||||
"group, and nothing is asked of anyone here (novox/hq ADR 0447, issue 1318). Kept so a caller is told where to read.",
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{"id": str("the delivery's id"),
|
||||
"why": str("why, kept with the transition")}, "required": []string{"id", "why"}},
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
if err := need(a, "id", "why"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Recheck(strArg(a, "id"), strArg(a, "why"), aPerson)
|
||||
}},
|
||||
{Name: seat + "close",
|
||||
Description: "Healer H2's verb: for a proposed delivery past its bound, take the transition the table " +
|
||||
"names for that state. Refused for any other.",
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{"id": str("the delivery's id"),
|
||||
"why": str("the condition it answers")}, "required": []string{"id", "why"}},
|
||||
Run: func(a map[string]any) (any, error) {
|
||||
if err := need(a, "id", "why"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Close(strArg(a, "id"), strArg(a, "why"))
|
||||
}},
|
||||
{Name: "delivery_merge_again",
|
||||
Description: "On the operator's word, ask again at once the merge of an approved delivery the forge refused, or of an approved " +
|
||||
"group held or half merged, at the heads the Approve named (novox/hq issue 1275): every member is read first and " +
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
|
||||
}
|
||||
// The verbs the seat keeps: times, what-if, release, stop and retire-history went with the plans (ADR 0539 §17), and
|
||||
// settle with the merge states (design 47 G §431).
|
||||
if want := []string{"deliveries", "show", "groups", "source", "checks", "table", "stalled", "recheck", "close"}; !reflect.DeepEqual(m.Claims[0].Serves, want) {
|
||||
if want := []string{"deliveries", "show", "groups", "source", "checks", "stalled"}; !reflect.DeepEqual(m.Claims[0].Serves, want) {
|
||||
t.Fatalf("serves %v, wanted %v", m.Claims[0].Serves, want)
|
||||
}
|
||||
// The forge messages' layout is the bridge's published package forgemsg, taken by its series (ADR 0634 §3, design 32).
|
||||
@@ -293,7 +293,7 @@ func TestTheGateHoldsTheSeatAsTheSDKsTableOfRelease6HasIt(t *testing.T) {
|
||||
t.Errorf("the manifest serves %s, which G removed with the merge states", removed)
|
||||
}
|
||||
}
|
||||
for _, verb := range []string{"deliveries", "show", "groups", "checks", "table", "stalled", "recheck", "close"} {
|
||||
for _, verb := range []string{"deliveries", "show", "groups", "checks", "stalled"} {
|
||||
if !served[verb] {
|
||||
t.Errorf("the gate does not serve %s, which the seat keeps", verb)
|
||||
}
|
||||
|
||||
@@ -177,21 +177,6 @@ func TestAFailingMeshCheckRejects(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 1302 (lead's (2)): the recheck verb refuses a delivery on a mesh/check base, saying the checker checks again on every
|
||||
// move and where to read it.
|
||||
func TestRecheckRefusesAMeshCheckDelivery(t *testing.T) {
|
||||
w := newWorld(t)
|
||||
p := pr(oneRepo, 1, oneHead, "feat/one")
|
||||
requiresCheck(w, p)
|
||||
w.h.PullUpdated(p)
|
||||
w.h.CommitStatus(checkStatus(oneRepo, oneHead, "failure", oneTrunk))
|
||||
w.settleAll()
|
||||
_, err := w.h.Recheck(IDOf(oneRepo, oneHead), "try again", "tester")
|
||||
if err == nil || !strings.Contains(err.Error(), "checker_pull") || !strings.Contains(err.Error(), "checker") {
|
||||
t.Fatalf("recheck answered %v, want a refusal naming the checker and checker_pull", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1318 (the gate's part): a pull request closed unmerged asks the controller to forget nothing: forget-checks is not called.
|
||||
func TestAClosedPullRequestAsksNoForgetChecks(t *testing.T) {
|
||||
w := newWorld(t)
|
||||
|
||||
@@ -55,13 +55,12 @@ const (
|
||||
OpFree = "free" // remove from pinned, by revision
|
||||
OpEnd = "end" // set-fields: ended
|
||||
OpAct = "act" // set-entry into acts, by id: an act owed the forge with its outcome
|
||||
OpRemove = "remove" // remove the instance, at its retention
|
||||
OpHold = "hold" // a month's list: set-entry into records, by instance
|
||||
OpDrop = "drop" // a month's list: remove from records, by instance
|
||||
)
|
||||
|
||||
// RecordOperations are a record's operations, in the order the manifest's type allows them.
|
||||
var RecordOperations = []string{OpOpen, OpGroup, OpTrace, OpEntry, OpOutcome, OpAway, OpBack, OpPin, OpFree, OpAct, OpEnd, OpRemove}
|
||||
var RecordOperations = []string{OpOpen, OpGroup, OpTrace, OpEntry, OpOutcome, OpAway, OpBack, OpPin, OpFree, OpAct, OpEnd}
|
||||
|
||||
// HeldOperations are the month's list's operations.
|
||||
var HeldOperations = []string{OpHold, OpDrop}
|
||||
|
||||
@@ -146,8 +146,10 @@ func (f *ledgerFake) Apply(instance, operation string, body any) (uint64, error)
|
||||
}
|
||||
|
||||
func (f *ledgerFake) Remove(instance string) error {
|
||||
_, err := f.Apply(instance, OpRemove, map[string]any{})
|
||||
return err
|
||||
f.seq[instance]++
|
||||
f.states[instance] = json.RawMessage("null")
|
||||
f.history = append(f.history, instance+" remove")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ledgerFake) Get(instance string) (json.RawMessage, uint64, error) {
|
||||
|
||||
@@ -381,33 +381,3 @@ func pastBound(d *Delivery, now time.Time) bool {
|
||||
b, bounded := boundOf(d)
|
||||
return bounded && !d.State.Final() && (b.For == 0 || now.Sub(boundSince(d)) > b.For)
|
||||
}
|
||||
|
||||
// TableText is the table as the `table` verb answers it.
|
||||
func TableText() map[string]any {
|
||||
var rows []map[string]any
|
||||
for _, r := range Table {
|
||||
from := make([]string, 0, len(r.From))
|
||||
for _, s := range r.From {
|
||||
if s == None {
|
||||
from = append(from, "(none)")
|
||||
continue
|
||||
}
|
||||
from = append(from, string(s))
|
||||
}
|
||||
kind := "observed"
|
||||
if r.Act {
|
||||
kind = "asked"
|
||||
}
|
||||
rows = append(rows, map[string]any{"from": strings.Join(from, ", "), "event": r.Event, "to": r.To,
|
||||
"guard": r.Guard, "taken": kind})
|
||||
}
|
||||
bounds := map[string]any{}
|
||||
for s, b := range Bounds {
|
||||
var h2 []string
|
||||
for _, e := range b.H2 {
|
||||
h2 = append(h2, string(e))
|
||||
}
|
||||
bounds[string(s)] = map[string]any{"bound": b.For.String(), "h2": h2, "says": b.Says}
|
||||
}
|
||||
return map[string]any{"transitions": rows, "bounds": bounds}
|
||||
}
|
||||
|
||||
@@ -252,51 +252,6 @@ func (h *Holder) Stalled() []StalledLine {
|
||||
return out
|
||||
}
|
||||
|
||||
// Recheck is the `recheck` verb. The checker judges every head and checks it again on every move of its trunk (ADR 0447 §4), and
|
||||
// the controller judges none (ADR 0524 §5), so there is no ask to make: a delivery that exists is refused, naming the checker
|
||||
// (the lead's decision for issue 1302), and an unknown id is refused as before.
|
||||
func (h *Holder) Recheck(id, why, by string) (string, error) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
if h.deliveries[id] == nil {
|
||||
return "", fmt.Errorf("no delivery %q", id)
|
||||
}
|
||||
return "", checkerRefusal(id)
|
||||
}
|
||||
|
||||
// Close is healer H2's verb: for a proposed delivery past its bound, its check is asked once more; only a transition
|
||||
// the table names for that state counts, any other answer is refused.
|
||||
func (h *Holder) Close(id, why string) (string, error) {
|
||||
h.mu.Lock()
|
||||
d := h.deliveries[id]
|
||||
if d == nil {
|
||||
h.mu.Unlock()
|
||||
return "", fmt.Errorf("no delivery %q", id)
|
||||
}
|
||||
b, bounded := boundOf(d)
|
||||
if !bounded || len(b.H2) == 0 {
|
||||
h.mu.Unlock()
|
||||
return "", fmt.Errorf("%s is %s: H2 has no transition there — the state is the operator's", id, d.State)
|
||||
}
|
||||
if !pastBound(d, h.Now()) {
|
||||
h.mu.Unlock()
|
||||
return "", fmt.Errorf("%s has been %s for %s, within its bound of %s: nothing to close", id, d.State,
|
||||
h.Now().Sub(boundSince(d)).Round(time.Second), b.For)
|
||||
}
|
||||
if d.State == Proposed {
|
||||
return h.reask(d, why) // unlocks
|
||||
}
|
||||
h.mu.Unlock()
|
||||
return "", fmt.Errorf("%s is %s: H2 has no transition there", id, d.State)
|
||||
}
|
||||
|
||||
// reask is H2's close of a proposed delivery past its bound. A head's check is the checker's, which asks nothing of anyone
|
||||
// and checks again on its own (ADR 0447 §4), so there is no check to ask once more. Called under the lock; it unlocks.
|
||||
func (h *Holder) reask(d *Delivery, why string) (string, error) {
|
||||
h.mu.Unlock()
|
||||
return "", checkerRefusal(d.ID)
|
||||
}
|
||||
|
||||
// Status is the module's own `delivery_status`.
|
||||
func (h *Holder) Status(listening string) map[string]any {
|
||||
h.mu.Lock()
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
"groups",
|
||||
"source",
|
||||
"checks",
|
||||
"table",
|
||||
"stalled",
|
||||
"recheck",
|
||||
"close"
|
||||
"stalled"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -248,10 +245,6 @@
|
||||
"ended"
|
||||
],
|
||||
"sequence": true
|
||||
},
|
||||
{
|
||||
"name": "remove",
|
||||
"effect": "remove"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -343,7 +336,7 @@
|
||||
"config": {
|
||||
"refusal-stall-floor-minutes": {
|
||||
"type": "number",
|
||||
"description": "minutes, at least, before a merge refused because its trunk moved, whose check the gate asked again, is said as stalled; twice the repository's last check time when that is longer (ADR 0505 §4).",
|
||||
"description": "minutes, at least, before a merge refused because its trunk moved, whose check the gate asked again, is said as stalled; twice the repository's last check time when that is longer (ADR 0505 \u00a74).",
|
||||
"minimum": 1,
|
||||
"maximum": 1440,
|
||||
"trusted": false,
|
||||
@@ -351,7 +344,7 @@
|
||||
},
|
||||
"author-new-head-minutes": {
|
||||
"type": "number",
|
||||
"description": "minutes a delivery that needs its author's new head (a conflict, a failed check, a rejected group) waits for one before the operator hears of it (ADR 0505 §5).",
|
||||
"description": "minutes a delivery that needs its author's new head (a conflict, a failed check, a rejected group) waits for one before the operator hears of it (ADR 0505 \u00a75).",
|
||||
"minimum": 1,
|
||||
"maximum": 10080,
|
||||
"trusted": false,
|
||||
|
||||
Reference in New Issue
Block a user