GATE7 (source part, §690): the gate's source answers a branch's source key, or the branches it keeps
The gate's source value is the contract's SourceByBranchValue and its key SourceKey(branch); the verb takes an optional branch and answers branches alone when given none. The whole-module package does not compile on the join for GATE11 (settle) and GATE17 (ledger fill and remove), so the unit suite was run with those five sites stubbed in a scratch edit, never committed: green apart from the stubbed removal case. Builds: design 47 G §690, §614, §15 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -178,17 +178,27 @@ func (s *fakeSource) Move(update func(prev *Source) (Source, bool)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read is source.main as held, and its revision: the number of writes.
|
||||
func (s *fakeSource) Read() (*Source, uint64, error) {
|
||||
// Read is a branch's source key as held, and its revision: the number of writes. The fake holds the key of MergeBranch only.
|
||||
func (s *fakeSource) Read(branch string) (*Source, uint64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.main == nil {
|
||||
if s.main == nil || branch != MergeBranch {
|
||||
return nil, 0, nil
|
||||
}
|
||||
held := *s.main
|
||||
return &held, uint64(len(s.moves)), nil
|
||||
}
|
||||
|
||||
// Branches are the branches whose source key the fake holds.
|
||||
func (s *fakeSource) Branches() ([]string, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.main == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{MergeBranch}, nil
|
||||
}
|
||||
|
||||
func sortStrings(s []string) { sort.Strings(s) }
|
||||
func itoa(n int) string { return strconv.Itoa(n) }
|
||||
|
||||
|
||||
@@ -639,7 +639,7 @@ func TestAPassingVerdictAsksTheApproveAndItsApproveMergesWithinSecondsOverARealB
|
||||
// The forge's own word that the pull request merged follows the bridge's (its webhook): the delivery ends merged.
|
||||
b.publish(t, "mesh.seat.git.event.pull.merged", seatMerged(merged("novox/app", 7, head, strings.Repeat("b", 40))))
|
||||
rt.until("source.main to hold the merge", eventWithin, func() bool {
|
||||
e, err := b.kvs["source"].Get(ctx, mdc.SourceMainKey)
|
||||
e, err := b.kvs["source"].Get(ctx, mdc.SourceKey(MergeBranch))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -864,7 +864,7 @@ func TestAGroupOfTwoWithGreenChecksGetsOneAskAndOnItsApproveBothMergeOneAfterThe
|
||||
b.forgeSays(t, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: two.Repository, Number: two.Number, Head: two.Head.Head,
|
||||
Merge: strings.Repeat("b", 40), Root: "r", Trees: map[string]string{"x": "t"}})
|
||||
rt.until("source.main to hold both merges", eventWithin, func() bool {
|
||||
e, err := b.kvs["source"].Get(ctx, mdc.SourceMainKey)
|
||||
e, err := b.kvs["source"].Get(ctx, mdc.SourceKey(MergeBranch))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -41,11 +42,18 @@ func TestAMergeHeardOnlyFromTheForgeMovesSourceMain(t *testing.T) {
|
||||
t.Fatalf("source.main is %+v; wanted %s at %s and %s kept", w.source.main, i888App, i888Merge1, i888Other)
|
||||
}
|
||||
// The seat's verb source answers each repository's trunk branch and head, and the revision, from the same key.
|
||||
if a := w.h.SourceRevision(); a.Error != "" || a.Revision != 1 || len(a.Repositories) != 2 ||
|
||||
if a := w.h.SourceRevision(MergeBranch); a.Error != "" || a.Revision != 1 || len(a.Repositories) != 2 ||
|
||||
a.Repositories[0] != (seat.SourceHead{Repository: i888App, Trunk: MergeBranch, Head: i888Merge1}) ||
|
||||
a.Repositories[1].Repository != i888Other || a.Repositories[1].Head != i888OtherH {
|
||||
t.Fatalf("source answers %+v", a)
|
||||
}
|
||||
// §690: given no branch it answers the branches of the keys it keeps, and no repository; a branch it keeps no key of has none.
|
||||
if a := w.h.SourceRevision(""); a.Error != "" || !reflect.DeepEqual(a.Branches, []string{MergeBranch}) || len(a.Repositories) != 0 || a.Revision != 0 {
|
||||
t.Fatalf("source with no branch answers %+v, want the branches %v alone", a, []string{MergeBranch})
|
||||
}
|
||||
if a := w.h.SourceRevision("beta"); a.Error != "" || a.Branch != "beta" || a.Revision != 0 || len(a.Repositories) != 0 {
|
||||
t.Fatalf("source of a branch with no key answers %+v", a)
|
||||
}
|
||||
n := len(w.source.moves)
|
||||
w.settleAll()
|
||||
w.h.PullMerged(i888MergedAt(i888App, 3, i888Head1, i888Merge1, w.now.Add(-time.Minute))) // heard again
|
||||
|
||||
@@ -345,8 +345,9 @@ func tools(h *Holder, l *listening) []stdio.Tool {
|
||||
Description: "The source revision the gate holds: every repository it names, its trunk branch and that trunk's head, the " +
|
||||
"group that moved it last and its revision. Read, never written: a node-engine reads the repositories here to call a " +
|
||||
"reverted build's catch-up tool once per repository, never the gate's state.",
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{}},
|
||||
Run: func(map[string]any) (any, error) { return h.SourceRevision(), nil }},
|
||||
Input: map[string]any{"type": "object", "properties": map[string]any{
|
||||
"branch": str("the watched branch, escaped as a key name is; absent: the list of the branches the gate keeps a source revision of")}},
|
||||
Run: func(a map[string]any) (any, error) { return h.SourceRevision(strArg(a, "branch")), nil }},
|
||||
{Name: seat + "checks",
|
||||
Description: "What the mesh's checks said of a pull request's head or of one commit: each of the commit's " +
|
||||
"statuses (mesh/check, mesh/delivery, …) with its state, description and when it " +
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
// The gate merges through the forge bridge, never itself (novox/hq ADR 0437 §8, ADR 0441 §5, ADR 0450 §3). It writes into
|
||||
@@ -48,13 +49,15 @@ func (kvOld) Get(state, key string) ([]byte, uint64, error) {
|
||||
return e.Value, e.Revision, nil
|
||||
}
|
||||
|
||||
// SourceStore is the gate's bucket source, one key, moved by compare-and-set.
|
||||
// SourceStore is the gate's bucket source, one key per watched branch (design 47 G §15), moved by compare-and-set.
|
||||
type SourceStore interface {
|
||||
// Move reads source.main and writes what update makes of it, unless update says nothing is to be written; a write
|
||||
// another writer won is read again and tried again.
|
||||
// Move reads the source key of the gate's merge branch (MergeBranch) and writes what update makes of it, unless update
|
||||
// says nothing is to be written; a write another writer won is read again and tried again.
|
||||
Move(update func(prev *Source) (Source, bool)) error
|
||||
// Read is source.main as held and its revision on the bus; nil and 0 before it is first written.
|
||||
Read() (*Source, uint64, error)
|
||||
// Read is a branch's source key as held and its revision on the bus; nil and 0 before it is first written.
|
||||
Read(branch string) (*Source, uint64, error)
|
||||
// Branches are the branch names of the source keys the gate keeps (§614, §690).
|
||||
Branches() ([]string, error)
|
||||
}
|
||||
|
||||
// kvMerges is merges over the runtime.
|
||||
@@ -95,7 +98,7 @@ type kvSource struct{}
|
||||
func (kvSource) Move(update func(prev *Source) (Source, bool)) error {
|
||||
state := stdio.State("source")
|
||||
for range changeAttempts {
|
||||
e, err := state.Get(mdc.SourceMainKey)
|
||||
e, err := state.Get(mdc.SourceKey(MergeBranch))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,9 +119,9 @@ func (kvSource) Move(update func(prev *Source) (Source, bool)) error {
|
||||
return err
|
||||
}
|
||||
if e == nil {
|
||||
_, err = state.Create(mdc.SourceMainKey, json.RawMessage(raw))
|
||||
_, err = state.Create(mdc.SourceKey(MergeBranch), json.RawMessage(raw))
|
||||
} else {
|
||||
_, err = state.Update(mdc.SourceMainKey, json.RawMessage(raw), e.Revision)
|
||||
_, err = state.Update(mdc.SourceKey(MergeBranch), json.RawMessage(raw), e.Revision)
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
@@ -130,33 +133,64 @@ func (kvSource) Move(update func(prev *Source) (Source, bool)) error {
|
||||
return fmt.Errorf("source.main was written by another writer first %d times: %w", changeAttempts, stdio.ErrChanged)
|
||||
}
|
||||
|
||||
func (kvSource) Read() (*Source, uint64, error) {
|
||||
e, err := stdio.State("source").Get(mdc.SourceMainKey)
|
||||
func (kvSource) Read(branch string) (*Source, uint64, error) {
|
||||
e, err := stdio.State("source").Get(mdc.SourceKey(branch))
|
||||
if err != nil || e == nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
held, err := mdc.DecodeSource(e.Value)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("source.main is not readable: %w", err)
|
||||
return nil, 0, fmt.Errorf("source.%s is not readable: %w", branch, err)
|
||||
}
|
||||
return &held, e.Revision, nil
|
||||
}
|
||||
|
||||
// SourceRevision is the seat's verb source: every repository source.main names, its trunk branch and head, the group that
|
||||
// moved it last and its revision, or why it cannot be read. source.main names each repository's main only (MergeBranch).
|
||||
// Outside the lock.
|
||||
func (h *Holder) SourceRevision() seat.SourceAnswer {
|
||||
func (kvSource) Branches() ([]string, error) {
|
||||
names, err := stdio.State("source").Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []string
|
||||
for _, key := range names {
|
||||
escaped, ok := seat.ParseSourceByBranchKey(key)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
branch, err := keys.UnescapeName(escaped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the source key %q is not a branch's: %w", key, err)
|
||||
}
|
||||
out = append(out, branch)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SourceRevision is the seat's verb source (§690): given a branch, every repository that branch's source key names, its
|
||||
// trunk branch and head, the group that moved it last and its revision, or why it cannot be read; given none, `branches`,
|
||||
// the branch of each source key the gate keeps. A key names each repository's merge branch only (MergeBranch). Outside the lock.
|
||||
func (h *Holder) SourceRevision(branch string) seat.SourceAnswer {
|
||||
if h.Source == nil {
|
||||
return seat.SourceAnswer{Error: "no source bucket"}
|
||||
}
|
||||
held, revision, err := h.Source.Read()
|
||||
if branch == "" {
|
||||
branches, err := h.Source.Branches()
|
||||
if err != nil {
|
||||
return seat.SourceAnswer{Error: err.Error()}
|
||||
}
|
||||
if branches == nil {
|
||||
branches = []string{}
|
||||
}
|
||||
return seat.SourceAnswer{Branches: branches}
|
||||
}
|
||||
held, revision, err := h.Source.Read(branch)
|
||||
if err != nil {
|
||||
return seat.SourceAnswer{Error: err.Error()}
|
||||
}
|
||||
if held == nil {
|
||||
return seat.SourceAnswer{}
|
||||
return seat.SourceAnswer{Branch: branch}
|
||||
}
|
||||
out := seat.SourceAnswer{Group: held.Group, Revision: int64(revision)}
|
||||
out := seat.SourceAnswer{Branch: branch, Group: held.Group, Revision: int64(revision)}
|
||||
for repository, head := range held.Heads {
|
||||
out.Repositories = append(out.Repositories, seat.SourceHead{Repository: repository, Trunk: MergeBranch, Head: head})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
// are converted at the edge, so a stored value decodes unchanged (novox/hq ADR 0634 §2).
|
||||
type (
|
||||
// Source is the state source's value.
|
||||
Source = seat.SourceByMainValue
|
||||
Source = seat.SourceByBranchValue
|
||||
// Owed is the state owed's value: what the forge bridge performs.
|
||||
Owed = seat.OwedByRepositoryNumberHeadActValue
|
||||
// Answer is the operator's answer as the gate acts on it.
|
||||
|
||||
Reference in New Issue
Block a user