Let a walk wait for its delivery's word, and serve the delivery's owner (hq ADR 0239)
mesh/merge-gate pass: every machine composes with the change as it did without (0 of 4 compose)
mesh/delivery delivered
mesh/delivery-group group feat/mesh-delivery delivered: every member is delivered

While the mesh-delivery seat has a holder on record, a merge that moves no
core module opens its walk and asks nothing until mesh-delivery or a person
says go; nothing of it is registered before its turn, so no other send
carries it. The controller keeps the planner, the gate, sending and the
walk, and gains the verbs the owner asks with: delivery-plan, -order,
-check (a group composed as one future state), deliver, delivery-stop,
delivery-walks; every walk kept is said as plan-moved.
This commit is contained in:
jochen
2026-10-07 00:01:42 +02:00
parent ba26ba2772
commit 487aa040de
29 changed files with 1562 additions and 39 deletions
+3 -1
View File
@@ -114,7 +114,9 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio
for _, c := range m.Consumes {
emitter, event, named := strings.Cut(c, ".")
if named {
if s, isASeat := seats[emitter]; isASeat {
// A seat's event when the seat says it; else the event of the module of that name — a seat and
// the module holding it may share a name (mesh-delivery, novox/hq ADR 0239).
if s, isASeat := seats[emitter]; isASeat && catalogue.SeatSays(s.Emits, event) {
watches = append(watches, broker.Seat{Name: s.Name, Emits: []string{event}})
continue
}
@@ -0,0 +1,9 @@
-- A walk waits for its delivery's word (novox/hq ADR 0239).
--
-- A plan is from now on the walk of one delivery's trunk commit across the machines: tier by tier, one
-- machine first and judged at the gate. While the mesh-delivery seat has a holder on record, a walk that
-- moves no core module is opened by the merge and waits — nothing asked, nothing registered, nothing sent —
-- until the delivery's owner says it may start, or a person does (`plans go`). Kept with the plan, as one
-- document: who it waits for, and when, by whom and why it was let go or stopped. Null for every plan
-- before this and for a walk on the controller's own path, which waits for nobody.
alter table release_plan add column delivery jsonb;
+49 -8
View File
@@ -41,8 +41,35 @@ type Plan struct {
// Release is set on a release plan (novox/hq ADR 0236): not a merge's, but the builds waiting for a
// gate, walked through the machines one at a time.
Release *PlanRelease `json:"release,omitempty"`
// Delivery is set on a walk that waits for its delivery's word (novox/hq ADR 0239): nil for a walk on
// the controller's own path, which starts at the merge as every plan did before.
Delivery *PlanDelivery `json:"delivery,omitempty"`
}
// PlanDelivery is what a walk waits for and what came of the wait (novox/hq ADR 0239).
type PlanDelivery struct {
// Awaits is who must say the walk may start: the seat whose holder owns the delivery.
Awaits string `json:"awaits"`
// Go is when it was let go, By by whom (the seat's holder, or a person's name) and Why.
Go *time.Time `json:"go,omitempty"`
By string `json:"by,omitempty"`
Why string `json:"why,omitempty"`
// Stopped is who ended the walk through the delivery's owner, and StoppedWhy why: the walk is failed,
// and the delivery reads it as stopped, not as a build that failed.
Stopped string `json:"stopped,omitempty"`
StoppedWhy string `json:"stopped_why,omitempty"`
}
// Waiting is whether the walk waits for its delivery's word.
func (p Plan) Waiting() bool {
return p.Delivery != nil && p.Delivery.Awaits != "" && p.Delivery.Go == nil
}
// PlanSaved is told every plan this process kept, after it is kept (novox/hq ADR 0239): the serving
// controller says it on the bus as `plan-moved`. Nil in a command, which says nothing; whoever follows a
// walk also asks for it, so a save made by a command is found by comparison.
var PlanSaved func(Plan)
// ErrPlanMoved is a save against a plan written by somebody else since it was read.
var ErrPlanMoved = errors.New("the plan was written by somebody else since it was read")
@@ -174,12 +201,17 @@ func (i *Inventory) SavePlan(ctx context.Context, p *Plan) error {
if err != nil {
return err
}
var release []byte
var release, delivery []byte
if p.Release != nil {
if release, err = json.Marshal(p.Release); err != nil {
return err
}
}
if p.Delivery != nil {
if delivery, err = json.Marshal(p.Delivery); err != nil {
return err
}
}
// **And how long the tier it left took** (novox/hq to-be 45 Phase 0): measured here, where the
// plan moves, in the same transaction as the move, so no save can move a tier unmeasured or
// measure one twice.
@@ -195,16 +227,16 @@ func (i *Inventory) SavePlan(ctx context.Context, p *Plan) error {
var revision int64
err = tx.QueryRow(ctx,
`insert into release_plan (id, repository, commit_hash, created, updated, state, tier, tiers, modules, note,
branch, tier_entered, revision, epoch, release)
values ($1, $2, $3, $4, now(), $5, $6, $7, $8, $9, $10, $11, 1, $13, $14)
branch, tier_entered, revision, epoch, release, delivery)
values ($1, $2, $3, $4, now(), $5, $6, $7, $8, $9, $10, $11, 1, $13, $14, $15)
on conflict (id) do update set updated = now(), state = excluded.state, tier = excluded.tier,
tiers = excluded.tiers, modules = excluded.modules, note = excluded.note, branch = excluded.branch,
tier_entered = excluded.tier_entered, revision = release_plan.revision + 1, epoch = excluded.epoch,
release = excluded.release
release = excluded.release, delivery = excluded.delivery
where release_plan.revision = $12
returning revision`,
p.ID, p.Repository, p.Commit, p.Created, p.State, p.Tier, tiers, modules, p.Note, p.Branch, entered,
p.Revision, epoch, release).Scan(&revision)
p.Revision, epoch, release, delivery).Scan(&revision)
if errors.Is(err, pgx.ErrNoRows) {
// The row is there and at another revision — moved since this was read, or there already
// when this one is new: either way not this writer's to overwrite. (A plan saved before plans
@@ -223,6 +255,9 @@ func (i *Inventory) SavePlan(ctx context.Context, p *Plan) error {
} else {
p.Epoch = 0
}
if PlanSaved != nil {
PlanSaved(*p)
}
return nil
}
@@ -259,7 +294,7 @@ func (i *Inventory) PlanByID(ctx context.Context, id string) (Plan, error) {
func (i *Inventory) plans(ctx context.Context, tail string) ([]Plan, error) {
rows, err := i.store.Pool().Query(ctx,
`select id, repository, commit_hash, created, updated, state, tier, tiers, modules, note, branch,
coalesce(tier_entered, created), revision, coalesce(epoch, 0), release
coalesce(tier_entered, created), revision, coalesce(epoch, 0), release, delivery
from release_plan `+tail)
if err != nil {
return nil, err
@@ -268,10 +303,11 @@ func (i *Inventory) plans(ctx context.Context, tail string) ([]Plan, error) {
var out []Plan
for rows.Next() {
var p Plan
var tiers, modules, release []byte
var tiers, modules, release, delivery []byte
var epoch int64
if err := rows.Scan(&p.ID, &p.Repository, &p.Commit, &p.Created, &p.Updated, &p.State,
&p.Tier, &tiers, &modules, &p.Note, &p.Branch, &p.TierEntered, &p.Revision, &epoch, &release); err != nil {
&p.Tier, &tiers, &modules, &p.Note, &p.Branch, &p.TierEntered, &p.Revision, &epoch, &release,
&delivery); err != nil {
return nil, err
}
if len(release) > 0 {
@@ -279,6 +315,11 @@ func (i *Inventory) plans(ctx context.Context, tail string) ([]Plan, error) {
return nil, err
}
}
if len(delivery) > 0 {
if err := json.Unmarshal(delivery, &p.Delivery); err != nil {
return nil, err
}
}
p.Epoch = uint64(epoch)
if err := json.Unmarshal(tiers, &p.Tiers); err != nil {
return nil, err