diff --git a/base/code/go.mod b/base/code/go.mod index 65cf35a..3df5178 100644 --- a/base/code/go.mod +++ b/base/code/go.mod @@ -3,7 +3,7 @@ module codefly-base go 1.27.3 require ( - github.com/codefly-dev/core v0.19.9 + github.com/codefly-dev/core v0.19.10 github.com/codefly-dev/sdk-go v0.4.3 ) diff --git a/base/code/go.sum b/base/code/go.sum index 1052a23..678169b 100644 --- a/base/code/go.sum +++ b/base/code/go.sum @@ -30,8 +30,8 @@ github.com/cheggaaa/pb/v3 v3.2.1 h1:aprZbFRG+B7+ug76S8QZ6Y1PW168UHzOmbC3wa+aU6I= github.com/cheggaaa/pb/v3 v3.2.1/go.mod h1:U9hSVxoKqJrZIE3PkFG1xXXNaK/Ilzg+EF/scpXzEdw= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/codefly-dev/core v0.19.9 h1:lTDsRJTceSsHLTmVttxFzNM3rEjuKsT8w5NmwozLu3Y= -github.com/codefly-dev/core v0.19.9/go.mod h1:aTJY3Rw0OSVkbWRThgDzSkqieoF9mW4MiPRXM8Vzxww= +github.com/codefly-dev/core v0.19.10 h1:rTeh1vph0xGtpiCCt2Dl1AH2Rz9hzIYt1YGcbv6bLYE= +github.com/codefly-dev/core v0.19.10/go.mod h1:aTJY3Rw0OSVkbWRThgDzSkqieoF9mW4MiPRXM8Vzxww= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/codefly-dev/sdk-go v0.4.3 h1:f7QHqqE7xqqpglOQ2kJ+DZCO/TbrNaDEZhGZ1IOd46Q= diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index cd73d48..0eaae30 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -7,7 +7,7 @@ cancellation. If the build succeeded and shutdown returns a wrapped keeps the packaged result. Other shutdown errors remain fatal. When the build failed, any shutdown error is joined to the build error so both remain visible. -The companion's Docker lifecycle is owned by core. The pinned core v0.19.4 also +The companion's Docker lifecycle is owned by core. The pinned Core also applies its own ten-second timeout to the removal request; the outer two-minute context does not override that timeout. A removal timeout can leave a container behind; this change preserves the artifact and reports its container name, and @@ -17,3 +17,31 @@ does not change core's container recovery policy. injects only the shutdown error to exercise these outcomes without Docker. `TestPackageRealCGO` retains the real native/cross CGO contract and requires `SERVICE_GO_PACKAGE_TESTS=required` and Docker. + +## Graph identity in package evidence + +Each executable receives its own SBOM using Core's existing inventory and +artifact-binding operations. A native target honors the CLI's prepared GOWORK; +without one it uses the pinned module graph. Cross targets use GOWORK=off for +source discovery, toolchain selection, build and inventory because their +companion mounts the source module, not host workspace siblings. The service's +with-workspace setting is not Package's graph selector. A single mixed-target +request may therefore produce different dependency graphs, each bound to its +own executable digest. The public Package regression links a workspace sibling, +checks the actual source attachment and all component references, and compares +the selected graph with dependency versions embedded in real CGO executables. + +Workspace SBOM qualification requires the Core workspace-root correction. The +current released pin, Core v0.19.10, omits other workspace main modules and can +attach an executable to the wrong source. The strengthened regression is qualified +with an uncommitted local Core workspace override while that owner fix awaits a +release. This does not qualify a clean install from the current pin. Adopt the +released correction through `codefly agent deps --pin`, then rerun standalone, +generated-service and public native/cross packaging checks before release. + +Core v0.19.10 includes the Linux process-identity correction. Native ownership +records use authenticated-v2; older agents/CLIs that only know authenticated-v1 +do not reap those records. Use compatible CLI recovery tooling; an installation +key alone is not evidence that a locally built binary is the published release. +The ownership gate retains the launch birth and requires it before signaling, +then verifies the recovery receipt and preservation of an independent sibling. diff --git a/go.mod b/go.mod index 0268404..58e4850 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/codefly-dev/service-go go 1.27.3 require ( - github.com/codefly-dev/core v0.19.9 + github.com/codefly-dev/core v0.19.10 github.com/stretchr/testify v1.12.1 golang.org/x/mod v0.42.0 golang.org/x/tools v0.52.0 diff --git a/go.sum b/go.sum index 820c052..77a8746 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJ github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cloudflare/circl v1.6.5 h1:O64F26HEqNhznd/hrC5KZXVKYuKM2rx4deZDTc4ihQA= github.com/cloudflare/circl v1.6.5/go.mod h1:h5LNyxAc5nTue9DS5jT+48en2PSDYt3zdGnz5OstK6c= -github.com/codefly-dev/core v0.19.9 h1:lTDsRJTceSsHLTmVttxFzNM3rEjuKsT8w5NmwozLu3Y= -github.com/codefly-dev/core v0.19.9/go.mod h1:aTJY3Rw0OSVkbWRThgDzSkqieoF9mW4MiPRXM8Vzxww= +github.com/codefly-dev/core v0.19.10 h1:rTeh1vph0xGtpiCCt2Dl1AH2Rz9hzIYt1YGcbv6bLYE= +github.com/codefly-dev/core v0.19.10/go.mod h1:aTJY3Rw0OSVkbWRThgDzSkqieoF9mW4MiPRXM8Vzxww= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= diff --git a/native_ownership_test.go b/native_ownership_test.go index c9e3b6c..280c8e7 100644 --- a/native_ownership_test.go +++ b/native_ownership_test.go @@ -69,11 +69,24 @@ func TestNativeAgentOwnershipLifecycle(t *testing.T) { // Restart through the same Runtime, then kill only its disposable owner // birth so no agent Stop/finalizer can mask recovery of an orphan workload. a.group = startNativeWorkload(t, ctx, a.client, a.workspace) - owner, err := runners.InspectProcess(a.conn.ProcessInfo().PID) - if err != nil { - t.Fatal(err) + // A fresh inspection alone could select a replacement at the saved PID. + // Exercise the real pre-signal guard against a stale birth: the current + // disposable agent and its workload must remain alive and authenticated. + if !t.Run("stale_owner_refuses_delivery", func(t *testing.T) { + stale := a.owner + stale.StartID++ + if err := signalNativeOwner(ctx, stale); !errors.Is(err, errNativeOwnerChanged) { + t.Fatalf("stale owner was not refused before delivery: %v", err) + } + current, err := runners.InspectProcess(a.owner.PID) + if err != nil || current != a.owner { + t.Fatalf("stale-owner refusal affected the original agent: %+v, %v", current, err) + } + assertNativeOwnership(t, ctx, a.group, a.workspace, true) + }) { + t.FailNow() } - if err := runners.SignalProcess(ctx, owner, syscall.SIGKILL); err != nil { + if err := signalNativeOwner(ctx, a.owner); err != nil { t.Fatal(err) } waitNativeCondition(t, func() bool { @@ -91,9 +104,14 @@ func TestNativeAgentOwnershipLifecycle(t *testing.T) { t.Fatalf("preview: %v", err) } expected, err := runners.ParseProcessGroupRecoveryExpectation(preview) - if err != nil || expected.Namespace != "authenticated-v2" || expected.PGID != a.group.PGID() || expected.Owner.PID != owner.PID { + if err != nil || expected.Namespace != "authenticated-v2" || expected.PGID != a.group.PGID() { t.Fatalf("actual agent did not write the expected v2 registration: %+v, %v", expected, err) } + // Recovery receipts represent the recorded owner with PGID zero. Its birth + // must still be the authenticated agent captured before any workload RPC. + if expected.Owner.PGID != 0 || expected.Owner.PID != a.owner.PID || expected.Owner.BootID != a.owner.BootID || expected.Owner.StartID != a.owner.StartID || expected.Owner.Executable != a.owner.Executable { + t.Fatalf("recovery receipt names a different owner: %+v, original %+v", expected.Owner, a.owner) + } t.Logf("orphan preview: %s", preview) assertNativeOwnership(t, ctx, a.group, a.workspace, false) receipt := filepath.Join(t.TempDir(), "recovery.json") @@ -124,6 +142,22 @@ type ownedNativeService struct { conn *manager.AgentConn client runtimev0.RuntimeClient group *runners.TrackedProcessGroup + owner runners.ProcessIdentity +} + +var errNativeOwnerChanged = errors.New("native agent owner identity changed before signal") + +// This fixture never execs or changes process groups. Require the entire +// original identity, then let Core bind the current identity to signal delivery. +func signalNativeOwner(ctx context.Context, original runners.ProcessIdentity) error { + current, err := runners.InspectProcess(original.PID) + if err != nil { + return err + } + if current != original { + return errNativeOwnerChanged + } + return runners.SignalProcess(ctx, current, syscall.SIGKILL) } func startOwnedNativeService(t *testing.T, ctx context.Context, name string) *ownedNativeService { @@ -147,6 +181,15 @@ func main() { t.Fatal(err) } t.Cleanup(conn.Close) + ownerGroup, err := runners.LookupProcessGroup(conn.ProcessInfo().PID) + if err != nil { + t.Fatal(err) + } + owner := ownerGroup.Leader() + ownership, err := ownerGroup.InspectOwnership(ctx) + if err != nil || !ownership.Authenticated || !ownership.OwnerAlive || owner.PID != conn.ProcessInfo().PID || len(ownership.Members) != 1 || ownership.Members[0] != owner { + t.Fatalf("agent launch did not authenticate its original owner identity: %+v, %v", ownership, err) + } client := runtimev0.NewRuntimeClient(conn.GRPCConn()) t.Cleanup(func() { cleanup, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -168,7 +211,7 @@ func main() { if err != nil || initialized.GetStatus().GetState() != runtimev0.InitStatus_READY { t.Fatalf("init: %v, %v\n%s", initialized, err, conn.StderrTail()) } - return &ownedNativeService{workspace, conn, client, startNativeWorkload(t, ctx, client, workspace)} + return &ownedNativeService{workspace, conn, client, startNativeWorkload(t, ctx, client, workspace), owner} } func startNativeWorkload(t *testing.T, ctx context.Context, client runtimev0.RuntimeClient, workspace string) *runners.TrackedProcessGroup { diff --git a/package_evidence_test.go b/package_evidence_test.go new file mode 100644 index 0000000..5ed8251 --- /dev/null +++ b/package_evidence_test.go @@ -0,0 +1,166 @@ +package main + +import ( + "crypto/sha256" + "debug/buildinfo" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + builderv0 "github.com/codefly-dev/core/generated/go/codefly/services/builder/v0" + "github.com/stretchr/testify/require" +) + +// Exercise the public Package RPC, real binaries and Core's attached evidence. +// The workspace links a sibling main module instead of its pinned release. +func TestPackageEvidenceFollowsEachTargetGraph(t *testing.T) { + root, err := filepath.EvalSymlinks(t.TempDir()) + if err != nil { + t.Fatal(err) + } + source := filepath.Join(root, "code") + write(t, filepath.Join(root, "service.codefly.yaml"), serviceConfig) + write(t, filepath.Join(source, "go.mod"), "module example.test/package-evidence\n\ngo 1.27.0\n\nrequire rsc.io/quote v1.5.1\n") + write(t, filepath.Join(source, "main.go"), "package main\n/* static int answer(void) { return 42; } */\nimport \"C\"\nimport (\"fmt\"; \"rsc.io/quote\")\nfunc main() { if C.answer()!=42 { panic(\"C compiler\") }; fmt.Println(quote.Hello()) }\n") + prepare := exec.Command("go", "mod", "tidy") + prepare.Dir = source + prepare.Env = append(os.Environ(), "GOWORK=off") + if out, err := prepare.CombinedOutput(); err != nil { + t.Fatalf("prepare pinned graph: %v %s", err, out) + } + work := filepath.Join(root, "go.work") + write(t, filepath.Join(root, "sibling/go.mod"), "module rsc.io/quote\n\ngo 1.27.0\n") + write(t, filepath.Join(root, "sibling/quote.go"), "package quote\nfunc Hello() string { return \"workspace sibling\" }\n") + write(t, work, "go 1.27.0\nuse (\n./code\n./sibling\n)\n") + for _, workspace := range []bool{false, true} { + name := "pinned" + if workspace { + name = "prepared-workspace" + } + t.Run(name, func(t *testing.T) { + mode := "off" + if workspace { + mode = work + } + t.Setenv("GOWORK", mode) + b, ctx := loadBuilderAt(t, root) + b.Settings.WithWorkspace = !workspace // not Package's graph selector + targets := []*builderv0.PackageTarget{{Os: runtime.GOOS, Architecture: runtime.GOARCH}} + if os.Getenv("SERVICE_GO_PACKAGE_TESTS") == "required" && (runtime.GOOS != "linux" || runtime.GOARCH != "amd64") { + targets = append(targets, &builderv0.PackageTarget{Os: "linux", Architecture: "amd64"}) + } + response, err := grpcBuilderClient(t, b).Package(ctx, &builderv0.PackageRequest{OutputDirectory: t.TempDir(), ArtifactName: "evidence", Targets: targets, IncludeSbom: true}) + if err != nil || response.GetState().GetState() != builderv0.PackageStatus_SUCCESS { + t.Fatalf("Package: %v %v", response, err) + } + if len(response.Artifacts) != 2*len(targets) { + t.Fatalf("artifacts: %v", response.Artifacts) + } + for _, artifact := range response.Artifacts { + if artifact.Kind != builderv0.PackageArtifact_EXECUTABLE { + continue + } + want := "v1.5.1" + isNative := artifact.Target.Os == runtime.GOOS && artifact.Target.Architecture == runtime.GOARCH + if workspace && isNative { + want = "(devel)" + } + info, err := buildinfo.ReadFile(artifact.Path) + if err != nil { + t.Fatal(err) + } + found := false + for _, dep := range info.Deps { + if dep.Path == "rsc.io/quote" { + version := dep.Version + if dep.Replace != nil { + version = dep.Replace.Version + } + if version != want { + t.Fatalf("binary dependency %s want %s", version, want) + } + found = true + } + } + if !found { + t.Fatal("binary omitted fixture dependency") + } + if isNative { + out, err := exec.CommandContext(ctx, artifact.Path).CombinedOutput() + require.NoError(t, err, "%s", out) + if workspace { + require.Equal(t, "workspace sibling\n", string(out)) + } else { + require.NotEqual(t, "workspace sibling\n", string(out)) + } + } + payload, err := os.ReadFile(artifact.Path + ".cdx.json") + if err != nil { + t.Fatal(err) + } + assertPackageEvidence(t, artifact, payload, workspace && isNative) + } + }) + } +} + +func assertPackageEvidence(t *testing.T, artifact *builderv0.PackageArtifact, payload []byte, workspace bool) { + t.Helper() + type component struct { + Ref string `json:"bom-ref"` + Hashes []struct{ Alg, Content string } + } + var document struct { + Metadata struct{ Component component } + Components []component + Dependencies []struct { + Ref string + DependsOn []string + } + } + require.NoError(t, json.Unmarshal(payload, &document)) + root := document.Metadata.Component + bytes, err := os.ReadFile(artifact.Path) + require.NoError(t, err) + digest := fmt.Sprintf("%x", sha256.Sum256(bytes)) + require.Equal(t, digest, artifact.Sha256) + require.Len(t, root.Hashes, 1) + require.Equal(t, "SHA-256", root.Hashes[0].Alg) + require.Equal(t, digest, root.Hashes[0].Content) + refs := map[string]bool{root.Ref: true} + for _, component := range document.Components { + require.NotEmpty(t, component.Ref) + require.False(t, refs[component.Ref], "duplicate component %s", component.Ref) + refs[component.Ref] = true + } + edges := map[string][]string{} + for _, dependency := range document.Dependencies { + require.True(t, refs[dependency.Ref], "unknown graph source %s", dependency.Ref) + for _, ref := range dependency.DependsOn { + require.True(t, refs[ref], "unknown graph target %s", ref) + } + edges[dependency.Ref] = dependency.DependsOn + } + const source = "pkg:golang/example.test/package-evidence" + quote := "pkg:golang/rsc.io/quote" + if !workspace { + quote += "@v1.5.1" + } + require.Contains(t, refs, source) + require.Contains(t, refs, quote) + require.Equal(t, []string{source}, edges[root.Ref], "executable must attach its actual source") + require.Contains(t, edges[source], quote) + const sampler = "pkg:golang/rsc.io/sampler@v1.3.0" + if workspace { + require.NotContains(t, refs, quote+"@v1.5.1") + require.NotContains(t, edges[quote], sampler, "local sibling has no released-version requirements") + } else { + require.NotContains(t, refs, strings.TrimSuffix(quote, "@v1.5.1")) + require.Contains(t, edges[quote], sampler) + } +} diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go index 3badb1a..2e03067 100644 --- a/pkg/builder/builder.go +++ b/pkg/builder/builder.go @@ -384,13 +384,15 @@ func (s *Builder) Package(ctx context.Context, req *builderv0.PackageRequest) (* } if req.GetIncludeSbom() { - source, err := sbom.GolangWithOptions(ctx, s.Service.SourceLocation, sbom.GolangOptions{ - UseWorkspace: s.Settings.WithWorkspace, - }) - if err != nil { - return s.Builder.PackageError(fmt.Errorf("generate package SBOM: %w", err)) - } for _, executable := range append([]*builderv0.PackageArtifact(nil), artifacts...) { + // Inventory the same graph that produced this target. Settings.WithWorkspace + // configures service builds; Package follows the CLI's prepared GOWORK. + source, err := sbom.GolangWithOptions(ctx, s.Service.SourceLocation, sbom.GolangOptions{ + UseWorkspace: packageGoWorkMode(executable.GetTarget()) != "off", + }) + if err != nil { + return s.Builder.PackageError(fmt.Errorf("generate package SBOM: %w", err)) + } subject := req.GetSubject() publisher, name, version := "", artifactName, "" if subject != nil { @@ -552,7 +554,7 @@ func packageCrossGoBinaryWithRunner(ctx context.Context, source, entry, destinat if !supported { return fmt.Errorf("go package %s: no CGO cross toolchain; supported cross targets are darwin/amd64, darwin/arm64, linux/amd64, linux/arm64", identity) } - goToolchain, err := resolvePackageGoToolchain(ctx, source) + goToolchain, err := resolvePackageGoToolchain(ctx, source, target) if err != nil { return err } diff --git a/pkg/builder/package_integration_test.go b/pkg/builder/package_integration_test.go index 33cc219..904432c 100644 --- a/pkg/builder/package_integration_test.go +++ b/pkg/builder/package_integration_test.go @@ -57,7 +57,7 @@ func TestPackageRealCGO(t *testing.T) { t.Fatal(err) } } - selectedToolchain, err := resolvePackageGoToolchain(ctx, source) + selectedToolchain, err := resolvePackageGoToolchain(ctx, source, &builderv0.PackageTarget{Os: runtime.GOOS, Architecture: runtime.GOARCH}) if err != nil { t.Fatal(err) } diff --git a/pkg/builder/package_source.go b/pkg/builder/package_source.go index cda9524..c7d46ec 100644 --- a/pkg/builder/package_source.go +++ b/pkg/builder/package_source.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "strings" builderv0 "github.com/codefly-dev/core/generated/go/codefly/services/builder/v0" @@ -39,12 +40,21 @@ func goWorkMode() string { return "off" } +// A cross companion cannot see the host's prepared workspace. Source discovery, +// toolchain selection and its SBOM must use the same pinned graph as its build. +func packageGoWorkMode(target *builderv0.PackageTarget) string { + if target.GetOs() == runtime.GOOS && target.GetArchitecture() == runtime.GOARCH { + return goWorkMode() + } + return "off" +} + // Use Go's module/toolchain selection on the source, just as the native build // does. The cross image supplies C compilers, not the module's Go version. -func resolvePackageGoToolchain(ctx context.Context, source string) (string, error) { +func resolvePackageGoToolchain(ctx context.Context, source string, target *builderv0.PackageTarget) (string, error) { command := exec.CommandContext(ctx, "go", "env", "GOVERSION") command.Dir = source - command.Env = hostGoEnvironment() + command.Env = append(os.Environ(), "GOWORK="+packageGoWorkMode(target)) var stderr bytes.Buffer command.Stderr = &stderr output, err := command.Output() @@ -63,7 +73,7 @@ func resolvePackageGoToolchain(ctx context.Context, source string) (string, erro func resolvePackageSource(ctx context.Context, source string, target *builderv0.PackageTarget) (root, entry string, err error) { command := exec.CommandContext(ctx, "go", "list", "-mod=readonly", "-json", ".") command.Dir = source - command.Env = hostGoEnvironment("CGO_ENABLED=1", "GOOS="+target.GetOs(), "GOARCH="+target.GetArchitecture()) + command.Env = append(os.Environ(), "GOWORK="+packageGoWorkMode(target), "CGO_ENABLED=1", "GOOS="+target.GetOs(), "GOARCH="+target.GetArchitecture()) var stderr bytes.Buffer command.Stderr = &stderr payload, err := command.Output() diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 87c48d6..6f1d6c8 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -4,7 +4,7 @@ // Load (additional endpoint wiring) and Init (REST/gRPC env vars). // Test, Lint, Build are reused as-is. // -// Native ownership is implemented by the pinned Core runner. Core v0.19.9 +// Native ownership is implemented by the pinned Core runner. Core v0.19.10 // writes authenticated-v2 process-group registrations, retaining the launching // workspace even when the child environment cannot be inspected. Start and // Stop use that runner directly; this agent owns no parallel registry or diff --git a/templates/factory/code/go.mod.tmpl b/templates/factory/code/go.mod.tmpl index 8532c10..6dbceb6 100644 --- a/templates/factory/code/go.mod.tmpl +++ b/templates/factory/code/go.mod.tmpl @@ -3,7 +3,7 @@ module {{ .Service.Name.DNSCase }} go 1.27.3 require ( - github.com/codefly-dev/core v0.19.9 + github.com/codefly-dev/core v0.19.10 github.com/codefly-dev/sdk-go v0.4.3 ) diff --git a/templates/factory/code/go.sum.tmpl b/templates/factory/code/go.sum.tmpl index 1052a23..678169b 100644 --- a/templates/factory/code/go.sum.tmpl +++ b/templates/factory/code/go.sum.tmpl @@ -30,8 +30,8 @@ github.com/cheggaaa/pb/v3 v3.2.1 h1:aprZbFRG+B7+ug76S8QZ6Y1PW168UHzOmbC3wa+aU6I= github.com/cheggaaa/pb/v3 v3.2.1/go.mod h1:U9hSVxoKqJrZIE3PkFG1xXXNaK/Ilzg+EF/scpXzEdw= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/codefly-dev/core v0.19.9 h1:lTDsRJTceSsHLTmVttxFzNM3rEjuKsT8w5NmwozLu3Y= -github.com/codefly-dev/core v0.19.9/go.mod h1:aTJY3Rw0OSVkbWRThgDzSkqieoF9mW4MiPRXM8Vzxww= +github.com/codefly-dev/core v0.19.10 h1:rTeh1vph0xGtpiCCt2Dl1AH2Rz9hzIYt1YGcbv6bLYE= +github.com/codefly-dev/core v0.19.10/go.mod h1:aTJY3Rw0OSVkbWRThgDzSkqieoF9mW4MiPRXM8Vzxww= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= github.com/codefly-dev/gortk v0.2.0/go.mod h1:dDWUMFgAP063OCGhTEpV7FFUj9+zTcxxO/nV80VKEwg= github.com/codefly-dev/sdk-go v0.4.3 h1:f7QHqqE7xqqpglOQ2kJ+DZCO/TbrNaDEZhGZ1IOd46Q=