An ask reads at a glance on the phone: a headline, a summary and its points, the rest in a note (issues 383 and 639) #278
@@ -2,6 +2,6 @@ module claude-code
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
|
||||
require gopkg.in/yaml.v3 v3.0.1
|
||||
|
||||
@@ -2,6 +2,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.13 h1:Su4JYpZ+zhNovkGA2DgxiZdcuHpjw2tPJzc/7P
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.13/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -2,4 +2,4 @@ module deskchannel
|
||||
|
||||
go 1.22
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
|
||||
@@ -2,3 +2,5 @@ git.novox.be/novox/mesh-sdk/go v0.1.10 h1:EHhnPRrkw7/CSQAqRsxYnTvlTZ3dI02IPO6WSm
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.10/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-knowledge
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
github.com/jackc/pgx/v5 v5.11.0
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.51.0
|
||||
|
||||
@@ -2,6 +2,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261011091942-4cf45b5ca0cf h1:ED/+RszK
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261011091942-4cf45b5ca0cf/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
@@ -136,7 +136,9 @@ condition's message. A warning kept quiet is also said once after a day.
|
||||
explanation alone — a summary, then at most three points (`asks.Glance`; an ask that does not hold to it is shown
|
||||
and said in the log). Everything else is the message's note (`asks.Message.Note`), which a channel shows apart and
|
||||
smaller: who asked and when, with what happens if nobody answers; what each answer does; where else it is answered;
|
||||
and the asker's Details (who proposed it, a fingerprint, how to read it whole). Answered, the message is one line —
|
||||
and the asker's Details (who proposed it, a fingerprint, how to read it whole) — on a kind that proves who answers
|
||||
and was given the ask's answers, its Details whole (`asks.Ask.WholeDetails`): every value an approval sets, held to
|
||||
the secret rule alone, as the whole words are. Answered, the message is one line —
|
||||
"You chose Approve at 16:06 on Telegram · <headline>" — and the router's word on the asker is its note.
|
||||
|
||||
**An ask's values are shown whole where the sender is proven** (hq issue 383). An ask may carry its explanation
|
||||
|
||||
@@ -172,22 +172,39 @@ func (h *Holder) openAsk(r *Record) *AskRecord {
|
||||
// rule, is shown. The ask's state does not matter: a message edited to say how the ask ended still shows what
|
||||
// was approved.
|
||||
func (h *Holder) wholeOf(r *Record, kind string) string {
|
||||
if a := h.provenAsk(r, kind); a != nil {
|
||||
return a.Whole
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// wholeDetailsOf is an ask's Details with their values whole (asks.Ask.WholeDetails), shown in the note in place of
|
||||
// its Details where wholeOf would show its whole words: every value an approval sets, so the operator reads exactly
|
||||
// what they approve (the review of mesh-controller #304). Empty elsewhere.
|
||||
func (h *Holder) wholeDetailsOf(r *Record, kind string) string {
|
||||
if a := h.provenAsk(r, kind); a != nil {
|
||||
return a.WholeDetails
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// provenAsk is the ask of an ask's own record when the kind proves who answers and was given the ask's answers;
|
||||
// nil otherwise.
|
||||
func (h *Holder) provenAsk(r *Record, kind string) *asks.Ask {
|
||||
if r == nil || r.Origin != "ask" || r.Ask == "" || kind == "" || h.Bench == nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
e := h.askOf(r.Ask)
|
||||
if e == nil || e.rec.Ask.Whole == "" {
|
||||
return ""
|
||||
}
|
||||
if !h.Bench.Promises(kind, "verified-sender") {
|
||||
return ""
|
||||
if e == nil || !h.Bench.Promises(kind, "verified-sender") {
|
||||
return nil
|
||||
}
|
||||
for _, tk := range e.rec.Tickets {
|
||||
if tk.Kind == kind {
|
||||
return e.rec.Ask.Whole
|
||||
a := e.rec.Ask
|
||||
return &a
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// carriers are the kinds that can carry an answer of a level now: ready, offering a choice, and for
|
||||
@@ -474,7 +491,7 @@ func (h *Holder) TakeAsk(verb, asker string, body json.RawMessage) error {
|
||||
// The whole words are shown only where the sender is proven (wholeOf), so a path or an address may stand
|
||||
// in them; a secret's shape leaves the mesh nowhere, and an ask carrying one is refused as a whole, never
|
||||
// shown with its explanation alone (novox/hq issue 383).
|
||||
if refusal, ok := CheckSecret(a.Whole); !ok {
|
||||
if refusal, ok := CheckSecret(a.Whole + "\n" + a.WholeDetails); !ok {
|
||||
h.refuseAsk(asker, a, "its whole words carry "+refusal.String()+", which may not leave the mesh on any channel: withhold it")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -885,11 +885,17 @@ func (h *Holder) sayOn(r *Record, what, kind string) Message {
|
||||
note = append(note, line)
|
||||
}
|
||||
}
|
||||
for _, line := range strings.Split(strings.TrimSpace(rec.Ask.Details), "\n") {
|
||||
// On a kind that proves who answers, the Details whole (issue 383): every value an approval sets, held to
|
||||
// the secret rule alone, as the whole words are.
|
||||
details, passes := rec.Ask.Details, Check
|
||||
if wd := h.wholeDetailsOf(r, kind); wd != "" {
|
||||
details, passes = wd, CheckSecret
|
||||
}
|
||||
for _, line := range strings.Split(strings.TrimSpace(details), "\n") {
|
||||
if line = strings.TrimSpace(line); line == "" {
|
||||
continue
|
||||
}
|
||||
if refusal, ok := Check(line); !ok {
|
||||
if refusal, ok := passes(line); !ok {
|
||||
line = "(a line carrying " + refusal.What + " is withheld here)"
|
||||
}
|
||||
note = append(note, line)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -153,3 +154,57 @@ func TestAnAskWhoseWholeWordsCarryASecretIsRefused(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The review of mesh-controller #304: a proposal of five keys, one of them 300 characters long, shows two keys at
|
||||
// a glance; every key and every value, whole, is in the note on a kind that proves who answers, so the operator can
|
||||
// read exactly what Approve sets. The desk, which proves nothing, shows them masked.
|
||||
func TestEveryKeyAndItsWholeValueIsInTheNoteWhereTheSenderIsProven(t *testing.T) {
|
||||
h, tg, _, c, store, _ := newRouter(t)
|
||||
link(h, store, c, "42", c.now().Add(-2*time.Hour))
|
||||
var long strings.Builder
|
||||
long.WriteString("library=/storage")
|
||||
for i := 0; long.Len() < 300; i++ {
|
||||
long.WriteString(fmt.Sprintf("/album-%02d", i))
|
||||
}
|
||||
value := long.String()[:300]
|
||||
keys := map[string]string{"a": "1", "b": "2", "c": "3", "d": "4", "shares": value}
|
||||
masked := map[string]string{"a": "1", "b": "2", "c": "3", "d": "4", "shares": "library=‹path›"}
|
||||
lines := func(values map[string]string) string {
|
||||
var out []string
|
||||
for _, k := range []string{"a", "b", "c", "d", "shares"} {
|
||||
out = append(out, "+ "+k+": "+values[k])
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
a := proposalAsk(c, "s-9")
|
||||
a.Explanation = "Changes 5 setting(s) of mounts on shanks.\n+ a: 1\n+ b: 2\nand 3 more, each whole below: c, d, shares"
|
||||
a.Whole = ""
|
||||
a.Details = "Fingerprint 1234 5678 9abc def0 1234 5678.\n" + lines(masked)
|
||||
a.WholeDetails = "Fingerprint 1234 5678 9abc def0 1234 5678.\n" + lines(keys)
|
||||
submit(t, h, "mesh-controller", a)
|
||||
settle(h, c)
|
||||
if len(tg.sends) != 1 {
|
||||
t.Fatalf("telegram got %d messages", len(tg.sends))
|
||||
}
|
||||
phone := tg.sends[0]
|
||||
for k, v := range keys {
|
||||
if !strings.Contains(phone.Note, "+ "+k+": "+v) {
|
||||
t.Errorf("the phone's note lacks %s whole:\n%s", k, phone.Note)
|
||||
}
|
||||
}
|
||||
if strings.Contains(phone.Body, "/storage") || strings.Count(phone.Body, "\n") > 3 {
|
||||
t.Errorf("the body is not the glance:\n%s", phone.Body)
|
||||
}
|
||||
desk := h.sayOn(h.open["ask.mesh-controller.s-9"], "raised", "desktop")
|
||||
if strings.Contains(desk.Text(), "/storage") || !strings.Contains(desk.Note, "+ shares: library=‹path›") {
|
||||
t.Errorf("the desk shows a value whole, or not every key:\n%s", desk.Note)
|
||||
}
|
||||
// A secret's shape in the whole details refuses the ask as a whole.
|
||||
b := proposalAsk(c, "s-10")
|
||||
b.WholeDetails = "+ token: ghp_abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
submit(t, h, "mesh-controller", b)
|
||||
settle(h, c)
|
||||
if _, kept := store.asks["mesh-controller.s-10"]; kept || len(tg.sends) != 1 {
|
||||
t.Errorf("an ask whose whole details carry a secret was taken")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ module messenger
|
||||
|
||||
go 1.22
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
|
||||
@@ -2,3 +2,5 @@ git.novox.be/novox/mesh-sdk/go v0.1.13 h1:Su4JYpZ+zhNovkGA2DgxiZdcuHpjw2tPJzc/7P
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.13/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
|
||||
@@ -31,7 +31,7 @@ as the operator.
|
||||
| | fixed here |
|
||||
|---|---|
|
||||
| D1 | up to 4096 characters arrive whole; longer is cut and the cut said |
|
||||
| issues 383, 639 | shown formatted (HTML), never reworded: the title bold, the body as it is with a key line in monospace, the note apart in a blockquote collapsed to its first lines; `<`, `>` and `&` escaped first, so nothing in the words is read as markup. A tap is compared by the plain words, whitespace aside |
|
||||
| issues 383, 639 | shown formatted (HTML), never reworded: the title bold, the body as it is with a key line in monospace, the note apart in a blockquote collapsed to its first lines, kept whole when a message is too long (the body is cut first, and the cut said); `<`, `>` and `&` escaped first, so nothing in the words is read as markup. A tap is compared by the plain words, whitespace aside |
|
||||
| D4 | a warning or a clearing arrives without sound; an urgent message or a question rings |
|
||||
| D5 | a rate limit is waited out as told |
|
||||
| D6 | a refusal is a permanent failure, said to the router and not retried; an edit to the same text is a success |
|
||||
|
||||
@@ -35,8 +35,10 @@ func escape(s string) string {
|
||||
// shown is a message's three parts as the chat carries them.
|
||||
type shown struct{ title, body, note string }
|
||||
|
||||
// partsOf is a message's parts, cut so that its plain words fit MaxLength and the cut said (D1): the note is cut
|
||||
// first, then the body. The bound is on the words, as Telegram's is on the text once its markup is read.
|
||||
// partsOf is a message's parts, cut so that its plain words fit MaxLength and the cut said (D1). The body is cut
|
||||
// first: the note carries what each answer does and every value an approval sets, so it is kept whole as long as
|
||||
// the title and the note fit; only then is the note cut, and that said too. The bound is on the words, as
|
||||
// Telegram's is on the text once its markup is read.
|
||||
func partsOf(m asks.Message) shown {
|
||||
s := shown{title: strings.TrimSpace(m.Title), body: strings.TrimSpace(m.Body), note: strings.TrimSpace(m.Note)}
|
||||
over := utf8.RuneCountInString(s.plain()) - MaxLength
|
||||
@@ -44,21 +46,16 @@ func partsOf(m asks.Message) shown {
|
||||
return s
|
||||
}
|
||||
mark := utf8.RuneCountInString(cutNote)
|
||||
cut := func(text string, by int) string {
|
||||
r := []rune(text)
|
||||
keep := max(len(r)-by-mark, 0)
|
||||
return string(r[:keep]) + cutNote
|
||||
}
|
||||
if utf8.RuneCountInString(s.note) > over+mark {
|
||||
s.note = cut(s.note, over)
|
||||
if r := []rune(s.body); len(r) >= over+mark {
|
||||
s.body = string(r[:len(r)-over-mark]) + cutNote
|
||||
return s
|
||||
}
|
||||
s.note = ""
|
||||
keep := max(MaxLength-utf8.RuneCountInString(s.title)-1-mark, 0)
|
||||
if r := []rune(s.body); keep < len(r) {
|
||||
s.body = string(r[:keep])
|
||||
// The body cannot make room alone: what is left of it is the cut's word, and the note is cut too.
|
||||
s.body = strings.TrimPrefix(cutNote, "\n")
|
||||
if over = utf8.RuneCountInString(s.plain()) - MaxLength; over > 0 {
|
||||
r := []rune(s.note)
|
||||
s.note = string(r[:max(len(r)-over-mark, 0)]) + cutNote
|
||||
}
|
||||
s.body += cutNote
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
@@ -61,16 +61,26 @@ func TestTheADR0343AskFitsAGlance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A long note is cut before the body is, and the cut said; a body too long loses the note and is cut itself.
|
||||
func TestALongNoteIsCutBeforeTheBody(t *testing.T) {
|
||||
m := asks.Message{Title: "t", Body: "the summary", Note: strings.Repeat("n", 5000)}
|
||||
// The note is kept whole when the message is too long: the body is cut first and the cut said; only a note that
|
||||
// cannot fit beside the title is cut, and that said too (the review of mesh-catalog #278).
|
||||
func TestALongBodyIsCutBeforeTheNote(t *testing.T) {
|
||||
m := asks.Message{Title: "t", Body: strings.Repeat("b", 5000), Note: "+ shares: media=/storage/media\nFingerprint 1234."}
|
||||
s := partsOf(m)
|
||||
if s.body != "the summary" || !strings.HasSuffix(s.note, cutNote) || len([]rune(s.plain())) != MaxLength {
|
||||
if s.note != m.Note || !strings.HasSuffix(s.body, cutNote) || len([]rune(s.plain())) != MaxLength {
|
||||
t.Errorf("note %q, body ends %q, %d characters", s.note, s.body[len(s.body)-20:], len([]rune(s.plain())))
|
||||
}
|
||||
m.Body, m.Note = "the summary", strings.Repeat("n", 5000)
|
||||
s = partsOf(m)
|
||||
if !strings.HasSuffix(s.note, cutNote) || !strings.Contains(s.body, "cut here") || len([]rune(s.plain())) != MaxLength {
|
||||
t.Errorf("body %q, note ends %q, %d characters", s.body, s.note[len(s.note)-20:], len([]rune(s.plain())))
|
||||
}
|
||||
m.Body = strings.Repeat("b", 5000)
|
||||
s = partsOf(m)
|
||||
if s.note != "" || !strings.HasSuffix(s.body, cutNote) || len([]rune(s.plain())) != MaxLength {
|
||||
t.Errorf("note %d, %d characters", len(s.note), len([]rune(s.plain())))
|
||||
}
|
||||
|
||||
// A '<', '>' or '&' in the note is escaped like any other part: it neither breaks the blockquote nor injects.
|
||||
func TestTheNoteIsEscaped(t *testing.T) {
|
||||
got := partsOf(asks.Message{Title: "t", Note: "Asked by <b>x</b> & y </blockquote><i>"}).html()
|
||||
want := "<b>t</b>\n\n<blockquote expandable>Asked by <b>x</b> & y </blockquote><i></blockquote>"
|
||||
if got != want {
|
||||
t.Errorf("%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ module telegram
|
||||
|
||||
go 1.22
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d
|
||||
|
||||
@@ -2,3 +2,5 @@ git.novox.be/novox/mesh-sdk/go v0.1.10 h1:EHhnPRrkw7/CSQAqRsxYnTvlTZ3dI02IPO6WSm
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.10/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629 h1:7TYFV1Q6Mcz+lsLP/sds87tysET4rKCn3rRIQV8gNmA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013141520-9588871d6629/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d h1:HEJyqorKktrkJPeeJdwEo0kdrJjH3fm/YVS9MGftCqY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261013145622-f4745447ba6d/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
|
||||
Reference in New Issue
Block a user