Judge every pull request against the mesh that runs, before it merges (hq ADR 0237, to-be 45 §9)

Every check the mesh had ran after a merge, on a machine: a manifest the node-engine refused
(236), an identity a real machine's name made too long (263). merge-gate raises the mesh as the
facts snapshot says it is and the mesh with the change, each in a throwaway store through the
controller's own records, composes every machine twice and validates it with the node-engine's
validator, and fails what the change breaks, naming the machine's roles and the module - plus a
manifest the judging controller cannot read, a consumer left out of its grant, a module removed
while a machine runs it, a new module the node-engine would refuse; it warns on a wide rebuild.

The forge's new head of a pull request becomes a check the controller asks of the build seat:
the head and, beside it, the controller the mesh runs, the catalogue, the host and the lab; a
throwaway store and bus of the versions the mesh runs; the repository's merge-check.sh in the
mesh's Go toolchain with no container runtime socket; then mesh-lab's replays. The verdict is
said as checked, an error never a pass, and nothing is recorded or registered.
This commit is contained in:
jochen
2026-10-06 21:11:26 +02:00
parent 068283137b
commit 9c714f00d6
22 changed files with 2320 additions and 6 deletions
+41
View File
@@ -87,6 +87,42 @@ type BuildRequest struct {
// builder echoes it, and whoever hears the outcome takes nothing in — no record, no registration,
// no plan, nothing a push could send.
DryRun bool `json:"dry-run,omitempty"`
// Check makes this ask a pull request's merge check rather than a build (novox/hq to-be 45 §9): the
// builder checks the repository out at Ref with the repositories it is checked beside, reads the
// facts snapshot, raises the throwaway stores the check needs, runs the repository's own
// merge-check.sh and answers its verdict. Nothing is built, published or registered.
Check *CheckRequest `json:"check,omitempty"`
}
// CheckRequest is what a merge check needs beyond the repository and its head.
type CheckRequest struct {
Owner string `json:"owner"`
Repo string `json:"repo"`
Number int `json:"number,omitempty"`
Base string `json:"base,omitempty"`
// Paths are the files the pull request changes, for the width of its rebuild.
Paths []string `json:"paths,omitempty"`
// Beside are the repositories the check reads next to this one, each cloned at the ref given — the
// controller the mesh runs, the catalogue it holds, the host it runs — keyed by the directory name the
// check finds it under.
Beside map[string]CheckedOut `json:"beside,omitempty"`
}
// CheckedOut is a repository cloned beside a check, at a ref.
type CheckedOut struct {
Repository string `json:"repository"`
Ref string `json:"ref,omitempty"`
}
// CheckOutcome is a merge check's verdict.
type CheckOutcome struct {
// Verdict is pass, warning, fail, or error: the check could not run, which is never a pass.
Verdict string `json:"verdict"`
Summary string `json:"summary"`
// Report is the check's own account, its last lines, bounded.
Report string `json:"report,omitempty"`
// Took is how long it ran.
Took string `json:"took,omitempty"`
}
// SourceOnSeat names a repository by the seat whose holder serves it and its path there.
@@ -151,6 +187,11 @@ type BuildResult struct {
// DryRun is the request's, echoed: an outcome nobody may take in (novox/hq issue 240).
DryRun bool `json:"dry-run,omitempty"`
// Check is a merge check's verdict, for an ask that was one; the request's Check is echoed in
// Checked so whoever hears it knows which pull request it judged.
Check *CheckOutcome `json:"check-outcome,omitempty"`
Checked *CheckRequest `json:"check,omitempty"`
}
// ReadRepository is a repository a build read source from besides the module's own, at the branch,
+3
View File
@@ -40,6 +40,9 @@ var Contracts = map[string]Contract{
Tests: []string{"TestAnEnrolmentMetByAHeldTokenIsAskedToTryAgain"}},
KindModuleMoved: {Unordered: "the catalogue saying a module's current build moved: acted on by reading the " +
"catalogue's record, which is the order, so a late one reads the same record"},
KindPullUpdated: {Unordered: "a pull request's head, asked to be checked: each head is its own commit, and its " +
"verdict is set on that commit alone, so a head heard late is checked and judged as itself and never " +
"stands for a newer one (novox/hq to-be 45 §9)"},
KindCatchUp: {Unordered: "a catalogue asking what it missed: answered from the record, whenever asked"},
KindProvisioner: {Unordered: "a provider's newest word about a consumer, said again every fifteen minutes " +
"while it holds (ADR 0224): the condition keeps the last observed, and S8 says when the words stop. " +
+37
View File
@@ -73,6 +73,9 @@ const (
// KeyRolledBack: a build failed its gate on its first machine and was put back there, or could not
// be (novox/hq ADR 0236, to-be 45 §8); or a witness on a machine put a core component back.
KeyRolledBack = "rolled-back"
// KeyChecked: a pull request's merge check was judged (novox/hq to-be 45 §9) — the verdict, its
// summary and its report, for the forge's holder to set as the pull request's status.
KeyChecked = "checked"
)
// Applied is what a machine now runs, as the mesh states it.
@@ -203,6 +206,40 @@ type SourceMoved struct {
ModuleDirsSaid bool `json:"module_dirs_said,omitempty"`
}
// PullUpdated is what the forge announces when an open pull request's head moves — opened, or pushed
// to (novox/hq to-be 45 §9): what the controller asks the build seat to check before it merges.
type PullUpdated struct {
Owner string `json:"owner"`
Repo string `json:"repo"`
Number int `json:"number"`
Title string `json:"title,omitempty"`
Base string `json:"base"`
Head string `json:"head"`
Commit string `json:"head_sha"`
CloneURL string `json:"clone_url"`
HTMLURL string `json:"html_url,omitempty"`
// Paths are the files the pull request changes; PathsTruncated says there were more.
Paths []string `json:"paths,omitempty"`
PathsTruncated bool `json:"paths_truncated,omitempty"`
}
// Checked is a pull request's merge check, judged: what the controller says as `checked`.
type Checked struct {
Owner string `json:"owner"`
Repo string `json:"repo"`
Number int `json:"number,omitempty"`
Commit string `json:"commit"`
// Verdict is pass, warning, fail, or error — the check could not be run, which is not the change's
// fault and is never read as a pass.
Verdict string `json:"verdict"`
Summary string `json:"summary"`
// Report is the check's own account, bounded.
Report string `json:"report,omitempty"`
// ID is the ask, and On the machine that ran it.
ID string `json:"id"`
On string `json:"on,omitempty"`
}
type Upgraded struct {
Module string `json:"module"`
Commit string `json:"commit"`
+3
View File
@@ -39,6 +39,9 @@ const (
// KindProvisioner is a provider saying a consumer has failed for minutes, or recovered
// (novox/hq ADR 0224).
KindProvisioner = "provisioner"
// KindPullUpdated is the forge announcing a pull request's new head: checked before it merges
// (novox/hq to-be 45 §9).
KindPullUpdated = "pull-updated"
)
// Control is one thing a node or a module said, as the controller must act on it.
+3 -1
View File
@@ -53,7 +53,7 @@ func Nats(js *broker.JetStream) Inbound {
// whatever was asked for — and not at all when nothing was.
func (n *natsInbound) Also(kind string) error {
switch kind {
case KindModuleMoved, KindCatchUp, KindSourceMoved, KindProvisioner:
case KindModuleMoved, KindCatchUp, KindSourceMoved, KindProvisioner, KindPullUpdated:
n.follows[kind] = true
return nil
default:
@@ -248,6 +248,8 @@ func kindOfSubject(subject string) (string, bool) {
return KindCatchUp, true
case broker.ControllerFollows[3]:
return KindSourceMoved, true
case PullUpdatedSubject:
return KindPullUpdated, true
case BuildOutcome(), BuildOutcomeOf(TheBuildMachineBefore):
// A build's outcome is the role's event now, so it arrives on the events stream rather than
// the control branch — and is acted on by the same handler, because what the controller does
+41
View File
@@ -63,6 +63,15 @@ type Upgrader interface {
SourceMoved(ctx context.Context, m SourceMoved) error
}
// Checker is what the controller does when the forge says a pull request's head moved: ask for it to be
// checked before it merges (novox/hq to-be 45 §9).
type Checker interface {
PullUpdated(ctx context.Context, p PullUpdated) error
}
// PullUpdatedSubject is where the forge's pull requests land: the controller's own follow of them.
var PullUpdatedSubject = broker.ControllerFollows[len(broker.ControllerFollows)-1]
// Server acts on what nodes and modules say.
//
// **It holds no transport.** What arrives comes through Inbound and what it publishes goes through
@@ -83,6 +92,8 @@ type Server struct {
standings Standings
// retirements keeps what providers say about consumers the mesh stopped asking for (ADR 0230).
retirements Retirements
// checker asks for a pull request's merge check (novox/hq to-be 45 §9).
checker Checker
log *log.Logger
// giveUp is how long one message is held for the store; zero means GiveUpAfter.
@@ -116,6 +127,15 @@ func (s *Server) Follows(u Upgrader) error {
return nil
}
// Checks says what to do about a pull request's new head, and asks for them to be delivered.
func (s *Server) Checks(c Checker) error {
if err := s.inbound.Also(KindPullUpdated); err != nil {
return err
}
s.checker = c
return nil
}
// Answers says what to do about a catalogue's catch-up request, and asks for them to be delivered.
func (s *Server) Answers(r Replayer) error {
if err := s.inbound.Also(KindCatchUp); err != nil {
@@ -184,6 +204,8 @@ func (s *Server) act(ctx context.Context, m Control) {
s.catchingUp(ctx, m)
case KindProvisioner:
s.provisioner(ctx, m)
case KindPullUpdated:
s.pullUpdated(ctx, m)
default:
// Dropped: a message nothing understands will not be understood on the next attempt
// either, and asking for it again would spin.
@@ -611,6 +633,25 @@ func (s *Server) sourceMoved(ctx context.Context, m Control) {
_ = m.Took()
}
// pullUpdated asks for a pull request's merge check. Taken whatever happens: a check that could not be
// asked is said here, and the next push to the pull request asks again.
func (s *Server) pullUpdated(ctx context.Context, m Control) {
var p PullUpdated
if err := json.Unmarshal(m.Body(), &p); err != nil || p.Commit == "" || p.Repo == "" {
s.log.Printf("a pull request's announcement could not be read or named no repository or head; ignored")
_ = m.Took()
return
}
if s.checker == nil {
_ = m.Took()
return
}
if err := s.checker.PullUpdated(ctx, p); err != nil {
s.log.Printf("%s/%s#%d at %.8s could not be asked to be checked: %v", p.Owner, p.Repo, p.Number, p.Commit, err)
}
_ = m.Took()
}
// saysWhatItDid states what a machine now runs, or what it would not take, as a fact on the bus
// (novox/hq ADR 0134).
//