diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e6a098..d2800a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,11 +130,11 @@ jobs: - name: Capture sanitized backend logs if: always() && steps.prepare.outcome == 'success' shell: bash - run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile watch-refresh logs --no-color 2>&1 | perl scripts/sanitize-logs.pl + run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile runtime-registration --profile watch-refresh logs --no-color 2>&1 | perl scripts/sanitize-logs.pl - name: Tear down backend assembly if: always() && steps.prepare.outcome == 'success' - run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile watch-refresh down --volumes --remove-orphans + run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile runtime-registration --profile watch-refresh down --volumes --remove-orphans browser: runs-on: ubuntu-latest diff --git a/.github/workflows/core-integration.yml b/.github/workflows/core-integration.yml index a388f52..44ec3ff 100644 --- a/.github/workflows/core-integration.yml +++ b/.github/workflows/core-integration.yml @@ -83,11 +83,11 @@ jobs: - name: Capture sanitized backend logs if: always() && steps.prepare.outcome == 'success' shell: bash - run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile watch-refresh logs --no-color 2>&1 | perl scripts/sanitize-logs.pl + run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile runtime-registration --profile watch-refresh logs --no-color 2>&1 | perl scripts/sanitize-logs.pl - name: Tear down backend assembly if: always() && steps.prepare.outcome == 'success' - run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile watch-refresh down --volumes --remove-orphans + run: docker compose --project-name "$NEKIRO_E2E_COMPOSE_PROJECT" --file compose.yaml --profile runtime-registration --profile watch-refresh down --volumes --remove-orphans browser: runs-on: ubuntu-latest diff --git a/README.md b/README.md index ca89de1..b8d5c15 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ explicitly because instance-directory behavior is outside those checks. |---|---|---| | Manifest | `go run ./cmd/manifest-validator components.json` | All five component owners and immutable revisions validate | | Compose | `docker compose --file compose.yaml config --quiet` | Configuration exits `0` with no `build:` or floating image | -| Backend acceptance | `go test -tags=e2e -count=1 ./tests/backend` | Register → publish → discover → install → invoke → record passes, including cross-runtime lineage | +| Backend acceptance | `go test -tags=e2e -count=1 ./tests/backend` | Provider registration lease → Nacos watch discovery → install → Router-only invoke → Ledger record passes, including cross-runtime lineage, explicit deregistration, fail-closed removal, and replacement recovery | | Browser acceptance | Console `pnpm test:e2e` in Stack CI | Every production Console scenario passes through the live Gateway | A healthy container set alone is not acceptance. Backend success requires the diff --git a/components.json b/components.json index 65d3fac..09ffe7c 100644 --- a/components.json +++ b/components.json @@ -1,10 +1,10 @@ { "schemaVersion": "1", - "contractIdentity": "NeKiro/contracts@bf705bf0b2cfb412d0c21e46a3b7c0c2bdbe0aa1", + "contractIdentity": "NeKiro/contracts@a737184be63533a578355f98d1b8b94648e05910", "components": { "core": { "repository": "NeKiro-project/NeKiro", - "commitSha": "bf705bf0b2cfb412d0c21e46a3b7c0c2bdbe0aa1" + "commitSha": "a737184be63533a578355f98d1b8b94648e05910" }, "console": { "repository": "NeKiro-project/NeKiro-Console", @@ -16,7 +16,7 @@ }, "samples": { "repository": "NeKiro-project/NeKiro-Samples", - "commitSha": "8f6842eab413d9cc1926cbb247dc5e26cc0a7fa4" + "commitSha": "143685bebf3dc88819d3192e9047c1204a2f871d" }, "transportGo": { "repository": "NeKiro-project/nekiro-a2a-transport-go", diff --git a/compose.yaml b/compose.yaml index 3b6bb9f..ff22b26 100644 --- a/compose.yaml +++ b/compose.yaml @@ -216,20 +216,30 @@ services: start_period: 5s runtime-b-directory: + profiles: ["runtime-registration"] image: "${NEKIRO_RUNTIME_B_IMAGE:?NEKIRO_RUNTIME_B_IMAGE must be set to the prepared immutable image}" environment: RUNTIME_B_LISTEN_ADDR: "0.0.0.0:8092" RUNTIME_B_AGENT_ID: "runtime-b" RUNTIME_B_INSTANCE_ID: "runtime-b-directory" RUNTIME_B_REGISTRATION_MODE: "nacos" + RUNTIME_B_AGENT_CARD_VERSION: "${RUNTIME_B_AGENT_CARD_VERSION:-}" + RUNTIME_B_RELEASE_ID: "${RUNTIME_B_RELEASE_ID:-}" + RUNTIME_B_CARD_DIGEST: "${RUNTIME_B_CARD_DIGEST:-}" + RUNTIME_B_CANONICAL_ENDPOINT: "${RUNTIME_B_CANONICAL_ENDPOINT:-}" + RUNTIME_B_AUDIENCE: "${RUNTIME_B_AUDIENCE:-}" RUNTIME_B_NACOS_API_ORIGIN: "http://nacos:8848/nacos" RUNTIME_B_NACOS_NAMESPACE_ID: "nekiro" RUNTIME_B_NACOS_GROUP_NAME: "NEKIRO" RUNTIME_B_NACOS_SERVICE_NAME: "runtime-b" RUNTIME_B_NACOS_CLUSTER_NAME: "DEFAULT" + RUNTIME_B_NACOS_PORT_NAME: "a2a" RUNTIME_B_NACOS_ADVERTISED_IP: "172.28.0.12" RUNTIME_B_NACOS_ADVERTISED_PORT: "8092" + RUNTIME_B_NACOS_WEIGHT: "1" RUNTIME_B_NACOS_HEARTBEAT_INTERVAL_MS: "2000" + RUNTIME_B_NACOS_HEARTBEAT_TIMEOUT_MS: "5000" + RUNTIME_B_NACOS_IP_DELETE_TIMEOUT_MS: "10000" RUNTIME_B_NACOS_REQUEST_TIMEOUT_MS: "3000" RUNTIME_B_NACOS_AUTH_MODE: "none" RUNTIME_B_ROUTER_URL: "http://a2a-router:8081" @@ -264,14 +274,23 @@ services: RUNTIME_B_AGENT_ID: "runtime-b" RUNTIME_B_INSTANCE_ID: "runtime-b-primary" RUNTIME_B_REGISTRATION_MODE: "nacos" + RUNTIME_B_AGENT_CARD_VERSION: "${RUNTIME_B_AGENT_CARD_VERSION:-}" + RUNTIME_B_RELEASE_ID: "${RUNTIME_B_RELEASE_ID:-}" + RUNTIME_B_CARD_DIGEST: "${RUNTIME_B_CARD_DIGEST:-}" + RUNTIME_B_CANONICAL_ENDPOINT: "${RUNTIME_B_CANONICAL_ENDPOINT:-}" + RUNTIME_B_AUDIENCE: "${RUNTIME_B_AUDIENCE:-}" RUNTIME_B_NACOS_API_ORIGIN: "http://nacos:8848/nacos" RUNTIME_B_NACOS_NAMESPACE_ID: "nekiro" RUNTIME_B_NACOS_GROUP_NAME: "NEKIRO" RUNTIME_B_NACOS_SERVICE_NAME: "runtime-b" RUNTIME_B_NACOS_CLUSTER_NAME: "DEFAULT" + RUNTIME_B_NACOS_PORT_NAME: "a2a" RUNTIME_B_NACOS_ADVERTISED_IP: "172.28.0.13" RUNTIME_B_NACOS_ADVERTISED_PORT: "8092" + RUNTIME_B_NACOS_WEIGHT: "1" RUNTIME_B_NACOS_HEARTBEAT_INTERVAL_MS: "2000" + RUNTIME_B_NACOS_HEARTBEAT_TIMEOUT_MS: "5000" + RUNTIME_B_NACOS_IP_DELETE_TIMEOUT_MS: "10000" RUNTIME_B_NACOS_REQUEST_TIMEOUT_MS: "3000" RUNTIME_B_NACOS_AUTH_MODE: "none" RUNTIME_B_ROUTER_URL: "http://a2a-router:8081" @@ -299,20 +318,63 @@ services: start_period: 5s runtime-a: + image: "${NEKIRO_RUNTIME_A_IMAGE:?NEKIRO_RUNTIME_A_IMAGE must be set to the prepared immutable image}" + environment: + RUNTIME_A_LISTEN_ADDR: "0.0.0.0:8091" + RUNTIME_A_AGENT_ID: "runtime-a" + RUNTIME_A_INSTANCE_ID: "runtime-a-primary" + RUNTIME_A_REGISTRATION_MODE: "disabled" + RUNTIME_A_ROUTER_URL: "http://a2a-router:8081" + RUNTIME_A_ROUTER_TOKEN: "${RUNTIME_A_ROUTER_TOKEN:?RUNTIME_A_ROUTER_TOKEN must be set and non-empty}" + RUNTIME_A_TARGET_AGENT_ID: "runtime-b" + RUNTIME_A_TARGET_CAPABILITY: "runtime.echo" + RUNTIME_A_RESPONSE_LIMIT_BYTES: "${RUNTIME_A_RESPONSE_LIMIT_BYTES:?RUNTIME_A_RESPONSE_LIMIT_BYTES must be set and non-empty}" + RUNTIME_A_EVENT_LIMIT_BYTES: "${RUNTIME_A_EVENT_LIMIT_BYTES:?RUNTIME_A_EVENT_LIMIT_BYTES must be set and non-empty}" + NEKIRO_AGENT_CHALLENGE_DIRECTORY: "/tmp/nekiro-challenges" + NEKIRO_AGENT_ROUTER_ISSUER: "${NEKIRO_AGENT_ROUTER_ISSUER:?NEKIRO_AGENT_ROUTER_ISSUER must be set and non-empty}" + NEKIRO_AGENT_ROUTER_AUDIENCE: "http://runtime-a:8091" + NEKIRO_AGENT_ROUTER_KEY_ID: "${NEKIRO_AGENT_ROUTER_KEY_ID:?NEKIRO_AGENT_ROUTER_KEY_ID must be set and non-empty}" + NEKIRO_AGENT_ROUTER_PUBLIC_KEY_BASE64URL: "${NEKIRO_AGENT_ROUTER_PUBLIC_KEY_BASE64URL:?NEKIRO_AGENT_ROUTER_PUBLIC_KEY_BASE64URL must be set and non-empty}" + depends_on: + a2a-router: + condition: service_healthy + runtime-b: + condition: service_healthy + networks: + platform-internal: + ipv4_address: 172.28.0.11 + healthcheck: + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8091/readyz"] + interval: 5s + timeout: 5s + retries: 12 + start_period: 5s + + runtime-a-directory: + profiles: ["runtime-registration"] image: "${NEKIRO_RUNTIME_A_IMAGE:?NEKIRO_RUNTIME_A_IMAGE must be set to the prepared immutable image}" environment: RUNTIME_A_LISTEN_ADDR: "0.0.0.0:8091" RUNTIME_A_AGENT_ID: "runtime-a" RUNTIME_A_INSTANCE_ID: "runtime-a-primary" RUNTIME_A_REGISTRATION_MODE: "nacos" + RUNTIME_A_AGENT_CARD_VERSION: "${RUNTIME_A_AGENT_CARD_VERSION:-}" + RUNTIME_A_RELEASE_ID: "${RUNTIME_A_RELEASE_ID:-}" + RUNTIME_A_CARD_DIGEST: "${RUNTIME_A_CARD_DIGEST:-}" + RUNTIME_A_CANONICAL_ENDPOINT: "${RUNTIME_A_CANONICAL_ENDPOINT:-}" + RUNTIME_A_AUDIENCE: "${RUNTIME_A_AUDIENCE:-}" RUNTIME_A_NACOS_API_ORIGIN: "http://nacos:8848/nacos" RUNTIME_A_NACOS_NAMESPACE_ID: "nekiro" RUNTIME_A_NACOS_GROUP_NAME: "NEKIRO" RUNTIME_A_NACOS_SERVICE_NAME: "runtime-a" RUNTIME_A_NACOS_CLUSTER_NAME: "DEFAULT" - RUNTIME_A_NACOS_ADVERTISED_IP: "172.28.0.11" + RUNTIME_A_NACOS_PORT_NAME: "a2a" + RUNTIME_A_NACOS_ADVERTISED_IP: "172.28.0.14" RUNTIME_A_NACOS_ADVERTISED_PORT: "8091" + RUNTIME_A_NACOS_WEIGHT: "1" RUNTIME_A_NACOS_HEARTBEAT_INTERVAL_MS: "2000" + RUNTIME_A_NACOS_HEARTBEAT_TIMEOUT_MS: "5000" + RUNTIME_A_NACOS_IP_DELETE_TIMEOUT_MS: "10000" RUNTIME_A_NACOS_REQUEST_TIMEOUT_MS: "3000" RUNTIME_A_NACOS_AUTH_MODE: "none" RUNTIME_A_ROUTER_URL: "http://a2a-router:8081" @@ -333,7 +395,7 @@ services: condition: service_healthy networks: platform-internal: - ipv4_address: 172.28.0.11 + ipv4_address: 172.28.0.14 healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8091/readyz"] interval: 5s diff --git a/go.mod b/go.mod index f5cb5d5..72b5137 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/NeKiro-project/NeKiro-Stack go 1.26.0 require ( - github.com/NeKiro-project/NeKiro v0.0.0-20260809083232-6cee25590bb7 + github.com/NeKiro-project/NeKiro v0.0.0-20260810073448-a737184be635 github.com/jackc/pgx/v5 v5.10.0 ) diff --git a/go.sum b/go.sum index 74d6b7c..4bde0bf 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/NeKiro-project/NeKiro v0.0.0-20260809083232-6cee25590bb7 h1:ju+I5nKZxgl/22830m+PewVUeqSrw8dYAGHPl7aBZyg= -github.com/NeKiro-project/NeKiro v0.0.0-20260809083232-6cee25590bb7/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= +github.com/NeKiro-project/NeKiro v0.0.0-20260810073448-a737184be635 h1:lNVDKPv040qm4EGN26vet3feeSyvfDmTS51ZUWJnjdI= +github.com/NeKiro-project/NeKiro v0.0.0-20260810073448-a737184be635/go.mod h1:JCIEeiLu52WC/Q5QlcAKmWKRtW7CNLkZ3lV3BAn92Oo= github.com/a2aproject/a2a-go v0.3.15 h1:h5YpCiPq3jxQ5rIns7oDjPag3ivP8u817AzdA4F+NiI= github.com/a2aproject/a2a-go v0.3.15/go.mod h1:I7Cm+a1oL+UT6zMoP+roaRE5vdfUa1iQGVN8aSOuZ0I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/tests/backend/invoke_record_test.go b/tests/backend/invoke_record_test.go index e74abb1..e4596cb 100644 --- a/tests/backend/invoke_record_test.go +++ b/tests/backend/invoke_record_test.go @@ -118,7 +118,7 @@ func TestInvokeToRecordAcceptance(t *testing.T) { "policy-content-secret", "protocol-content-secret", "agent-content-secret", "route-content-secret", "timeout-content-secret", "cancel-content-secret", "interrupted-content-secret", "dependency-content-secret", "dependency-raw-secret", - "snapshot-refresh-value", + "snapshot-refresh-value", "removed-runtime-value", }, env.credentialForbidden...) client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, Timeout: 45 * time.Second} if result := doRequest(t, client, env.controlPlane+"/readyz", http.MethodGet, "", "", nil); result.status != http.StatusNoContent { @@ -138,6 +138,7 @@ func TestInvokeToRecordAcceptance(t *testing.T) { registerAndPublish(t, client, &env, runtimeTimeout) registerAndPublish(t, client, &env, runtimeInterrupted) runtimeLifecycleRelease := registerAndPublish(t, client, &env, runtimeLifecycle) + startRegisteredRuntimes(t, env) assertNacosRegistrations(t, client, env) publishRouterNacosBindings(t, client, env) assertPublicAgentInputMatrix(t, client, env, contracts.CatalogEntry{PublicAgentID: env.publicAgentIDs["runtime-a"], PublicURL: env.publicAgentOrigin + "/a/" + env.publicAgentIDs["runtime-a"]}) @@ -308,12 +309,29 @@ func assertNacosRegistrations(t *testing.T, client *http.Client, env acceptanceE } } +func startRegisteredRuntimes(t *testing.T, env acceptanceEnv) { + t.Helper() + command := composeCommand( + t.Context(), env, + "--profile", "runtime-registration", "up", "--detach", "--no-deps", "--force-recreate", + "--wait", "--wait-timeout", "60", "runtime-a-directory", "runtime-b-directory", + ) + if output, err := command.CombinedOutput(); err != nil { + t.Fatalf("start exact-Release runtime registrations: %v output=%s", err, output) + } +} + func replaceRuntimeBNacosInstance(t *testing.T, client *http.Client, env acceptanceEnv) { t.Helper() - stop := composeCommand(t.Context(), env, "stop", "runtime-b-directory") + release := env.releases["runtime-b"] + populated := waitForRouterTopologyState(t, client, env, release, contracts.RouterTopologyStatePopulated, 0) + stop := composeCommand(t.Context(), env, "--profile", "runtime-registration", "stop", "runtime-b-directory") if output, err := stop.CombinedOutput(); err != nil { t.Fatalf("stop original Runtime B directory instance: %v output=%s", err, output) } + waitForNacosInstanceRemoval(t, client, env, "runtime-b") + empty := waitForRouterTopologyState(t, client, env, release, contracts.RouterTopologyStateEmpty, populated.LocalRevision+1) + assertRemovedRuntimeRejected(t, client, env, contracts.ErrorCodeDependency) start := composeCommand( t.Context(), env, @@ -324,6 +342,80 @@ func replaceRuntimeBNacosInstance(t *testing.T, client *http.Client, env accepta t.Fatalf("start replacement Runtime B directory instance: %v output=%s", err, output) } waitForNacosInstance(t, client, env, "runtime-b", "runtime-b-primary") + waitForRouterTopologyState(t, client, env, release, contracts.RouterTopologyStatePopulated, empty.LocalRevision+1) +} + +func waitForRouterTopologyState( + t *testing.T, + client *http.Client, + env acceptanceEnv, + release contracts.AgentReleaseResponse, + wantState contracts.RouterTopologyObservationState, + minimumRevision uint64, +) contracts.RouterTopologyStatusObservationV1 { + t.Helper() + deadline := time.Now().Add(30 * time.Second) + var last httpResult + for time.Now().Before(deadline) { + last = doRequest(t, client, env.routerURL+"/internal/v1/instance-topology/status", http.MethodGet, env.routerToken, "", nil) + var status contracts.RouterTopologyStatusV1 + if last.status == http.StatusOK { + assertNoForbiddenBody(t, last.body, env.forbidden, "Router topology status") + } + if last.status == http.StatusOK && json.Unmarshal(last.body, &status) == nil && contracts.ValidateRouterTopologyStatusV1(status) == nil && status.Provider == "nacos" { + for _, observation := range status.Observations { + if observation.AgentID == release.AgentID && observation.AgentCardVersion == release.AgentCardVersion && observation.ReleaseID == release.ReleaseID && + observation.State == wantState && observation.LocalRevision >= minimumRevision { + return observation + } + } + } + time.Sleep(250 * time.Millisecond) + } + t.Fatalf("Router topology did not reach %s at revision >= %d for %s/%s/%s: status=%d body=%s", + wantState, minimumRevision, release.AgentID, release.AgentCardVersion, release.ReleaseID, last.status, last.body) + return contracts.RouterTopologyStatusObservationV1{} +} + +func waitForNacosInstanceRemoval(t *testing.T, client *http.Client, env acceptanceEnv, serviceName string) { + t.Helper() + endpoint, err := url.Parse(env.nacosURL + "/v1/ns/instance/list") + if err != nil { + t.Fatal(err) + } + query := endpoint.Query() + query.Set("serviceName", serviceName) + query.Set("groupName", "NEKIRO") + query.Set("clusters", "DEFAULT") + query.Set("namespaceId", "nekiro") + query.Set("healthyOnly", "false") + endpoint.RawQuery = query.Encode() + deadline := time.Now().Add(30 * time.Second) + var last httpResult + for time.Now().Before(deadline) { + last = doRequest(t, client, endpoint.String(), http.MethodGet, "", "", nil) + var response struct { + Hosts []json.RawMessage `json:"hosts"` + } + if last.status == http.StatusOK && json.Unmarshal(last.body, &response) == nil && len(response.Hosts) == 0 { + return + } + time.Sleep(250 * time.Millisecond) + } + t.Fatalf("Nacos service %s retained an instance after lease close: status=%d body=%s", serviceName, last.status, last.body) +} + +func assertRemovedRuntimeRejected(t *testing.T, client *http.Client, env acceptanceEnv, wantCode contracts.PlatformErrorCode) { + t.Helper() + result, err := doRequestRaw(t.Context(), client, env.controlPlane+"/v4/workspaces/"+acceptanceWorkspace+"/invocations", http.MethodPost, env.ownerToken, "application/json", map[string]any{ + "agentId": "runtime-b", "capability": "runtime.echo", + "input": map[string]any{"fixture": "success", "value": "removed-runtime-value"}, "stream": false, + }) + if err != nil { + t.Fatal(err) + } + validated := assertCorrelatedInvocationError(t, result, wantCode, env.forbidden) + assertRecord(t, client, env, validated.InvocationID, acceptanceWorkspace, "runtime-b", "failed", string(wantCode)) } func waitForNacosInstance(t *testing.T, client *http.Client, env acceptanceEnv, serviceName, instanceID string) { @@ -423,7 +515,27 @@ func requiredEnv(t *testing.T, name string) string { func composeCommand(ctx context.Context, env acceptanceEnv, args ...string) *exec.Cmd { base := []string{"compose", "--project-name", env.composeProject, "--file", env.composeFile} - return exec.CommandContext(ctx, "docker", append(base, args...)...) + command := exec.CommandContext(ctx, "docker", append(base, args...)...) + command.Env = append(os.Environ(), runtimeRegistrationEnvironment(env)...) + return command +} + +func runtimeRegistrationEnvironment(env acceptanceEnv) []string { + values := make([]string, 0, 10) + for prefix, agentID := range map[string]string{"RUNTIME_A": "runtime-a", "RUNTIME_B": "runtime-b"} { + release, ok := env.releases[agentID] + if !ok { + continue + } + values = append(values, + prefix+"_AGENT_CARD_VERSION="+release.AgentCardVersion, + prefix+"_RELEASE_ID="+release.ReleaseID, + prefix+"_CARD_DIGEST="+release.CardDigest, + prefix+"_CANONICAL_ENDPOINT="+release.EndpointOrigin+release.EndpointPath, + prefix+"_AUDIENCE="+release.EndpointOrigin, + ) + } + return values } func acceptanceCard(agentID, name, endpoint, capability string, permissions []string, streaming bool) []byte { @@ -1911,7 +2023,7 @@ func assertStorageAndLogsAreMetadataOnly(t *testing.T, env acceptanceEnv) { t.Fatal(err) } installationRows.Close() - logs := composeCommand(ctx, env, "--profile", "watch-refresh", "logs", "--no-color") + logs := composeCommand(ctx, env, "--profile", "runtime-registration", "--profile", "watch-refresh", "logs", "--no-color") output, err := logs.Output() if err != nil { t.Fatal(err)