A build keeps what it was told
The builder announces a build with the resolved manifest, the path inside the repository, and every artifact it stood on. The control plane received all of it and kept none of it. That was survivable while the catalogue heard the same announcement directly. It stops being survivable the moment the catalogue was not there to hear it — which on a fresh mesh is always, and always for the same modules: the shared base, the store the catalogue runs on, and the catalogue itself are each necessarily built BEFORE the catalogue exists to hear about them. The graph's foundation is the part the graph never sees. Replaying those builds needs what they said, not a summary. Without the manifest there are no requires/provides edges; without `against` there are no build edges, which are the ones that answer "a base moved, what must be rebuilt". A replay carrying neither would restore the module list and leave the question the catalogue exists for still wrong, while looking fixed. Kept null rather than empty where a build predates this, so a replay can say it is holding nothing instead of inventing an empty declaration for a module that certainly had one. And `built_against`, not `built_on`: that column exists and means the machine, which is a different fact about a different subject. Toward novox/hq 04-ISSUES/050. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -72,6 +72,12 @@ func buildFrom(result link.BuildResult) inventory.Build {
|
|||||||
kept := inventory.Build{
|
kept := inventory.Build{
|
||||||
ID: result.ID, Repository: result.Repository, Ref: result.Ref,
|
ID: result.ID, Repository: result.Repository, Ref: result.Ref,
|
||||||
Commit: result.Commit, On: result.On, Failed: result.Failed,
|
Commit: result.Commit, On: result.On, Failed: result.Failed,
|
||||||
|
// **What the announcement carries, kept rather than discarded** (novox/hq 04-ISSUES/050).
|
||||||
|
// The catalogue turns the manifest into requires/provides edges and `against` into build
|
||||||
|
// edges, and it is not always listening when a build happens — on a fresh mesh it cannot
|
||||||
|
// be, for exactly the modules it needs most. Keeping them is what makes a replay able to
|
||||||
|
// rebuild the graph rather than a list of names.
|
||||||
|
Path: result.Path, Manifest: result.Manifest, Against: result.Against,
|
||||||
}
|
}
|
||||||
for _, made := range result.Made {
|
for _, made := range result.Made {
|
||||||
kept.Made = append(kept.Made, inventory.Artifact{
|
kept.Made = append(kept.Made, inventory.Artifact{
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ type Build struct {
|
|||||||
Commit string
|
Commit string
|
||||||
// On is the machine that did it.
|
// On is the machine that did it.
|
||||||
On string
|
On string
|
||||||
|
// Path is where inside the repository the module lives (novox/hq ADR 0069).
|
||||||
|
Path string
|
||||||
|
// Manifest is the declaration the builder resolved, as it announced it.
|
||||||
|
//
|
||||||
|
// **Kept because the catalogue may not have been listening.** The announcement carries this
|
||||||
|
// and the catalogue turns it into the module's requires/provides edges. On a fresh mesh the
|
||||||
|
// modules built before the catalogue exists are exactly the ones it most needs, so the mesh
|
||||||
|
// has to be able to say afterwards what they declared (novox/hq 04-ISSUES/050).
|
||||||
|
Manifest []byte
|
||||||
|
// Against is every artifact this build stood on, as references rather than module names —
|
||||||
|
// what makes a build edge derived rather than declared (ADR 0009).
|
||||||
|
Against []string
|
||||||
// Failed is the builder's own words, empty when it worked.
|
// Failed is the builder's own words, empty when it worked.
|
||||||
Failed string
|
Failed string
|
||||||
Made []Artifact
|
Made []Artifact
|
||||||
@@ -49,15 +61,21 @@ func (i *Inventory) RecordBuild(ctx context.Context, b Build) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
against, err := json.Marshal(b.Against)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var module *string
|
var module *string
|
||||||
if b.Module != "" {
|
if b.Module != "" {
|
||||||
module = &b.Module
|
module = &b.Module
|
||||||
}
|
}
|
||||||
_, err = i.store.Pool().Exec(ctx,
|
_, err = i.store.Pool().Exec(ctx,
|
||||||
`insert into build (id, repository, ref, module, commit_hash, built_on, failed, made)
|
`insert into build (id, repository, ref, module, commit_hash, built_on, failed, made,
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
source_path, manifest, built_against)
|
||||||
|
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||||
on conflict (id) do nothing`,
|
on conflict (id) do nothing`,
|
||||||
b.ID, b.Repository, b.Ref, module, b.Commit, b.On, b.Failed, made)
|
b.ID, b.Repository, b.Ref, module, b.Commit, b.On, b.Failed, made,
|
||||||
|
b.Path, manifestOrNil(b.Manifest), against)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,3 +162,15 @@ func (i *Inventory) Held(ctx context.Context) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
return held, rows.Err()
|
return held, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// manifestOrNil keeps the difference between "declared nothing" and "predates this being kept".
|
||||||
|
//
|
||||||
|
// A build recorded before the mesh kept manifests has no manifest, and that is not the same as one
|
||||||
|
// whose manifest was empty. A replay can then say which it is holding instead of inventing an
|
||||||
|
// empty declaration for a module that certainly had one.
|
||||||
|
func manifestOrNil(raw []byte) any {
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
-- What a build result carried and the mesh threw away.
|
||||||
|
--
|
||||||
|
-- novox/hq 04-ISSUES/050. The builder announces a build with the resolved manifest, the path
|
||||||
|
-- inside the repository, and every artifact it was built against. The control plane receives all
|
||||||
|
-- of it and kept none of it: `build` held the repository, the ref, the commit and what was made.
|
||||||
|
--
|
||||||
|
-- That was survivable while the catalogue heard the same announcement directly. It stops being
|
||||||
|
-- survivable the moment the catalogue was not there to hear it — which on a fresh mesh is always,
|
||||||
|
-- and always for the same modules. The shared base, the store the catalogue itself runs on, and
|
||||||
|
-- the catalogue: each is necessarily built BEFORE the catalogue exists to hear about it, so the
|
||||||
|
-- graph's foundation is the part the graph never sees.
|
||||||
|
--
|
||||||
|
-- Replaying those builds needs what they said, not a summary of it. Without the manifest there
|
||||||
|
-- are no requires/provides edges; without `against` there are no build edges, which are the ones
|
||||||
|
-- that answer "a base moved, what must be rebuilt". A replay carrying neither would restore the
|
||||||
|
-- module list and leave the question the catalogue exists for still wrong, while looking fixed.
|
||||||
|
--
|
||||||
|
-- Empty and null-free, so every build recorded before this keeps exactly the meaning it had: a
|
||||||
|
-- row with no manifest is one that predates this, and a replay says so rather than inventing an
|
||||||
|
-- empty declaration.
|
||||||
|
--
|
||||||
|
-- `built_against` rather than `built_on`: that column already exists and means the MACHINE that
|
||||||
|
-- did the build, which is a different fact about a different subject.
|
||||||
|
alter table build add column source_path text not null default '';
|
||||||
|
alter table build add column manifest jsonb;
|
||||||
|
alter table build add column built_against jsonb;
|
||||||
Reference in New Issue
Block a user