Seats are a closed set, a seat's holder answers for what it delivers, and a build source may live on the git seat
Implements novox/hq ADR 0110 and 0111. The seat set lives in internal/catalogue/seats.go: fourteen seats, each with a scope, what occupying it delivers, and the record that made it one. A test asserts the count and a decision per entry, so changing the set means finding the argument, as the host's vocabulary test does. The first set is every seat already claimed — including the-private-network, which the network module claims from a manifest composed in this repository's code, not from any module.json — plus npm-package-registry (ADR 0109) and git (ADR 0111). A test parses every catalogue manifest and this repository's own and fails on any refused claim, so closing the set refuses nothing in use. ParseManifest now refuses a claim on a seat the mesh does not define, a seat claimed at another scope, and a delivering seat claimed by a module that does not provide what it delivers. A malformed claim is refused once, for being malformed. Resolution: among several providers of a mesh provision, a pin still wins; then the holder of the seat that delivers it; then the only provider; otherwise refused as before. ADR 0009's "never guessed" holds — the seat is the choice made once, mesh-wide, rather than a pin per consumer node. A provider now carries the module it came from, because a provider is a (node, module) pair and the pair is what tells a holder from a neighbour on the same machine. The planner's second pass is now given the first pass's holdings. Without them, a node consuming a seat-delivered provision was refused there, and a refused node's own claims dropped out of what the mesh holds — letting a second holder of one of its seats pass unrefused. `seats [--json]` lists every seat, what it delivers, and each holder, derived from assignments every time and never stored. Unheld seats are listed. A stored claim outside the set — possible for a manifest registered before the set closed, since stored manifests are not re-validated — is shown rather than hidden. `build --self <owner>/<repo>` builds from a repository on the git seat's holder. The clone URL is composed at build time from the holder's node and what it serves for git; the recorded source is the path and the seat (migration 0032), never an address, so a moved forge changes nothing recorded. Nobody holding the seat refuses self-hosted builds and says so; external URLs are unchanged. An address passed with --self is refused rather than recorded as a path. Replaces three foundation tests that defended the builder's carried package binding. The catalogue removed that binding when the builder began requiring the registry through a real grant, so the tests were already failing on main; they now assert the builder requires what the npm seat delivers and carries no copy of its own, and that the forge holds the npm and git seats. Verified: go vet clean; the whole suite passes against a throwaway Postgres (make postgres), the new inventory tests included; gofmt clean apart from cmd/mesh-builder/stdout_test.go, which fails on main too.
This commit is contained in:
@@ -24,7 +24,12 @@ var ErrStillAssigned = errors.New("that module is still assigned to nodes")
|
||||
|
||||
// Source is where a module comes from and what has been built from it.
|
||||
type Source struct {
|
||||
// Repository is a URL, cloned exactly as given, unless Seat is set — then it is the
|
||||
// repository's path on that seat's holder, and never an address (novox/hq ADR 0111).
|
||||
Repository string
|
||||
// Seat is the seat the repository lives on: `git` for the mesh's own forge, empty for a
|
||||
// repository anywhere else.
|
||||
Seat string
|
||||
// Path is the module's directory inside that repository (novox/hq ADR 0069). Empty is the
|
||||
// repository's root, which is a real answer rather than a missing one.
|
||||
Path string
|
||||
@@ -62,8 +67,8 @@ func (i *Inventory) RegisterModule(ctx context.Context, m catalogue.Manifest, fr
|
||||
// record of where the module normally comes from — which is the only thing that would say,
|
||||
// afterwards, that the machine is running something nobody can rebuild.
|
||||
_, err = i.store.Pool().Exec(ctx,
|
||||
`insert into module (name, manifest, version, source, source_path, ref, built_from, source_head)
|
||||
values ($1, $2, nullif($3,''), nullif($4,''), $7, nullif($5,''), nullif($6,''), nullif($6,''))
|
||||
`insert into module (name, manifest, version, source, source_path, source_seat, ref, built_from, source_head)
|
||||
values ($1, $2, nullif($3,''), nullif($4,''), $7, $8, nullif($5,''), nullif($6,''), nullif($6,''))
|
||||
on conflict (name) do update set
|
||||
manifest = excluded.manifest,
|
||||
version = excluded.version,
|
||||
@@ -71,10 +76,12 @@ func (i *Inventory) RegisterModule(ctx context.Context, m catalogue.Manifest, fr
|
||||
source = coalesce(excluded.source, module.source),
|
||||
source_path = case when excluded.source is null then module.source_path
|
||||
else excluded.source_path end,
|
||||
source_seat = case when excluded.source is null then module.source_seat
|
||||
else excluded.source_seat end,
|
||||
ref = coalesce(excluded.ref, module.ref),
|
||||
built_from = coalesce(excluded.built_from, module.built_from),
|
||||
source_head = coalesce(excluded.built_from, module.source_head)`,
|
||||
m.Module, raw, m.Version, from.Repository, from.Ref, from.BuiltFrom, from.Path)
|
||||
m.Module, raw, m.Version, from.Repository, from.Ref, from.BuiltFrom, from.Path, from.Seat)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -99,10 +106,10 @@ func (i *Inventory) SourceMoved(ctx context.Context, module, head string) error
|
||||
func (i *Inventory) SourceOf(ctx context.Context, module string) (Source, error) {
|
||||
var s Source
|
||||
var repo, ref, built, head *string
|
||||
var path string
|
||||
var path, seat string
|
||||
err := i.store.Pool().QueryRow(ctx,
|
||||
`select source, source_path, ref, built_from, source_head from module where name = $1`,
|
||||
module).Scan(&repo, &path, &ref, &built, &head)
|
||||
`select source, source_path, source_seat, ref, built_from, source_head from module where name = $1`,
|
||||
module).Scan(&repo, &path, &seat, &ref, &built, &head)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return Source{}, fmt.Errorf("%w: %s", ErrNoSuchModule, module)
|
||||
}
|
||||
@@ -117,9 +124,10 @@ func (i *Inventory) SourceOf(ctx context.Context, module string) (Source, error)
|
||||
*pair.to = *pair.from
|
||||
}
|
||||
}
|
||||
// Not in the loop above: the path is never null, because "the repository's root" is an answer
|
||||
// rather than an absence.
|
||||
// Not in the loop above: the path and the seat are never null, because "the repository's root"
|
||||
// and "not on a seat" are answers rather than absences.
|
||||
s.Path = path
|
||||
s.Seat = seat
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -917,13 +925,14 @@ type Entry struct {
|
||||
func (i *Inventory) Catalogued(ctx context.Context) ([]Entry, error) {
|
||||
rows, err := i.store.Pool().Query(ctx,
|
||||
`select m.name, m.manifest,
|
||||
coalesce(m.source, ''), m.source_path, coalesce(m.ref, ''),
|
||||
coalesce(m.source, ''), m.source_path, m.source_seat, coalesce(m.ref, ''),
|
||||
coalesce(m.built_from, ''), coalesce(m.source_head, ''),
|
||||
coalesce(array_agg(n.name order by n.name) filter (where n.name is not null), '{}')
|
||||
from module m
|
||||
left join assignment a on a.module = m.name
|
||||
left join node n on n.id = a.node
|
||||
group by m.name, m.manifest, m.source, m.source_path, m.ref, m.built_from, m.source_head
|
||||
group by m.name, m.manifest, m.source, m.source_path, m.source_seat, m.ref, m.built_from,
|
||||
m.source_head
|
||||
order by m.name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -936,7 +945,7 @@ func (i *Inventory) Catalogued(ctx context.Context) ([]Entry, error) {
|
||||
var name string
|
||||
var source Source
|
||||
var on []string
|
||||
if err := rows.Scan(&name, &raw, &source.Repository, &source.Path, &source.Ref,
|
||||
if err := rows.Scan(&name, &raw, &source.Repository, &source.Path, &source.Seat, &source.Ref,
|
||||
&source.BuiltFrom, &source.Head, &on); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user