mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/check pass 32ad59fc6402 on 609013a2638f | unit 2010 run, 2322 kept, 5 skipped | integration 2032 run, 2341 kept, 9 skipped | over bound: unit, in…
The operator, 2026-10-25 22:56: "run the check locally only, but not as a gate, it loses us too much time". Tonight each merge ask waited 3-7 minutes for mesh/check, and every merge into main refused the next approved merge for a check judged on the trunk before (mesh-delivery#52). Both settings default to true, so nothing changes until the operator sets them. False: the gate makes a reviewed pull request ready and asks its Approve with no verdict, a trunk's move puts nothing out of date, a verdict is kept and shown, and a group still merges in one pass; the merge tool merges a head the Approve names whatever its mesh/check says. delivery_status and show say the mode.
109 lines
3.7 KiB
Go
109 lines
3.7 KiB
Go
package main
|
|
|
|
// The gate's settings (novox/hq ADR 0505 §4, §5), merged by the mesh into one JSON file the env names. Each has a
|
|
// default here, so a gate started without the file, or with a value it cannot read, keeps the decided bound and says
|
|
// why once.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// DefaultRefusalFloor is the least a refusal the gate is already answering (the trunk moved and its check was
|
|
// asked again) waits before it is said as stalled, when the setting refusal-stall-floor-minutes gives none.
|
|
DefaultRefusalFloor = 10 * time.Minute
|
|
// DefaultAuthorBound is how long a delivery whose cure is its author's new head waits for one before the operator
|
|
// hears of it, when the setting author-new-head-minutes gives none (ADR 0505 §5).
|
|
DefaultAuthorBound = 2 * time.Hour
|
|
)
|
|
|
|
// Settings are the operator's values for this module.
|
|
type Settings struct {
|
|
RefusalFloorMinutes *int `json:"refusal-stall-floor-minutes"`
|
|
AuthorBoundMinutes *int `json:"author-new-head-minutes"`
|
|
// CheckGatesMerge is whether mesh/check gates the merge (the default, true). False (the operator, 2026-10-25 22:56:
|
|
// "run the check locally only, but not as a gate, it loses us too much time"): a reviewed pull request is ready and its
|
|
// Approve asked without a verdict, a trunk's move puts nothing out of date, and a verdict is shown, never waited on.
|
|
CheckGatesMerge *bool `json:"check-gates-merge"`
|
|
}
|
|
|
|
// readSettings reads the settings file; a value that is not a whole number of minutes, one or more, is refused.
|
|
func readSettings(path string) (Settings, error) {
|
|
var s Settings
|
|
if path == "" {
|
|
return s, errors.New("started without a settings file")
|
|
}
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
var loose map[string]any
|
|
if err := json.Unmarshal(raw, &loose); err != nil {
|
|
return s, fmt.Errorf("the settings file is not JSON: %v", err)
|
|
}
|
|
for name, into := range map[string]**int{"refusal-stall-floor-minutes": &s.RefusalFloorMinutes,
|
|
"author-new-head-minutes": &s.AuthorBoundMinutes} {
|
|
switch v := loose[name].(type) {
|
|
case nil:
|
|
case float64:
|
|
n := int(v)
|
|
if v < 1 || v != float64(n) {
|
|
return Settings{}, fmt.Errorf("%s is not a whole number of minutes, one or more", name)
|
|
}
|
|
*into = &n
|
|
default:
|
|
return Settings{}, fmt.Errorf("%s is not a number", name)
|
|
}
|
|
}
|
|
switch v := loose["check-gates-merge"].(type) {
|
|
case nil:
|
|
case bool:
|
|
s.CheckGatesMerge = &v
|
|
default:
|
|
return Settings{}, errors.New("check-gates-merge is not true or false")
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// checkGates is whether mesh/check gates the merge: true unless the setting check-gates-merge is false.
|
|
func (h *Holder) checkGates() bool {
|
|
if h.Settings == nil {
|
|
return true
|
|
}
|
|
v := h.Settings().CheckGatesMerge
|
|
return v == nil || *v
|
|
}
|
|
|
|
// factsAt are the facts a settle reads at now: the moment, and whether the check gates the merge.
|
|
func (h *Holder) factsAt(now time.Time) Facts {
|
|
return Facts{Now: now, Ungated: !h.checkGates()}
|
|
}
|
|
|
|
// minutesOr is a setting's minutes, or the default when it gives none.
|
|
func minutesOr(n *int, def time.Duration) time.Duration {
|
|
if n == nil {
|
|
return def
|
|
}
|
|
return time.Duration(*n) * time.Minute
|
|
}
|
|
|
|
// refusalFloor is the least an answered refusal waits before it is said as stalled (ADR 0505 §4).
|
|
func (h *Holder) refusalFloor() time.Duration {
|
|
if h.Settings == nil {
|
|
return DefaultRefusalFloor
|
|
}
|
|
return minutesOr(h.Settings().RefusalFloorMinutes, DefaultRefusalFloor)
|
|
}
|
|
|
|
// authorBound is how long a delivery that needs its author's new head waits before the operator hears of it (§5).
|
|
func (h *Holder) authorBound() time.Duration {
|
|
if h.Settings == nil {
|
|
return DefaultAuthorBound
|
|
}
|
|
return minutesOr(h.Settings().AuthorBoundMinutes, DefaultAuthorBound)
|
|
}
|