Build a module from a repository and a path within it

The builder cloned a repository and read the manifest at its root, which means one
repository per module. Nothing we have is shaped that way, so the builder could be
asked to build nothing that exists (novox/hq ADR 0069).

The path travels the whole way — named when asking, carried in the request, used
to read the manifest and as the context everything is produced from, echoed back
in the result, and recorded as part of where a module came from. Without that last
part the mesh could notice a module was behind its source and then be unable to
rebuild it, which is the worst of both.

A path climbing out of the clone is refused: a machine whose job is building other
people's repositories must not read whatever else is on its disk.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-12 16:45:50 +02:00
parent c4030947b0
commit f151de103f
7 changed files with 160 additions and 33 deletions
+18 -9
View File
@@ -23,7 +23,10 @@ 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 string
Ref 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
Ref string
// BuiltFrom is the commit the manifest the mesh holds was read at.
BuiltFrom string
// Head is the newest commit the source is known to have.
@@ -57,17 +60,19 @@ 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, ref, built_from, source_head)
values ($1, $2, nullif($3,''), nullif($4,''), nullif($5,''), nullif($6,''), nullif($6,''))
`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,''))
on conflict (name) do update set
manifest = excluded.manifest,
version = excluded.version,
registered = now(),
source = coalesce(excluded.source, module.source),
source_path = case when excluded.source is null then module.source_path
else excluded.source_path 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)
m.Module, raw, m.Version, from.Repository, from.Ref, from.BuiltFrom, from.Path)
return err
}
@@ -92,9 +97,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
err := i.store.Pool().QueryRow(ctx,
`select source, ref, built_from, source_head from module where name = $1`,
module).Scan(&repo, &ref, &built, &head)
`select source, source_path, ref, built_from, source_head from module where name = $1`,
module).Scan(&repo, &path, &ref, &built, &head)
if errors.Is(err, pgx.ErrNoRows) {
return Source{}, fmt.Errorf("%w: %s", ErrNoSuchModule, module)
}
@@ -109,6 +115,9 @@ 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.
s.Path = path
return s, nil
}
@@ -746,13 +755,13 @@ 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, ''), coalesce(m.ref, ''),
coalesce(m.source, ''), m.source_path, 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.ref, m.built_from, m.source_head
group by m.name, m.manifest, m.source, m.source_path, m.ref, m.built_from, m.source_head
order by m.name`)
if err != nil {
return nil, err
@@ -765,7 +774,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.Ref,
if err := rows.Scan(&name, &raw, &source.Repository, &source.Path, &source.Ref,
&source.BuiltFrom, &source.Head, &on); err != nil {
return nil, err
}
@@ -0,0 +1,16 @@
-- Where inside its repository a module lives.
--
-- novox/hq ADR 0069. A module is a repository and a path within it. The mesh recorded the
-- repository and the ref and not the path, which was survivable only while every module was
-- assumed to sit at a repository's root — an assumption that matched nothing that exists. The
-- catalogue holds its modules one to a directory, and the system this replaces has always built a
-- module from a repository and a path.
--
-- Without this column the mesh can record that a module's source has moved ahead of what it holds
-- and then be unable to rebuild it, because it cannot say which part of the repository the module
-- is. That is the failure this prevents: noticing, and then not being able to act.
--
-- Empty rather than null, and defaulted, because "the repository's root" is a real answer and the
-- ordinary one — not an absence. Every module recorded before this keeps exactly the meaning it
-- had.
alter table module add column source_path text not null default '';