From 0282229fd974618c3f06b820e6c40d393baed5c1 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 18:20:27 +0100 Subject: [PATCH] =?UTF-8?q?Name=20the=20placement=20ask=20and=20a=20propos?= =?UTF-8?q?al's=20again=20and=20warrant,=20so=20the=20holder=20asks=20thro?= =?UTF-8?q?ugh=20the=20teller=20as=20its=20other=20asks=20(ADR=200567=20?= =?UTF-8?q?=C2=A75,=20=C2=A710,=20issue=201126)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/delivery/placement_proposal.go | 12 ++++++++---- go/delivery/view_answers.go | 26 ++++++++++++++++++++++---- go/delivery/view_answers_test.go | 14 +++++++++++--- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/go/delivery/placement_proposal.go b/go/delivery/placement_proposal.go index 0b079ff..bb32776 100644 --- a/go/delivery/placement_proposal.go +++ b/go/delivery/placement_proposal.go @@ -361,6 +361,9 @@ type PlacementProposal struct { Ask string `json:"ask,omitempty"` // Words is what happened last, in words (laid again over moved keys, asked again). Words string `json:"words,omitempty"` + // Again is when `again` asked the holder to judge it now and raise its ask again (§10); the holder does once per + // value. + Again time.Time `json:"again,omitempty"` } // Nodes is every node whose key the proposal changes, sorted. @@ -447,11 +450,12 @@ func ChangesDigest(p PlacementProposal) string { return "sha256:" + hex.EncodeToString(sum[:]) } -// PlacementWarrant is who approved a proposal, through which channel and when (§5, §6), and the ask it answered. +// PlacementWarrant is who approved a proposal, through which channel and when (§5, §6): the warrant's id as the teller +// wrote it (the ask's id and when), and its words, which name the person and the channel that proved them +// (asks.Warrant.Says). type PlacementWarrant struct { - Ask string `json:"ask"` - Who string `json:"who"` - Channel string `json:"channel"` + Warrant string `json:"warrant"` + Words string `json:"words"` At time.Time `json:"at"` } diff --git a/go/delivery/view_answers.go b/go/delivery/view_answers.go index 5109830..352a417 100644 --- a/go/delivery/view_answers.go +++ b/go/delivery/view_answers.go @@ -38,14 +38,18 @@ const ( // AskKindRecord is the kind of the assigner's ask about a move a person must allow, for a module of policy record: // a composition judged to recreate a container or move data, or one that cannot be judged (ADR 0539 clause 14). AskKindRecord = "record" + // AskKindPlacement is the kind of the seat mesh-placement's authorising ask about a proposal (novox/hq ADR 0567 §5): + // `placement.`. Its yes is an Approve that only a channel proving who answered takes, never the view. + AskKindPlacement = "placement" viewWord = "view" ) // AskKey is an ask's key read whole: its Kind, and the parts its kind has: a merge's Group and heads Hash, a hold's // Module and fingerprint FP, a restore's and a backup's Node, Module and FP, a record's Node, Module and the digest -// Composed of the composition it would allow. +// Composed of the composition it would allow, a placement's Proposal. type AskKey struct { + Proposal string Kind string Group string Hash string @@ -59,13 +63,13 @@ type AskKey struct { // merge's Approve does (ADR 0421 §1, issue 953), the assigner's hold and restore do not. An unknown kind is refused. func AskNeedsProof(kind string) (bool, error) { switch kind { - case AskKindMerge: + case AskKindMerge, AskKindPlacement: return true, nil case AskKindHold, AskKindRestore, AskKindBackup, AskKindRecord: return false, nil } - return false, fmt.Errorf("delivery: %q is not a kind of ask, %s, %s, %s, %s or %s", kind, AskKindMerge, AskKindHold, - AskKindRestore, AskKindBackup, AskKindRecord) + return false, fmt.Errorf("delivery: %q is not a kind of ask, %s, %s, %s, %s, %s or %s", kind, AskKindMerge, AskKindHold, + AskKindRestore, AskKindBackup, AskKindRecord, AskKindPlacement) } // HoldAskKey is the key, in AssignerAsksBucket, of the ask about a module's held fingerprint: `hold..`. @@ -100,6 +104,15 @@ func BackupAskKey(node, module, fingerprint string) (string, error) { return AskKindBackup + "." + key, nil } +// PlacementAskKey is the key, in AssignerAsksBucket, of the authorising ask about a proposal of placement: +// `placement.` (ADR 0567 §5). +func PlacementAskKey(proposal string) (string, error) { + if err := CheckProposalID(proposal); err != nil { + return "", err + } + return AskKindPlacement + "." + proposal, nil +} + // RecordAskKey is the key, in AssignerAsksBucket, of the ask about a move of a module on a node to the composition // whose digest it names: `record...`, so a new revision at the same fingerprint is asked // again (ADR 0539 clause 14). @@ -154,6 +167,11 @@ func ParseAnyAskKey(key string) (AskKey, error) { return refused(fmt.Errorf("a backup ask is backup...")) } return AskKey{Kind: kind, Node: node, Module: module, FP: fp}, nil + case AskKindPlacement: + if err := CheckProposalID(rest); err != nil { + return refused(err) + } + return AskKey{Kind: kind, Proposal: rest}, nil case AskKindRecord: parts := strings.Split(rest, ".") if len(parts) != 3 { diff --git a/go/delivery/view_answers_test.go b/go/delivery/view_answers_test.go index 1a9b824..56f2701 100644 --- a/go/delivery/view_answers_test.go +++ b/go/delivery/view_answers_test.go @@ -33,9 +33,17 @@ func TestParseAnyAskKey(t *testing.T) { if err != nil || restore != "restore.ace.mesh-mail."+strings.Repeat("d", 64) { t.Fatalf("RestoreAskKey = %q, %v", restore, err) } + placement, err := PlacementAskKey("p-one") + if err != nil || placement != "placement.p-one" { + t.Fatalf("PlacementAskKey = %q, %v", placement, err) + } + if proof, err := AskNeedsProof(AskKindPlacement); !proof || err != nil { + t.Error("a placement ask may be answered without proof of who answered") + } for key, want := range map[string]AskKey{ - hold: {Kind: AskKindHold, Module: "mesh-mail", FP: fp}, - restore: {Kind: AskKindRestore, Node: "ace", Module: "mesh-mail", FP: fp}, + hold: {Kind: AskKindHold, Module: "mesh-mail", FP: fp}, + restore: {Kind: AskKindRestore, Node: "ace", Module: "mesh-mail", FP: fp}, + placement: {Kind: AskKindPlacement, Proposal: "p-one"}, } { if got, err := ParseAnyAskKey(key); err != nil || got != want { t.Errorf("ParseAnyAskKey(%q) = %+v, %v", key, got, err) @@ -44,7 +52,7 @@ func TestParseAnyAskKey(t *testing.T) { if got, err := ParseAnyAskKey(merge); err != nil || got.Kind != AskKindMerge || got.Group != "feat/r4g" || len(got.Hash) != 12 { t.Errorf("ParseAnyAskKey(%q) = %+v, %v", merge, got, err) } - for _, bad := range []string{"want.ace.mesh-mail", "hold.mesh-mail", "restore.mesh-mail." + strings.Repeat("d", 64), "approve.x.y", "restore.a.b.c.d", ""} { + for _, bad := range []string{"want.ace.mesh-mail", "hold.mesh-mail", "restore.mesh-mail." + strings.Repeat("d", 64), "approve.x.y", "restore.a.b.c.d", "placement.P", ""} { if _, err := ParseAnyAskKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseAnyAskKey(%q) = %v, want a refusal naming it", bad, err) }