Files
mesh-controller/internal/inventory/schema.go
T
jschoubben e6e1e3bc89
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
A gate judges its own send and the build it sent, and never puts the controller back behind its store (hq issue 352)
On 2026-10-09 a release's gate on the control node read the machine's
report against a newer send another plan had just made there, failed
three builds the machine had reported healthy, and put them back on
every machine to a controller older than the store's schema; that
controller then passed the newer plan's gate from its own health.

- A gate keeps what its send carried (digest, sequence) and reads the
  report against it; a report on the last send is on it too.
- A gate judges only the build the machine was last sent: another build
  there supersedes the judging — no verdict, nothing put back.
- A controller is told its build (MESH_CONTROLLER_VERSION, ${version}
  in a process's env) and records how far it reads the store's schema;
  a put-back to a build that reaches less, or never said, is refused
  and the current build kept, said as urgent.
- A release's open gate holds other sends of its modules there, and a
  plan's own first send waits on it.
2026-10-09 17:15:40 +02:00

48 lines
1.7 KiB
Go

package inventory
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
)
// What a controller build knows of the store's schema (novox/hq issue 352): the highest migration it
// carries, recorded by its version when it serves, and the highest migration the store has applied. A
// put-back of the controller to a build that reaches less than the store is refused (gate.go), because
// such a controller starts behind its own records and judges with what it can read.
// RecordSchemaReach keeps the highest migration the build serving now carries.
func (i *Inventory) RecordSchemaReach(ctx context.Context, build string, reach int) error {
if build == "" {
return errors.New("a schema reach is recorded by a build's version, and this controller has none")
}
_, err := i.store.Pool().Exec(ctx,
`insert into controller_schema (build, reach) values ($1, $2)
on conflict (build) do update set reach = excluded.reach, recorded = now()`, build, reach)
return err
}
// SchemaReachOf is the highest migration a build carries, as it recorded when it served; false for a
// build that never did.
func (i *Inventory) SchemaReachOf(ctx context.Context, build string) (int, bool, error) {
var reach int
err := i.store.Pool().QueryRow(ctx, `select reach from controller_schema where build = $1`, build).Scan(&reach)
if errors.Is(err, pgx.ErrNoRows) {
return 0, false, nil
}
return reach, err == nil, err
}
// SchemaApplied is the highest migration the store has applied.
func (i *Inventory) SchemaApplied(ctx context.Context) (int, error) {
var n *int
if err := i.store.Pool().QueryRow(ctx, `select max(number) from migration`).Scan(&n); err != nil {
return 0, err
}
if n == nil {
return 0, nil
}
return *n, nil
}