1966 review round 1: Composed counts the bindings written or rewritten from fields; wrap Take's comment
mesh/delivery merged: source.main moved to novox/mesh-delivery@2f1b60f2a231
mesh/check checking 7db5b0dfe479 on 2f1b60f2a231

Builds: design 25 bindings-first-view §6, §8, §9

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 14:12:25 +01:00
parent ca9aad9757
commit 7db5b0dfe4
3 changed files with 16 additions and 10 deletions
+4 -1
View File
@@ -3165,8 +3165,11 @@ func TestGrants(t *testing.T) {
want: map[string]string{}, status: [3]int{0, 0, 1}, said: map[string]string{}}, want: map[string]string{}, status: [3]int{0, 0, 1}, said: map[string]string{}},
// §9 (1966/AC203-3..): an answer no entry of which carries a binding. // §9 (1966/AC203-3..): an answer no entry of which carries a binding.
{name: "§9 a key-value entry the bus holds is composed from the answer's fields, and said once per answer", answer: []any{oldBusModule}, {name: "§9 a key-value entry the bus holds is composed from the answer's fields, and said once per answer", answer: []any{oldBusModule},
want: map[string]string{"late": "mod-x_late"}, status: [3]int{1, 0, 1}, composed: 1, noted: 2, again: []any{oldBusModule}, want: map[string]string{"late": "mod-x_late"}, status: [3]int{1, 0, 1}, composed: 0, noted: 2, again: []any{oldBusModule},
wantAgn: map[string]string{"late": "mod-x_late"}}, wantAgn: map[string]string{"late": "mod-x_late"}},
{name: "§9 an entry an event named since the ask is not written, so it is not counted composed", answer: []any{oldBusModule},
during: func(st *setup) { hear(st, "created", "late", "mod-x_event") }, want: map[string]string{"late": "mod-x_event"},
status: [3]int{0, 0, 1}, noted: 1},
{name: "§9 an object-store entry is composed with its object store", answer: []any{noBinding("media", "object-store", "OBJ_", true)}, {name: "§9 an object-store entry is composed with its object store", answer: []any{noBinding("media", "object-store", "OBJ_", true)},
want: map[string]string{"media": "mod-x_media"}, status: [3]int{1, 0, 1}, composed: 1, noted: 1}, want: map[string]string{"media": "mod-x_media"}, status: [3]int{1, 0, 1}, composed: 1, noted: 1},
{name: "§9 a stream and a ledger are left unbound and logged", answer: []any{noBinding("s", "stream", "", true), noBinding("l", "ledger", "", true)}, {name: "§9 a stream and a ledger are left unbound and logged", answer: []any{noBinding("s", "stream", "", true), noBinding("l", "ledger", "", true)},
@@ -43,7 +43,7 @@ type BindingsStatus struct {
Listed int `json:"listed"` Listed int `json:"listed"`
Written int `json:"written"` Written int `json:"written"`
Rewritten int `json:"rewritten"` Rewritten int `json:"rewritten"`
// Composed is how many entries of the last answer had their binding composed from the answer's own fields, and Unbound // Composed is how many of the bindings the last answer wrote or rewrote were composed from its own fields, and Unbound
// the entries it left unbound, `<module>/<local name> (<storage type>)`: both only for an answer no entry of which // the entries it left unbound, `<module>/<local name> (<storage type>)`: both only for an answer no entry of which
// carries `binding` (§9). // carries `binding` (§9).
Composed int `json:"composed"` Composed int `json:"composed"`
@@ -329,7 +329,7 @@ func (v *BindingsView) apply(p *Part, raw json.RawMessage) error {
} }
entries, composed, unbound := v.compose(answer.Entries) entries, composed, unbound := v.compose(answer.Entries)
p.bindMu.Lock() p.bindMu.Lock()
written, rewritten, err := v.take(p, entries, composed) written, rewritten, fromFields, err := v.take(p, entries, composed)
p.bindMu.Unlock() p.bindMu.Unlock()
if err != nil { if err != nil {
return err return err
@@ -340,7 +340,7 @@ func (v *BindingsView) apply(p *Part, raw json.RawMessage) error {
v.st.Answered = v.now() v.st.Answered = v.now()
v.st.Listed = len(answer.Entries) v.st.Listed = len(answer.Entries)
v.st.Written, v.st.Rewritten = written, rewritten v.st.Written, v.st.Rewritten = written, rewritten
v.st.Composed, v.st.Unbound = len(composed), unbound v.st.Composed, v.st.Unbound = fromFields, unbound
status := v.statusLocked() status := v.statusLocked()
v.mu.Unlock() v.mu.Unlock()
if wasRaised { if wasRaised {
@@ -401,7 +401,7 @@ func composeBinding(e meshbroker.Held) (*meshbroker.Binding, bool) {
// take writes the bindings of the entries an event did not name since the ask was sent, keeps every binding the answer does // take writes the bindings of the entries an event did not name since the ask was sent, keeps every binding the answer does
// not list and raises its unlisted condition, and answers how many bindings it wrote and rewrote; composed names the // not list and raises its unlisted condition, and answers how many bindings it wrote and rewrote; composed names the
// entries whose binding compose gave them. The caller holds the part's bindMu. // entries whose binding compose gave them. The caller holds the part's bindMu.
func (v *BindingsView) take(p *Part, entries []meshbroker.Held, composed map[string]bool) (written, rewritten int, err error) { func (v *BindingsView) take(p *Part, entries []meshbroker.Held, composed map[string]bool) (written, rewritten, fromFields int, err error) {
v.mu.Lock() v.mu.Lock()
touched := copyOf(v.touched) touched := copyOf(v.touched)
v.mu.Unlock() v.mu.Unlock()
@@ -425,7 +425,7 @@ func (v *BindingsView) take(p *Part, entries []meshbroker.Held, composed map[str
} }
b, berr := p.bound(module) b, berr := p.bound(module)
if berr != nil { if berr != nil {
return written, rewritten, berr return written, rewritten, fromFields, berr
} }
changed := false changed := false
for _, e := range byModule[module] { for _, e := range byModule[module] {
@@ -447,6 +447,9 @@ func (v *BindingsView) take(p *Part, entries []meshbroker.Held, composed map[str
} }
b.Bindings[e.LocalName] = bind b.Bindings[e.LocalName] = bind
changed = true changed = true
if composed[localKey(module, e.LocalName)] {
fromFields++
}
if had { if had {
rewritten++ rewritten++
v.logf("the binding of %s %s was rewritten, the cause %s", module, e.LocalName, cause) v.logf("the binding of %s %s was rewritten, the cause %s", module, e.LocalName, cause)
@@ -458,11 +461,11 @@ func (v *BindingsView) take(p *Part, entries []meshbroker.Held, composed map[str
if changed { if changed {
raw, _ := json.Marshal(b) raw, _ := json.Marshal(b)
if _, perr := p.Store.Put(StateMemberships, BoundKey(module), raw); perr != nil { if _, perr := p.Store.Put(StateMemberships, BoundKey(module), raw); perr != nil {
return written, rewritten, fmt.Errorf("its own %s %s cannot be written: %w", StateMemberships, BoundKey(module), perr) return written, rewritten, fromFields, fmt.Errorf("its own %s %s cannot be written: %w", StateMemberships, BoundKey(module), perr)
} }
} }
} }
return written, rewritten, v.unlisted(p, listed, touched) return written, rewritten, fromFields, v.unlisted(p, listed, touched)
} }
// unlisted keeps every binding the answer does not list and raises its condition, one per binding; a binding it lists again // unlisted keeps every binding the answer does not list and raises its condition, one per binding; a binding it lists again
+2 -2
View File
@@ -239,8 +239,8 @@ func (r *Receiver[M]) arm(id string) error {
} }
// Take takes one chunk of a part from the transfer's opener: chunks of a part arrive in sequence from 0, the last // Take takes one chunk of a part from the transfer's opener: chunks of a part arrive in sequence from 0, the last
// marked, and a chunk 0 again starts the part afresh (its earlier chunks are dropped); the whole part must hash to its digest, else its chunks are dropped and the part is lacked again. Belongs, // marked, and a chunk 0 again starts the part afresh (its earlier chunks are dropped); the whole part must hash to its
// when given, refuses a transfer the caller's own shape says is not this call's (another seat's, say). When the last // digest, else its chunks are dropped and the part is lacked again. Belongs, when given, refuses a transfer the caller's own shape says is not this call's (another seat's, say). When the last
// lacked part arrives the transfer ends: the caller's check, then its store. // lacked part arrives the transfer ends: the caller's check, then its store.
func (r *Receiver[M]) Take(id, opener, digest string, sequence int, last bool, data []byte, belongs func(Transfer[M]) error) (Received, error) { func (r *Receiver[M]) Take(id, opener, digest string, sequence int, last bool, data []byte, belongs func(Transfer[M]) error) (Received, error) {
t, found, err := r.Transfers.Get(id) t, found, err := r.Transfers.Get(id)