diff --git a/docs/orchestration.md b/docs/orchestration.md index 1d9b5ff6..d5d9df99 100644 --- a/docs/orchestration.md +++ b/docs/orchestration.md @@ -1252,9 +1252,18 @@ wildcard :P both bind. The one listener that is not a squatter is the service's own kept container: a Docker store with `keep-running` leaves its container up for the next run to adopt, still publishing the service's port. An address is let through only when the container listening there is the one -the service's agent will adopt — its name in this run's workspace and naming -scope, codefly-owned, labelled with this run's recovery scope, running, not -ephemeral, and publishing that port on that loopback family; any other holder, +the service owns: Core's exact parent-service label must match the loaded +module/service, and the logical name label must match an actual Docker name. +The container must also be codefly-owned, labelled with this run's recovery +scope and a compatible namespace, running, not ephemeral, and publishing that +port on that loopback family. Agents bind each Docker environment through +Core's `WithServiceIdentity`; CLI does not infer parent identity from suffixes. +For older unlabelled containers, the existing exact bare name in this run's +workspace and naming scope remains the only compatibility exception. A present +conflicting or malformed parent label refuses even that exception. Old suffixed +containers need owner-directed recovery preserving their data; the CLI does not +relabel or delete them. Parent attribution proves ownership, while the agent +owns actual reuse of its concrete containers. Any other holder, including one on the other family beside it, is refused. Not covered: a process that binds a loopback address on the port after that probe, and a routed module that crashes or is withdrawn mid-run, whose port the hop keeps diff --git a/docs/supported-matrix.md b/docs/supported-matrix.md index 6f81c7c7..93646fbe 100644 --- a/docs/supported-matrix.md +++ b/docs/supported-matrix.md @@ -27,33 +27,13 @@ When `go.mod` moves to a new core release line, update the `core` field in `go test ./pkg/conformance/...` before the full coverage and race gates. `TestMatrixCoreMatchesGoMod` refuses a stale declared version. -The current pin is a `v0.19.7` pseudo-version of core: `257e6b23`, branch -`chore/envelope-on-0.19.6`, which merges `v0.19.6` (with the git source -declaration, codefly-dev/core#832) into `5522c4da`, the commit carrying the -module envelope that `pkg/delivery/signing` uses. `v0.19.6` carries -`b81d92e6`, the merge of codefly-dev/core#812 — the module manifest -(`solutionhost.ModuleManifest`, one signed document per module, whose -workload is `(service, container)` and which names no build) and -`workloadidentity`, which a native run attests its workloads with. Its -companion is `github.com/codefly-dev/sdk-go/workcontext` at `70dc7a54` -(codefly-dev/sdk-go#80, a credential carries no build seal). Releasing a CLI -on it requires core `v0.19.6` and a `workcontext` release past `v0.1.0` to -be tagged first. The previous pin, a `v0.19.5` pseudo-version, carried the -`runtime-started-artifact/v1` contract (codefly-dev/core#796, -codefly-dev/cli#989), which this CLI no longer reads. On top of `v0.19.2`, `v0.19.4` adds -the issuer's non-consuming `Authority.ReadBack` and moves kustomize back to -`v0.21.1`, whose kube-openapi does not pull `structured-merge-diff/v7` (the -`v0.21.3` that `v0.19.2` and `v0.19.3` carried broke every build compiling -client-go). `v0.19.2` added a -solution manifest's scoped `api.consumes` and its consumption contract (#784) -and an issuer-assigned Work Context session (#783). `v0.19.1` -verifies delivered carriers -against pinned platform keys (#773), projects a service account entirely on a -caller-supplied filesystem (#774, #777), and carries ToolProjection and slot-only authority (#755), ciguard (#750), caller tokens, -image-lock/publish evidence (#759), and configuration-origin evidence (#746), -in addition to #752's composition-provenance contract. This source matrix does -not qualify released agents for the new -`deployment-composition-provenance/v1` capability. +The current pin targets Core's `v0.19.8` release line. The exact source +revision is declared in `go.mod`; the CLI consumes Core's container parent +attribution and release acquisition APIs. These source gates qualify the CLI +against that selected revision. They do not qualify separately compiled agents +or imply that the target Core release has been published. Agents creating +sidecar containers must adopt the attribution API in their own source and +qualify their lifecycle before consumers select the resulting release. A missing receipt can be a consequence of an earlier failure: coverage and race run `go test -failfast`, so a failed package can stop the npm tests in diff --git a/go.mod b/go.mod index 513ccffe..1ed43ef7 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/codefly-dev/authority/libraries/delivery/go v0.0.0-20261026023425-000d44495d93 github.com/codefly-dev/authority/libraries/fixture-composition/go v0.0.0-20261027171336-5178f01696e3 github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261026175541-3afad7224855 - github.com/codefly-dev/core v0.19.7-0.20261026194550-a8599bd3991a + github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 github.com/codefly-dev/golor v0.1.3 github.com/codefly-dev/llm v0.1.7 github.com/codefly-dev/sdk-go/workcontext v0.1.1-0.20261018052022-70dc7a546458 @@ -175,7 +175,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/buildkit v0.32.2 // indirect + github.com/moby/buildkit v0.34.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect @@ -195,7 +195,7 @@ require ( github.com/sergi/go-diff v1.4.0 // indirect github.com/shoenig/go-m1cpu v0.2.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect - github.com/sirupsen/logrus v1.10.0 // indirect + github.com/sirupsen/logrus v1.10.2 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/standard-webhooks/standard-webhooks/libraries v0.0.1 // indirect @@ -243,7 +243,7 @@ require ( golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect golang.org/x/net v0.60.0 // indirect golang.org/x/text v0.42.0 // indirect - golang.org/x/time v0.15.0 // indirect + golang.org/x/time v0.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20261012220534-4341edf3f941 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 47505b10..b2dd79eb 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,8 @@ github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261026023425-00 github.com/codefly-dev/authority/libraries/resources/go v0.0.0-20261026023425-000d44495d93/go.mod h1:1rmjrVyr0h7zOuwOHS9i+BoeS9dr3XNvtupLZFwY6As= github.com/codefly-dev/core v0.19.7-0.20261026194550-a8599bd3991a h1:ar8ERpN9n9ZL29LZ63mqBZNLPZ7OpcnQ1+D1g89DbPY= github.com/codefly-dev/core v0.19.7-0.20261026194550-a8599bd3991a/go.mod h1:1ina3kP+YSAL4tlZvnEYd4P8bFGnJECfkP86ojeeF1A= +github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156 h1:OLQ/LG4+KGRz3crTc8zgQlxTS/Ta3Yh7OUFu+12N5u4= +github.com/codefly-dev/core v0.19.8-0.20261027203111-6eadd92dd156/go.mod h1:NB7x1UZ01rYTfEZEl4vUFD1A+fgOXhsWbgA3M8x4ThA= github.com/codefly-dev/golor v0.1.3 h1:xmo+ceyJFRYZdvpWE2fNd0jeaadp/Ibm1BnganiGKOc= github.com/codefly-dev/golor v0.1.3/go.mod h1:sl/u/K1l7J0Pr3xyVZp8fOJYQItKKst1No9JqgzLLoY= github.com/codefly-dev/gortk v0.2.0 h1:7bOlS5valYz2zil+fZctQNcPCYBcPj86abcw9N8h1hQ= @@ -133,6 +135,7 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc= github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ= github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -332,6 +335,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/buildkit v0.32.2 h1:Sfy7+u6dUv/2yuBc9KCoK70Re8atuV8aPZ5UOC068Vc= github.com/moby/buildkit v0.32.2/go.mod h1:0GB/EJ1d+4VIVqIAgy3asaoGkVXy7IrDfVy7mPhOvg8= +github.com/moby/buildkit v0.34.1 h1:jVdxsXX0UpU0q1Et+9lFezPmh9sGE6vF0TrKZaGlfy4= +github.com/moby/buildkit v0.34.1/go.mod h1:e+Za70y2a3nL1U2Y0P8TvhPyKp2Ok2NS+Q4siSArDiM= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= @@ -406,6 +411,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.10.0 h1:T8MxJJXVZkfcC5zSRMRAg2F8+lxjmUCGGWPzFxO+Msc= github.com/sirupsen/logrus v1.10.0/go.mod h1:FXZFonkDAnFozmO+5hGAFvB0Yg9/j2SIhA/QuIkP180= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg= github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -514,6 +521,7 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxO go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0 h1:QBajQ2SrwQijzHyZbQlPsuIzpl/ll8DY6wPWsajeGcI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.45.0/go.mod h1:08ZQLjrPLQ6R4kAXvuOvODEer5Yh4CoFvll5qB2BCI8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0 h1:N3YQCxjxQ/bMjyc3heladfRm9t9RTksGQH8z4w6yU/0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0/go.mod h1:Mp8HOFqcaUyypCuGv9IhDdTHnJ56lSudSHMd+pVSCEA= go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= @@ -581,6 +589,8 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI= golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA= diff --git a/pkg/conformance/matrix.json b/pkg/conformance/matrix.json index e17544d6..7ad1a155 100644 --- a/pkg/conformance/matrix.json +++ b/pkg/conformance/matrix.json @@ -1,7 +1,7 @@ { "schema_version": 1, "cli": "source", - "core": "v0.19.7", + "core": "v0.19.8", "rows": [ { "id": "linux-amd64-source", diff --git a/pkg/orchestration/adopted_container_ports.go b/pkg/orchestration/adopted_container_ports.go index aa2b9d59..51faf250 100644 --- a/pkg/orchestration/adopted_container_ports.go +++ b/pkg/orchestration/adopted_container_ports.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/codefly-dev/core/resources" "github.com/codefly-dev/core/runners/dockerrun" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" @@ -40,22 +41,16 @@ var listOwnedContainers = func(ctx context.Context, scopeID string) ([]container // adoptedContainerAddresses answers which loopback addresses the first-Init // port guard must not treat as a squatter: the ones published by the -// container this service's agent will adopt. A Docker store with +// container attributed to this service. A Docker store with // keep-running leaves its container up so the next run reattaches to it by -// name, and that container still publishes the service's deterministic port — -// the one this Init proposes. +// name, and that container still publishes the service's deterministic port. +// The agent decides actual reuse during Init; this check establishes ownership. // -// The container is identified the way the agent's runner reuses it: the name -// every Core service agent gives it (dockerrun.ContainerName over the -// service's unique name in the run's workspace and naming scope, as -// Base.UniqueWithWorkspace spells it), owned by codefly, -// labelled with this flow's exact recovery scope and, when it carries one, its -// namespace (the rule dockerrun applies before reusing a container), running, -// and not an ephemeral one, which is never reused across sessions. An address -// is excused only for the loopback family the container publishes on: a -// 127.0.0.1 or 0.0.0.0 publication covers 127.0.0.1, a ::1 or :: one covers -// ::1, so a squatter on the other family is still refused. Anything this -// cannot establish — no recovery identity, no Docker engine — excuses nothing. +// Core supplies exact parent-service attribution for containers with arbitrary +// agent-owned names. Scope, namespace, lifecycle, the concrete name and the +// published loopback family are checked separately. Unlabelled containers retain +// only the existing exact-bare-name compatibility path; suffixes prove nothing. +// A present conflicting or malformed parent label always refuses the exception. func (runner *Runner) adoptedContainerAddresses(ctx context.Context) func(address string) bool { scopeID, namespace, ok := strings.Cut(runner.containerRecoveryIdentity, ":") if !ok || scopeID == "" || runner.instance == nil || runner.instance.Identity == nil || @@ -78,21 +73,40 @@ func (runner *Runner) adoptedContainerAddresses(ctx context.Context) func(addres containers, _ = listOwnedContainers(ctx, scopeID) } return slices.ContainsFunc(containers, func(c container.Summary) bool { - return adoptableContainer(&c, name, scopeID, namespace) && publishesAddress(&c, address) + return adoptableContainer(&c, identity, name, scopeID, namespace) && publishesAddress(&c, address) }) } } -func adoptableContainer(c *container.Summary, name, scopeID, namespace string) bool { +func adoptableContainer(c *container.Summary, service *resources.ServiceIdentity, name, scopeID, namespace string) bool { labels := c.Labels if labels[dockerrun.LabelCodeflyOwner] != "true" || labels[dockerrun.LabelCodeflyRecoveryScope] != scopeID || - labels[dockerrun.LabelCodeflyName] != name || labels[dockerrun.LabelCodeflyEphemeral] == "true" || c.State != "running" { + labels[dockerrun.LabelCodeflyEphemeral] == "true" || c.State != "running" { return false } if found := labels[dockerrun.LabelCodeflyRecoveryNamespace]; found != "" && found != namespace { return false } - return slices.Contains(c.Names, "/"+name) + parent, err := dockerrun.ContainerServiceIdentity(labels) + if err != nil || service == nil { + return false + } + logicalName := labels[dockerrun.LabelCodeflyName] + if logicalName == "" { + return false + } + if parent != nil { + if parent.Unique() != service.Unique() { + return false + } + } else if logicalName != name { + // Compatibility for the pre-existing exact-name guard only. An absent + // label cannot extend ownership to a suffixed container. + return false + } + return slices.ContainsFunc(c.Names, func(dockerName string) bool { + return strings.TrimPrefix(dockerName, "/") == logicalName + }) } // publishesAddress reports whether c publishes the loopback host:port address diff --git a/pkg/orchestration/adopted_container_ports_test.go b/pkg/orchestration/adopted_container_ports_test.go index 93768fef..2bdb407c 100644 --- a/pkg/orchestration/adopted_container_ports_test.go +++ b/pkg/orchestration/adopted_container_ports_test.go @@ -25,6 +25,7 @@ func keptContainer(name string, port uint16) container.Summary { State: "running", Labels: map[string]string{ dockerrun.LabelCodeflyOwner: "true", + dockerrun.LabelCodeflyService: "web/gateway", dockerrun.LabelCodeflyName: name, dockerrun.LabelCodeflyRecoveryScope: testRecoveryScope, dockerrun.LabelCodeflyRecoveryNamespace: testRecoveryNamespace, @@ -52,6 +53,44 @@ func TestFirstInitAdoptsItsOwnKeptContainerAndRefusesAnyOtherHolder(t *testing.T {name: "another service's name", mutate: func(c *container.Summary) { c.Names = []string{"/codefly-other"} c.Labels[dockerrun.LabelCodeflyName] = "codefly-other" + c.Labels[dockerrun.LabelCodeflyService] = "other/service" + }, refused: true}, + // An agent may run several containers for one service and names each + // after itself: service-object-storage publishes the service's own + // grpc port from `-gateway`, beside `-minio` and + // a separate `-network` Docker network. Each container belongs + // to this service. + {name: "its agent's gateway container", mutate: func(c *container.Summary) { + suffixed(c, "-gateway") + }, refused: false}, + {name: "its agent's minio container", mutate: func(c *container.Summary) { + suffixed(c, "-minio") + }, refused: false}, + // A sibling service is still not this one, suffix or no suffix. + {name: "a sibling service's container", mutate: func(c *container.Summary) { + c.Names = []string{"/codefly-other-web-gateway-minio"} + c.Labels[dockerrun.LabelCodeflyName] = "codefly-other-web-gateway-minio" + c.Labels[dockerrun.LabelCodeflyService] = "other/gateway" + }, refused: true}, + {name: "distinct sibling sharing the name prefix", mutate: func(c *container.Summary) { + suffixed(c, "-admin") + c.Labels[dockerrun.LabelCodeflyService] = "web/gateway-admin" + }, refused: true}, + {name: "label and concrete name disagree", mutate: func(c *container.Summary) { + c.Names[0] += "-other" + }, refused: true}, + {name: "legacy exact bare name", mutate: func(c *container.Summary) { + delete(c.Labels, dockerrun.LabelCodeflyService) + }}, + {name: "legacy suffix proves no parent", mutate: func(c *container.Summary) { + suffixed(c, "-gateway") + delete(c.Labels, dockerrun.LabelCodeflyService) + }, refused: true}, + {name: "malformed parent on exact bare name", mutate: func(c *container.Summary) { + c.Labels[dockerrun.LabelCodeflyService] = "web/gateway/other" + }, refused: true}, + {name: "conflicting parent on exact bare name", mutate: func(c *container.Summary) { + c.Labels[dockerrun.LabelCodeflyService] = "web/other" }, refused: true}, {name: "ephemeral", mutate: func(c *container.Summary) { c.Labels[dockerrun.LabelCodeflyEphemeral] = "true" }, refused: true}, {name: "not running", mutate: func(c *container.Summary) { c.State = "exited" }, refused: true}, @@ -108,6 +147,14 @@ func TestAdoptedContainerAddressesExcuseNothingWithoutARecoveryIdentity(t *testi require.False(t, runner.adoptedContainerAddresses(context.Background())("127.0.0.1:5432")) } +// suffixed renames a kept container the way an agent that runs more than one +// container for a service does, through Core's own ContainerName. +func suffixed(c *container.Summary, suffix string) { + name := c.Labels[dockerrun.LabelCodeflyName] + suffix + c.Names = []string{"/" + name} + c.Labels[dockerrun.LabelCodeflyName] = name +} + // A publication covers only its own loopback family. func TestPublishesAddressIsPerLoopbackFamily(t *testing.T) { c := container.Summary{Ports: []container.Port{{IP: "0.0.0.0", PublicPort: 5432, Type: "tcp"}}} diff --git a/pkg/orchestration/runner.go b/pkg/orchestration/runner.go index 5cdda09f..fe9c4be6 100644 --- a/pkg/orchestration/runner.go +++ b/pkg/orchestration/runner.go @@ -1126,8 +1126,8 @@ type portCollision struct { // of both families before the service binds sees either squatter. // // adopted excuses an address whose listener is the service's own: the -// container its agent will adopt (Runner.adoptedContainerAddresses). nil -// excuses nothing. +// container attributed by Runner.adoptedContainerAddresses. The agent decides +// actual reuse during Init. nil excuses nothing. func boundNativePorts(ctx context.Context, mappings []*basev0.NetworkMapping, adopted func(address string) bool) []string { var collisions []portCollision for _, mapping := range mappings { @@ -1282,7 +1282,7 @@ func (runner *Runner) checkInitialPortAvailability(ctx context.Context, proposed return nil } // A listener that is the service's own kept container is not a ghost: its - // agent adopts it (keep-running), and 'codefly clear' would delete it. + // agent decides its reuse (keep-running); this guard must not delete it. if held := boundNativePorts(ctx, proposed, runner.adoptedContainerAddresses(ctx)); len(held) > 0 { // The holder is whoever bound the port first — it may be a leftover // codefly run, but it may equally be the user's own server, so the