Commit the build modules' publish code a bare ignore pattern hid; drop committed go.work.sum

The patterns mesh-build-node and mesh-build-image matched the command
directory, so publish.go and its test were never committed and the
branch did not compile. Anchored to the binaries now.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 16:24:06 +01:00
parent b88c6dac5b
commit eb50bf1f48
7 changed files with 310 additions and 68 deletions
-39
View File
@@ -1,39 +0,0 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-tpm-tools v0.3.13-0.20230620182252-4639ecce2aba/go.mod h1:EFYHy8/1y2KfgTAsx7Luu7NGhoxtuVHnNo8jE7FikKc=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ=
golang.org/x/telemetry v0.0.0-20260924152758-ed294f943157/go.mod h1:i+ivNqjDnTF3WTElsdk5g9V5DTSBYgdNo7xTU9SDwYA=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+3 -1
View File
@@ -1 +1,3 @@
mesh-build-image
# the binary, built at the module or in its command directory; never the command directory itself
/mesh-build-image
/cmd/mesh-build-image/mesh-build-image
@@ -0,0 +1,151 @@
package main
// The publish of a trunk build (novox/hq design 51 *Publishing*, ADR 0611; CAT-BI1): after the build has ended, from
// the module's own process and not from a Dockerfile step, the image is written as an OCI layout in the step's
// workspace and published through the seat container-image-registry's verbs publish and publish-part, which the
// manifest `invokes`, with the build modules' one sender (buildstep.PublishFiles): its config, its layers and its
// manifest, by their digests, of which only the parts the holder lacks travel. This module holds no registry address and
// no credential; the holder checks the build with the builder and says published or refused on the seat's events. The
// step answers the seat's reference, container-image-registry/<module>/<artifact>@sha256:<digest>.
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
reg "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
)
// KindImage is the kind an image is published as on its seat.
const KindImage = "image"
// call is the seat's request: Call when a test set it, else the runtime's request over the bus (stdio.Ask).
func (m *Module) call() buildstep.SeatCall {
if m.Call != nil {
return m.Call
}
return stdio.Ask
}
// layout is an OCI image layout one build wrote: its one manifest's digest and the files of its parts.
type layout struct {
Digest string
Files []buildstep.File
}
type descriptor struct {
MediaType string `json:"mediaType"`
Digest string `json:"digest"`
Size int64 `json:"size"`
}
// readLayout reads the layout in dir: index.json names exactly one manifest, which names its config and layers; every
// part is a blob of the layout, of the size its descriptor says. The manifest comes last, so a holder stores it after
// what it names.
func readLayout(dir string) (layout, error) {
var index struct {
Manifests []descriptor `json:"manifests"`
}
raw, err := os.ReadFile(filepath.Join(dir, "index.json"))
if err != nil {
return layout{}, fmt.Errorf("the image's layout has no index: %w", err)
}
if err := json.Unmarshal(raw, &index); err != nil || len(index.Manifests) != 1 {
return layout{}, fmt.Errorf("the image's layout names %d manifests, not one (%v)", len(index.Manifests), err)
}
top := index.Manifests[0]
blob := func(d descriptor) (buildstep.File, error) {
if !digestOK.MatchString(d.Digest) {
return buildstep.File{}, fmt.Errorf("%q is not a digest", d.Digest)
}
p := filepath.Join(dir, "blobs", "sha256", strings.TrimPrefix(d.Digest, "sha256:"))
info, err := os.Stat(p)
if err != nil {
return buildstep.File{}, fmt.Errorf("the layout lacks the blob %s: %w", d.Digest, err)
}
if info.Size() != d.Size {
return buildstep.File{}, fmt.Errorf("the blob %s holds %d bytes, not the %d its descriptor says", d.Digest, info.Size(), d.Size)
}
return buildstep.File{Digest: d.Digest, Size: d.Size, Path: p}, nil
}
manifest, err := blob(top)
if err != nil {
return layout{}, err
}
var body struct {
Config descriptor `json:"config"`
Layers []descriptor `json:"layers"`
}
raw, err = os.ReadFile(manifest.Path)
if err != nil || json.Unmarshal(raw, &body) != nil || body.Config.Digest == "" {
return layout{}, fmt.Errorf("the manifest %s names no config (%v)", top.Digest, err)
}
out := layout{Digest: top.Digest}
seen := map[string]bool{}
for _, d := range append([]descriptor{body.Config}, body.Layers...) {
if seen[d.Digest] {
continue
}
seen[d.Digest] = true
f, err := blob(d)
if err != nil {
return layout{}, err
}
out.Files = append(out.Files, f)
}
out.Files = append(out.Files, manifest)
return out, nil
}
// publish writes local as an OCI layout and publishes it on the image seat as <module>/<artifact>, at the version the
// builder gave it (and the tag the call names): the step answers the layout's manifest digest, checked against the
// digest buildah wrote, and the seat's reference; a publish the holder does not take fails `not stored:`.
func (m *Module) publish(ctx context.Context, workspace, module, local string, res BuildResult, version, tag string) (any, error) {
dir := filepath.Join(m.Work, workspace, "layout."+res.Artifact)
digestFile := filepath.Join(m.Work, workspace, "pushed."+res.Artifact)
_ = os.RemoveAll(dir)
defer os.RemoveAll(dir)
argv := append(m.storeFlags(), "push", "--digestfile", digestFile, local, "oci:"+dir)
if r := m.Run(ctx, m.env(), "buildah", argv...); r.Status != 0 || r.Err != "" {
m.sayFailure("buildah push of "+res.Artifact+" to its layout", r)
return nil, fmt.Errorf("%s%s: writing its layout: %s", delivery.NotStored, res.Artifact, cause(r))
}
img, err := readLayout(dir)
if err != nil {
return nil, fmt.Errorf("%s%s: %w", delivery.NotStored, res.Artifact, err)
}
// What buildah wrote is checked against the layout: a digest never goes out unchecked.
raw, err := os.ReadFile(digestFile)
if pushed := strings.TrimSpace(string(raw)); err != nil || pushed != img.Digest {
return nil, fmt.Errorf("%s%s: buildah wrote the digest %q (%v), but its layout's manifest is %s", delivery.NotStored, res.Artifact, pushed, err, img.Digest)
}
name := module + "/" + res.Artifact
q := KindImage
if version != "" {
q = "v" + version
}
in := reg.PublishInput{Name: name, Kind: KindImage, Version: version, Tag: tag, Digest: img.Digest,
Transfer: buildstep.TransferName(name, q, img.Digest)}
sent, err := buildstep.PublishFiles(ctx, m.call(), buildstep.ImageSeat, in, img.Files)
if err != nil {
return nil, fmt.Errorf("%s%s: %v", delivery.NotStored, res.Artifact, err)
}
m.say("published %s on %s: %d of its %d parts sent, the rest held", name, buildstep.ImageSeat, len(sent.Parts), len(img.Files))
res.Digest, res.Pushed = img.Digest, true
res.Reference = catalogue.SeatReference(buildstep.ImageSeat, module, res.Artifact, img.Digest)
return res, nil
}
func (m *Module) say(format string, a ...any) {
if m.Say != nil {
m.Say(format, a...)
}
}
@@ -0,0 +1,124 @@
package main
import (
"errors"
"os"
"path/filepath"
"slices"
"strings"
"testing"
reg "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// 1703d/AC (ADR 0487 §5): a candidate's build leaves the image seat unchanged: nothing is written, nothing is asked.
func TestACandidatesBuildLeavesTheRegistryUnchanged(t *testing.T) {
m, f, states := setup(t, same)
res, st := runBuild(t, m, states, nil) // no publish
if st.State != delivery.StepPassed || res.Pushed {
t.Fatalf("step = %+v result = %+v", st, res)
}
if len(f.called("push")) != 0 || len(f.published) != 0 {
t.Errorf("a candidate's build reached the seat: published %v, calls %v", f.published, f.lines())
}
}
// CAT-BI1 (design 51 *Publishing*): a trunk build writes its image as an OCI layout after the build has ended and
// publishes it once through container-image-registry, as <module>/<artifact> at the ask's version, its config, layers
// and manifest by digest, sending only the parts the holder lacks; the key carries the layout's manifest digest and the
// seat's reference. A holder refusing, a digest file that disagrees and a seat other than the image seat each fail.
func TestATrunkBuildPublishesThroughTheImageSeatOnlyWhatTheHolderLacks(t *testing.T) {
for name, c := range map[string]struct {
extra map[string]any
heldLayer bool
refuse error
digestFile string
reason string
}{
"every part sent": {extra: map[string]any{"version": "1.4.0"}},
"the held layer is not sent": {extra: map[string]any{"version": "1.4.0"}, heldLayer: true},
"a tag of the call's own": {extra: map[string]any{"tag": "fp-abc123"}},
"the holder refuses": {refuse: errors.New("refused: the builder did not number it"), reason: "not stored: server: "},
"buildah wrote another digest": {digestFile: "other", reason: "not stored: server: buildah wrote the digest"},
"buildah wrote no digest": {digestFile: "none", reason: "not stored: server: buildah wrote the digest \"\""},
"another seat": {extra: map[string]any{"seat": "oci-artifact-registry"}, reason: "is published through container-image-registry"},
} {
t.Run(name, func(t *testing.T) {
m, f, states := setup(t, same)
f.refuse, f.digestFile = c.refuse, c.digestFile
args := map[string]any{"publish": true}
for k, v := range c.extra {
args[k] = v
}
if c.heldLayer {
// The layout's layer is the fake's one fixed layer: held under any name.
f.held[sha([]byte("the layer's bytes"))] = true
}
res, st := runBuild(t, m, states, args)
if c.reason != "" {
if (st.State != delivery.StepFailed && st.State != delivery.StepRefused) || !strings.Contains(st.Reason, c.reason) {
t.Fatalf("step = %+v, want failed with %q", st, c.reason)
}
return
}
if st.State != delivery.StepPassed {
t.Fatalf("step = %+v", st)
}
if !res.Pushed || res.Digest != f.pushed || res.Reference != reg.Seat+"/fixture-image/server@"+f.pushed {
t.Errorf("result = %+v, want the layout's %s", res, f.pushed)
}
if len(f.published) != 1 {
t.Fatalf("published = %+v", f.published)
}
p := f.published[0]
version, _ := c.extra["version"].(string)
tag, _ := c.extra["tag"].(string)
if p.Name != "fixture-image/server" || p.Kind != "image" || p.Digest != f.pushed || p.Version != version || p.Tag != tag || p.Transfer == "" {
t.Errorf("publish = %+v", p)
}
var parts []string
for _, x := range p.Parts {
parts = append(parts, x.Digest)
}
if !slices.Equal(parts, f.parts) {
t.Errorf("parts = %v, want config, layer, manifest %v", parts, f.parts)
}
want := f.parts
if c.heldLayer {
want = []string{f.parts[0], f.parts[2]}
}
if !slices.Equal(f.sent, want) {
t.Errorf("sent = %v, want %v", f.sent, want)
}
push := f.called("push")
if len(push) != 1 || !strings.Contains(push[0].line, " oci:"+filepath.Join(m.Work, "ws1", "layout.server")) || strings.Contains(push[0].line, "docker://") {
t.Fatalf("push = %v", push)
}
for _, bad := range []string{"--creds", "--authfile", "--sign", "--tls-verify"} {
if strings.Contains(push[0].line, bad) {
t.Errorf("the push carries %s", bad)
}
}
if _, err := os.Stat(filepath.Join(m.Work, "ws1", "layout.server")); !os.IsNotExist(err) {
t.Errorf("the layout outlived the step: %v", err)
}
// The layout is written after the build has ended: bud, then images, then push.
order := []string{}
for _, l := range f.lines() {
for _, v := range []string{" bud ", " images ", " push ", " rmi "} {
if strings.Contains(l, "buildah") && strings.Contains(l, v) {
order = append(order, strings.TrimSpace(v))
}
}
}
if strings.Join(order, ",") != "bud,images,push,rmi" {
t.Errorf("order = %v", order)
}
})
}
m2, f2, states2 := setup(t, same)
if _, st := runBuild(t, m2, states2, map[string]any{"publish": true, "tag": "a/b:c"}); st.State != delivery.StepFailed || len(f2.called("bud")) != 0 {
t.Errorf("a tag that is not one: step = %+v", st)
}
}
+3 -1
View File
@@ -1 +1,3 @@
mesh-build-node
# the binary, built at the module or in its command directory; never the command directory itself
/mesh-build-node
/cmd/mesh-build-node/mesh-build-node
@@ -0,0 +1,29 @@
package main
// The publish of a done build's file (novox/hq design 51 *Publishing*, ADR 0611; CAT-BN3): from this module's own
// process, after the build step ended, through a registry seat's verbs publish and publish-part, which the manifest
// `invokes`, with the build modules' one sender (buildstep.PublishOne): a bundle's archive on oci-artifact-registry, a
// trunk package on npm-package-registry under the version the builder gave it. No build step reaches a store, and this
// module holds no write credential: the holder checks the build with the builder and says published or refused.
import (
"context"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
)
// publishOn publishes body, of digest, as name (at version, when it has one) of kind on seat: through Call when a
// test set it, else the runtime's request over the bus (stdio.Ask).
func (m *Module) publishOn(ctx context.Context, seat, name, kind, version, digest string, body []byte) error {
call := m.Call
if call == nil {
call = stdio.Ask
}
q := kind
if version != "" {
q = "v" + version
}
return buildstep.PublishOne(ctx, call, seat, name, kind, version, digest, buildstep.TransferName(name, q, digest), body)
}
-27
View File
@@ -1,27 +0,0 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=