The switch says every act the old states owed and merges hold under its switched id, so the forge bridge performs only what the old bridge did not (milestone 47/G, W11.17d carry)
Every key of owed, and the member merging in every key of merges, is said as owed under meshdeliveryclient.SwitchedActID(state, key, revision), at every switch, the same act each time. The switch reads the old states through one reader (OldState). Rows in the switch's test. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ (Joined from dd0c4294.) Builds: design 47 G §169 (GATE5, GATE6); only the owed carry: the member merging in the old state merges is not carried as a merge act (G §167, §542) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -115,6 +115,47 @@ func (m *fakeMerges) last(group string) Merge {
|
||||
return m.values[group]
|
||||
}
|
||||
|
||||
// fakeOld is the gate's old states merges and owed, which only the switch reads.
|
||||
type fakeOld struct {
|
||||
mu sync.Mutex
|
||||
states map[string]map[string]oldEntry
|
||||
}
|
||||
|
||||
type oldEntry struct {
|
||||
raw []byte
|
||||
revision uint64
|
||||
}
|
||||
|
||||
func (o *fakeOld) put(state, key string, raw []byte, revision uint64) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
if o.states[state] == nil {
|
||||
o.states[state] = map[string]oldEntry{}
|
||||
}
|
||||
o.states[state][key] = oldEntry{raw: raw, revision: revision}
|
||||
}
|
||||
|
||||
func (o *fakeOld) Keys(state string) ([]string, error) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
var out []string
|
||||
for k := range o.states[state] {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (o *fakeOld) Get(state, key string) ([]byte, uint64, error) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
e, ok := o.states[state][key]
|
||||
if !ok {
|
||||
return nil, 0, nil
|
||||
}
|
||||
return e.raw, e.revision, nil
|
||||
}
|
||||
|
||||
// fakeSource is the gate's bucket source with its one key.
|
||||
type fakeSource struct {
|
||||
mu sync.Mutex
|
||||
@@ -174,6 +215,12 @@ func newFakeOwed(f *fakeForge) *fakeOwed {
|
||||
}
|
||||
|
||||
func (o *fakeOwed) Say(v seat.Owed) error {
|
||||
if _, _, _, switched := mdc.ParseSwitchedActID(v.ID); switched {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
o.said = append(o.said, v) // the bridge counts it against what the old bridge performed
|
||||
return nil
|
||||
}
|
||||
key, _, found := strings.Cut(v.ID, "@")
|
||||
if !found {
|
||||
return fmt.Errorf("the act's id %q holds no @", v.ID)
|
||||
|
||||
@@ -287,6 +287,7 @@ type world struct {
|
||||
forge *fakeForge
|
||||
op *fakeOperator
|
||||
merges *fakeMerges
|
||||
old *fakeOld
|
||||
source *fakeSource
|
||||
owed *fakeOwed
|
||||
// asked is the key of the ask a test answered.
|
||||
@@ -320,7 +321,8 @@ func newWorldOver(t *testing.T, store *memStore, forge *fakeForge, now time.Time
|
||||
w.op.auto = true
|
||||
w.h.Operator = w.op
|
||||
w.merges, w.source = newFakeMerges(forge), &fakeSource{}
|
||||
w.h.Merges, w.h.Source = w.merges, w.source
|
||||
w.old = &fakeOld{states: map[string]map[string]oldEntry{}}
|
||||
w.h.Merges, w.h.Old, w.h.Source = w.merges, w.old, w.source
|
||||
w.merges.h = w.h
|
||||
w.owed = newFakeOwed(forge)
|
||||
w.owed.h = w.h
|
||||
|
||||
@@ -35,6 +35,7 @@ type Holder struct {
|
||||
// (approval.go); Merges and Source are the gate's merges and source (merges.go).
|
||||
Operator Operator
|
||||
Merges MergeStore
|
||||
Old OldState
|
||||
Source SourceStore
|
||||
// Owed says what a transition owes the forge as the seat's event owed (owed.go).
|
||||
Owed OwedSayer
|
||||
|
||||
@@ -77,6 +77,7 @@ func main() {
|
||||
h.SaySeat = stdio.SeatPublish
|
||||
h.Records = &Records{Ledger: sdkLedger{}, Now: h.Now, Retention: h.recordRetention, Say: stdio.SeatPublish}
|
||||
h.Merges = recordedMerges{MergeStore: kvMerges{}, records: h.Records, members: h.groupMembers}
|
||||
h.Old = kvOld{}
|
||||
h.Consumer = &StepConsumer{Records: h.Records, Families: stepFamilies, Now: h.Now, Logf: logf}
|
||||
l := &listening{now: "not yet: starting"}
|
||||
go run(h, l)
|
||||
|
||||
@@ -28,6 +28,26 @@ type MergeStore interface {
|
||||
Put(group string, m Merge) error
|
||||
}
|
||||
|
||||
// OldState reads the gate's old state owed, which the switch says again as switched acts (switch.go): each key, and one
|
||||
// key's value with its revision.
|
||||
type OldState interface {
|
||||
Keys(state string) ([]string, error)
|
||||
Get(state, key string) (value []byte, revision uint64, err error)
|
||||
}
|
||||
|
||||
// kvOld is the old state over the runtime.
|
||||
type kvOld struct{}
|
||||
|
||||
func (kvOld) Keys(state string) ([]string, error) { return stdio.State(state).Keys() }
|
||||
|
||||
func (kvOld) Get(state, key string) ([]byte, uint64, error) {
|
||||
e, err := stdio.State(state).Get(key)
|
||||
if err != nil || e == nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return e.Value, e.Revision, nil
|
||||
}
|
||||
|
||||
// SourceStore is the gate's bucket source, one key, 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
|
||||
|
||||
@@ -541,15 +541,36 @@ func TestTheSwitchCarriesTheGroupsInFlightIntoRecordsOnce(t *testing.T) {
|
||||
l := newLedgerFake(t)
|
||||
w.h.Records = &Records{Ledger: l, Now: w.h.Now}
|
||||
w.h.Merges = recordedMerges{MergeStore: w.merges, records: w.h.Records, members: w.h.groupMembers}
|
||||
// W11.17d: what the old state owed is said as switched acts: every key of owed (G §169).
|
||||
statusKey, _ := mdc.OwedKey("novox/app", 7, readyHead, statusAct("mesh/delivery"))
|
||||
w.old.put(mdc.SwitchedOwed, statusKey, []byte(`{"act":"status","context":"mesh/delivery","state":"success","text":"ready","target":"none"}`), 4)
|
||||
switched := func() []meshdelivery.Owed {
|
||||
var out []meshdelivery.Owed
|
||||
for _, o := range w.owed.said {
|
||||
if _, _, _, ok := mdc.ParseSwitchedActID(o.ID); ok {
|
||||
out = append(out, o)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
opened, found, err := w.h.Switch()
|
||||
if err != nil || opened != 1 || found != 0 {
|
||||
t.Fatalf("the first switch: %d opened, %d found, %v", opened, found, err)
|
||||
}
|
||||
carried := switched()
|
||||
wantStatus, _ := mdc.SwitchedActID(mdc.SwitchedOwed, statusKey, 4)
|
||||
if len(carried) != 1 || carried[0].ID != wantStatus || carried[0].Act != meshdelivery.OwedActStatus || carried[0].Context != "mesh/delivery" ||
|
||||
carried[0].Commit != readyHead {
|
||||
t.Fatalf("the switch carried %+v", carried)
|
||||
}
|
||||
asked := w.op.count()
|
||||
opened, found, err = w.h.Switch()
|
||||
if err != nil || opened != 0 || found != 1 || w.op.count() != asked {
|
||||
t.Fatalf("the switch again: %d opened, %d found, %v, %d asks (was %d)", opened, found, err, w.op.count(), asked)
|
||||
}
|
||||
if again := switched(); len(again) != 2 || again[1].ID != wantStatus {
|
||||
t.Fatalf("the switch again said %+v, wanted the same act under the same id", again)
|
||||
}
|
||||
w.op.answer(w.op.open()[0], OptionApprove)
|
||||
w.settleAll()
|
||||
w.settleAll()
|
||||
|
||||
@@ -8,6 +8,8 @@ package main
|
||||
// second record and asks nothing again.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
@@ -123,9 +125,67 @@ func (h *Holder) Switch() (opened, found int, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := h.carryOwed(); err != nil {
|
||||
return opened, found, err
|
||||
}
|
||||
return opened, found, nil
|
||||
}
|
||||
|
||||
// oldMerge is a group's merge as the old state merges holds it, with its key and revision; nil when it holds none.
|
||||
func (h *Holder) oldMerge(group string) (*Merge, uint64, error) {
|
||||
if h.Old == nil {
|
||||
return nil, 0, nil
|
||||
}
|
||||
key, err := mdc.MergesKey(group)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
raw, rev, err := h.Old.Get(mdc.SwitchedMerges, key)
|
||||
if err != nil || raw == nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
m, err := DecodeMerge(raw)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return &m, rev, nil
|
||||
}
|
||||
|
||||
// carryOwed says every act the old state owed holds as owed, under its switched id (the state, the key and its revision:
|
||||
// meshdeliveryclient.SwitchedActID), so the forge bridge performs what the old bridge did not and counts as performed what
|
||||
// it did (design 47 G §169). No merge act is made from the old state merges (G §167, §542). Said at every switch, it is the same act each time. A key that cannot be read as an act is said
|
||||
// as refused and skipped.
|
||||
func (h *Holder) carryOwed() error {
|
||||
if h.Old == nil || h.Owed == nil {
|
||||
return nil
|
||||
}
|
||||
now := h.Now().UTC()
|
||||
keys, err := h.Old.Keys(mdc.SwitchedOwed)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the state owed cannot be read for the switch: %w", err)
|
||||
}
|
||||
for _, key := range keys {
|
||||
raw, rev, err := h.Old.Get(mdc.SwitchedOwed, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("the owed act %s cannot be read for the switch: %w", key, err)
|
||||
}
|
||||
if raw == nil {
|
||||
continue
|
||||
}
|
||||
repository, number, head, _, kerr := mdc.ParseOwedKey(key)
|
||||
var o Owed
|
||||
id, ierr := mdc.SwitchedActID(mdc.SwitchedOwed, key, rev)
|
||||
if derr := json.Unmarshal(raw, &o); kerr != nil || derr != nil || ierr != nil {
|
||||
h.Refuse(fmt.Errorf("the owed act %s is not carried by the switch: %v", key, errors.Join(kerr, derr, ierr)))
|
||||
continue
|
||||
}
|
||||
if err := h.Owed.Say(seatOwedOf(o, id, repository, number, head, &now)); err != nil {
|
||||
return fmt.Errorf("the owed act %s cannot be said for the switch: %w", key, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// groupMembers are the members' ids of the milestone a merge is handed under, under the lock.
|
||||
func (h *Holder) groupMembers(name string) []string {
|
||||
h.mu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user