diff --git a/go/compose/frombus/answer.go b/go/compose/frombus/answer.go index 71e6f22..64eb6d2 100644 --- a/go/compose/frombus/answer.go +++ b/go/compose/frombus/answer.go @@ -240,7 +240,9 @@ const MaxShown = 256 << 10 var ( grantStandIn = regexp.MustCompile(`^the grant of \S+ for (\S+) on (\S+)$`) - needsStandIn = regexp.MustCompile(`^(\S+) on (\S+) needs `) + // grantOfStandIn is the provision a grant's stand-in is of. + grantOfStandIn = regexp.MustCompile(`^the grant of (\S+) for \S+ on \S+$`) + needsStandIn = regexp.MustCompile(`^(\S+) on (\S+) needs `) ) // standsInFor is the node and module a stand-in of compose's words is for: an own secret `module/name`, a port @@ -360,6 +362,16 @@ func composeEach(in compose.Input, node string, fps map[string]string, applied b if _, done := failed[m]; !done { hold(m, "stands in for "+strings.Join(of[m], ", "), "") again = true + continue + } + // Already held, and still a stand-in in another module's composition: a grant not yet issued for what + // it contributes (a module held for its own secret keeps its contributions, leaveOwn). Its contribution + // to that provision is left out, so the provider never applies a stand-in (issue 774, review round 1). + for _, x := range of[m] { + if g := grantOfStandIn.FindStringSubmatch(x); g != nil && !left[m][g[1]] { + hold(m, failed[m], g[1]) + again = true + } } } if !again && len(nodes) > 0 { diff --git a/go/compose/frombus/held_rows_test.go b/go/compose/frombus/held_rows_test.go index 5566683..87959d1 100644 --- a/go/compose/frombus/held_rows_test.go +++ b/go/compose/frombus/held_rows_test.go @@ -179,16 +179,25 @@ func aHeldModuleOfItsOwnAccountNeverRefusesTheNode(t *testing.T) { issued []string // the own-account modules whose broker is issued building bool // node-tools' build is not done, and nothing of it runs held map[string]string // the modules held, each with words of the reason it was first held for + // ungranted gives the provider a grants file and no consumer's grant is issued yet (a newly placed module's + // usual state): a held module's contribution then stands in for its grant, and is left out (review round 1). + ungranted bool }{ {name: "none issued", held: map[string]string{"check": "broker", "heal": "broker"}}, + {name: "none issued, no grant issued", ungranted: true, held: map[string]string{"check": "broker", "heal": "broker"}}, {name: "one issued", issued: []string{"heal"}, held: map[string]string{"check": "broker"}}, {name: "both issued", issued: []string{"check", "heal"}}, // The runtime held: its stand-in is not refused for its bundle; the modules it would run are held, not the node. {name: "the runtime's build not done", issued: []string{"check", "heal"}, building: true, held: map[string]string{"node-tools": "its build produced 0 bundle(s)", "check": "not here to run it from", "heal": "not here to run it from"}}, } { - b, fps := aNode(t, store, tools, own("check"), lamp, own("heal")) - b.held = true // every grant held, so a contribution's credential stands in for nothing + provider := store + if row.ungranted { + provider = strings.Replace(store, `"receives":`, `"grants":{"artifact-store":"/var/lib/artifacts/grants"},"receives":`, 1) + } + b, fps := aNode(t, provider, tools, own("check"), lamp, own("heal")) + // Every grant held, so the held modules' contributions are composed whole; the row ungranted issues none. + b.held = !row.ungranted b.own = []compose.OwnSecretRow{{Node: "n1", Module: "node-tools", Name: "broker", Origin: "made", SealedTo: "sk", Sealed: `"box"`}} for _, m := range row.issued { b.own = append(b.own, compose.OwnSecretRow{Node: "n1", Module: m, Name: "broker", Origin: "made", SealedTo: "sk", Sealed: `"box"`}) @@ -216,7 +225,17 @@ func aHeldModuleOfItsOwnAccountNeverRefusesTheNode(t *testing.T) { // of postgres's received file, and postgres's helper would have locked its login): only its own secrets are left // out of its stand-in, never its contributions, so the provider's received file names every consumer. storeKey, _ := delivery.ComposedKey("n1", "artifacts", fps["artifacts"]) + // And never a stand-in for a grant not yet issued: the provider is applied, so a held module whose grant is + // not issued is composed without that contribution, never with "foreseen: made by the next send". + for key, c := range out { + if got, _ := json.Marshal(c.Resources); strings.Contains(string(got), "foreseen") { + t.Errorf("%s: %s applies a stand-in: %s", row.name, key, got) + } + } for _, m := range []string{"check", "heal"} { + if row.ungranted { + break + } if got, _ := json.Marshal(out[storeKey].Resources); !strings.Contains(string(got), `\"from\": \"`+m+`\"`) { t.Errorf("%s: the provider's received file drops %s's contribution: %s", row.name, m, got) }