From cdcfbf775296dba86f1e5fc80efc0e4ba52d38ef Mon Sep 17 00:00:00 2001 From: Nene7ko_ <1604009816@qq.com> Date: Sat, 8 Aug 2026 12:45:53 +0800 Subject: [PATCH 1/5] test: prove configured runtime instance routing --- .env.example | 9 ++++ .gitignore | 1 + README.md | 10 +++++ components.json | 6 +-- compose.yaml | 37 +++++++++++++++ go.mod | 2 +- go.sum | 4 +- scripts/write-ci-env.sh | 11 +++++ tests/backend/invoke_record_test.go | 70 ++++++++++++++++++++++++++--- 9 files changed, 139 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 16af1cd..878074a 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,15 @@ NEKIRO_A2A_ROUTER_IMAGE=replace-with-prepared-image NEKIRO_RUNTIME_A_IMAGE=replace-with-prepared-image NEKIRO_RUNTIME_B_IMAGE=replace-with-prepared-image +# Select exactly one Router instance-routing mode. Use direct to dispatch to +# the exact published Card endpoint, or config_center_file to select an +# instance from the exact Release-scoped directory document. +NEKIRO_ROUTER_INSTANCE_ROUTING_MODE=replace-with-direct-or-config_center_file +NEKIRO_ROUTER_CONFIG_CENTER_ROOT=replace-with-absolute-host-directory +NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES=replace-with-explicit-byte-limit +NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY=replace-with-directory-key +NEKIRO_ROUTER_INSTANCE_PORT_NAME=replace-with-tcp-port-name + # All remaining required service policy, credentials, byte limits, and origins # are intentionally omitted here rather than assigned unsafe defaults. Supply # them explicitly as documented by the owning core and Samples repositories. diff --git a/.gitignore b/.gitignore index 549774e..533cf03 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ Thumbs.db .worktrees/ .repowiki-site/ site/ +.runtime-config/ scripts/__pycache__/ diff --git a/README.md b/README.md index bdd46db..ca89de1 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,16 @@ docker compose --project-name nekiro-stack --file compose.yaml up --detach --wai go test -tags=e2e -count=1 ./tests/backend ``` +`NEKIRO_ROUTER_INSTANCE_ROUTING_MODE` must be either `direct` or +`config_center_file`; there is no automatic fallback between them. File mode +reads the exact Release-scoped `router-instance-directory.v1` document from +the absolute host directory named by `NEKIRO_ROUTER_CONFIG_CENTER_ROOT`. +Missing, malformed, empty, or ambiguous instance data makes new Invocations +fail closed. The backend acceptance scenario publishes one ready Runtime B +replica into that directory and verifies both JSON and SSE dispatch to its +reported instance ID. Compose and browser scenarios select `direct` +explicitly because instance-directory behavior is outside those checks. + ## Test matrix and success signals | Check | Command or workflow | Successful result | diff --git a/components.json b/components.json index 0760bf5..4477226 100644 --- a/components.json +++ b/components.json @@ -1,10 +1,10 @@ { "schemaVersion": "1", - "contractIdentity": "NeKiro/contracts@cac9cbf4c9b74e06b0d68e467dd8e03a5f822f20", + "contractIdentity": "NeKiro/contracts@ce3f6c4881d3d08abfe65ac3da2e0bfa37561f74", "components": { "core": { "repository": "NeKiro-project/NeKiro", - "commitSha": "cac9cbf4c9b74e06b0d68e467dd8e03a5f822f20" + "commitSha": "ce3f6c4881d3d08abfe65ac3da2e0bfa37561f74" }, "console": { "repository": "NeKiro-project/NeKiro-Console", @@ -16,7 +16,7 @@ }, "samples": { "repository": "NeKiro-project/NeKiro-Samples", - "commitSha": "0032b94043385cae7951680a865f307bec80b6f6" + "commitSha": "5bc9dde207732d25e853520f39144d2bfee8c879" }, "transportGo": { "repository": "NeKiro-project/nekiro-a2a-transport-go", diff --git a/compose.yaml b/compose.yaml index 2775d00..3e25ffa 100644 --- a/compose.yaml +++ b/compose.yaml @@ -106,6 +106,13 @@ services: NEKIRO_ROUTER_AGENT_CREDENTIAL_KEY_ID: "${NEKIRO_ROUTER_AGENT_CREDENTIAL_KEY_ID:?NEKIRO_ROUTER_AGENT_CREDENTIAL_KEY_ID must be set and non-empty}" NEKIRO_ROUTER_AGENT_CREDENTIAL_PRIVATE_KEY_BASE64URL: "${NEKIRO_ROUTER_AGENT_CREDENTIAL_PRIVATE_KEY_BASE64URL:?NEKIRO_ROUTER_AGENT_CREDENTIAL_PRIVATE_KEY_BASE64URL must be set and non-empty}" NEKIRO_ROUTER_AGENT_CREDENTIAL_TTL_SECONDS: "${NEKIRO_ROUTER_AGENT_CREDENTIAL_TTL_SECONDS:?NEKIRO_ROUTER_AGENT_CREDENTIAL_TTL_SECONDS must be set and non-empty}" + NEKIRO_ROUTER_INSTANCE_ROUTING_MODE: "${NEKIRO_ROUTER_INSTANCE_ROUTING_MODE:?NEKIRO_ROUTER_INSTANCE_ROUTING_MODE must be set and non-empty}" + NEKIRO_ROUTER_CONFIG_CENTER_FILE_ROOT: "/var/lib/nekiro/config" + NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES: "${NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES:?NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES must be set and non-empty}" + NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY: "${NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY:?NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY must be set and non-empty}" + NEKIRO_ROUTER_INSTANCE_PORT_NAME: "${NEKIRO_ROUTER_INSTANCE_PORT_NAME:?NEKIRO_ROUTER_INSTANCE_PORT_NAME must be set and non-empty}" + volumes: + - "${NEKIRO_ROUTER_CONFIG_CENTER_ROOT:?NEKIRO_ROUTER_CONFIG_CENTER_ROOT must be set and non-empty}:/var/lib/nekiro/config" ports: - "127.0.0.1:${A2A_ROUTER_PORT:?A2A_ROUTER_PORT must be set and non-empty}:8081" depends_on: @@ -128,6 +135,36 @@ services: environment: RUNTIME_B_LISTEN_ADDR: "0.0.0.0:8092" RUNTIME_B_AGENT_ID: "runtime-b" + RUNTIME_B_INSTANCE_ID: "runtime-b-primary" + RUNTIME_B_ROUTER_URL: "http://a2a-router:8081" + RUNTIME_B_ROUTER_TOKEN: "${RUNTIME_B_ROUTER_TOKEN:?RUNTIME_B_ROUTER_TOKEN must be set and non-empty}" + RUNTIME_B_TARGET_AGENT_ID: "runtime-a" + RUNTIME_B_TARGET_CAPABILITY: "runtime.echo" + RUNTIME_B_RESPONSE_LIMIT_BYTES: "${RUNTIME_B_RESPONSE_LIMIT_BYTES:?RUNTIME_B_RESPONSE_LIMIT_BYTES must be set and non-empty}" + RUNTIME_B_EVENT_LIMIT_BYTES: "${RUNTIME_B_EVENT_LIMIT_BYTES:?RUNTIME_B_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-b:8092" + 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 + networks: + - platform-internal + healthcheck: + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8092/readyz"] + interval: 5s + timeout: 5s + retries: 12 + start_period: 5s + + runtime-b-directory: + 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_ROUTER_URL: "http://a2a-router:8081" RUNTIME_B_ROUTER_TOKEN: "${RUNTIME_B_ROUTER_TOKEN:?RUNTIME_B_ROUTER_TOKEN must be set and non-empty}" RUNTIME_B_TARGET_AGENT_ID: "runtime-a" diff --git a/go.mod b/go.mod index 2b13515..b8594ab 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-20260804142931-aad73c450435 + github.com/NeKiro-project/NeKiro v0.0.0-20260808043100-ce3f6c4881d3 github.com/jackc/pgx/v5 v5.10.0 ) diff --git a/go.sum b/go.sum index 09f267d..dcf4250 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-20260804142931-aad73c450435 h1:Qwd8L31Yto8HZgDvekdMi53jEMNAt1uwqiXPh8IWkVA= -github.com/NeKiro-project/NeKiro v0.0.0-20260804142931-aad73c450435/go.mod h1:ceTCf86U3pNqSjqJXWgkOhfCv+BbEUicGD1IfY+R8Js= +github.com/NeKiro-project/NeKiro v0.0.0-20260808043100-ce3f6c4881d3 h1:PQu/ruIcaQtxNl+jexmwgS2uIg3ibosdE85dCAiabVU= +github.com/NeKiro-project/NeKiro v0.0.0-20260808043100-ce3f6c4881d3/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= 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/scripts/write-ci-env.sh b/scripts/write-ci-env.sh index 7dcac7a..53a1eef 100755 --- a/scripts/write-ci-env.sh +++ b/scripts/write-ci-env.sh @@ -16,6 +16,9 @@ if [[ "$stack_root" != /* ]] || [[ -z "$compose_project" ]]; then fi write_common() { + config_root="$stack_root/.runtime-config/$compose_project" + mkdir -p "$config_root" + printf '%s\n' '{"schemaVersion":"1","revision":"stack-bootstrap-1","targets":[]}' >"$config_root/cfg-v1-cm91dGVyL2luc3RhbmNlLWRpcmVjdG9yeQ.value" cat >>"$output" <<'EOF' POSTGRES_PORT=55432 CONTROL_PLANE_PORT=18080 @@ -44,7 +47,11 @@ RUNTIME_A_RESPONSE_LIMIT_BYTES=1048576 RUNTIME_A_EVENT_LIMIT_BYTES=65536 RUNTIME_B_RESPONSE_LIMIT_BYTES=1048576 RUNTIME_B_EVENT_LIMIT_BYTES=65536 +NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES=1048576 +NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY=router/instance-directory +NEKIRO_ROUTER_INSTANCE_PORT_NAME=a2a EOF + printf 'NEKIRO_ROUTER_CONFIG_CENTER_ROOT=%s\n' "$config_root" >>"$output" printf 'NEKIRO_E2E_COMPOSE_FILE=%s/compose.yaml\n' "$stack_root" >>"$output" printf 'NEKIRO_E2E_COMPOSE_PROJECT=%s\n' "$compose_project" >>"$output" } @@ -78,7 +85,9 @@ NEKIRO_E2E_OWNER_TOKEN=acceptance-owner-token NEKIRO_E2E_USER_TOKEN=acceptance-user-token NEKIRO_E2E_OTHER_TOKEN=acceptance-other-token NEKIRO_E2E_DATABASE_URL=postgresql://nekiro_acceptance:acceptance-only-password@127.0.0.1:55432/nekiro_acceptance?sslmode=disable +NEKIRO_ROUTER_INSTANCE_ROUTING_MODE=config_center_file EOF + printf 'NEKIRO_E2E_CONFIG_CENTER_ROOT=%s\n' "$config_root" >>"$output" ;; browser) write_common @@ -109,6 +118,7 @@ VITE_NEKIRO_PROVIDER_TOKEN=root-console-provider-token VITE_NEKIRO_OWNER_TOKEN=root-console-owner-token VITE_NEKIRO_DEFAULT_WORKSPACE_ID=root-console-workspace VITE_NEKIRO_PUBLIC_AGENT_ORIGIN=https://agents.nekiro.test +NEKIRO_ROUTER_INSTANCE_ROUTING_MODE=direct EOF ;; compose) @@ -135,6 +145,7 @@ NEKIRO_CONTROL_PLANE_IMAGE=nekiro-control-plane:compose-check NEKIRO_A2A_ROUTER_IMAGE=nekiro-a2a-router:compose-check NEKIRO_RUNTIME_A_IMAGE=nekiro-runtime-a:compose-check NEKIRO_RUNTIME_B_IMAGE=nekiro-runtime-b:compose-check +NEKIRO_ROUTER_INSTANCE_ROUTING_MODE=direct EOF ;; *) diff --git a/tests/backend/invoke_record_test.go b/tests/backend/invoke_record_test.go index b400a02..abf8ce9 100644 --- a/tests/backend/invoke_record_test.go +++ b/tests/backend/invoke_record_test.go @@ -49,6 +49,7 @@ type acceptanceEnv struct { databaseURL string composeFile string composeProject string + configCenterRoot string challengeTTL time.Duration credentialIssuer string credentialKeyID string @@ -136,6 +137,7 @@ func TestInvokeToRecordAcceptance(t *testing.T) { registerAndPublish(t, client, &env, runtimeTimeout) registerAndPublish(t, client, &env, runtimeInterrupted) runtimeLifecycleRelease := registerAndPublish(t, client, &env, runtimeLifecycle) + publishRouterInstanceDirectory(t, env) assertPublicAgentInputMatrix(t, client, env, contracts.CatalogEntry{PublicAgentID: env.publicAgentIDs["runtime-a"], PublicURL: env.publicAgentOrigin + "/a/" + env.publicAgentIDs["runtime-a"]}) assertVerificationFailureMatrix(t, client, &env) @@ -157,16 +159,20 @@ func TestInvokeToRecordAcceptance(t *testing.T) { assertUnpublishedReleaseRejected(t, client, &env) assertRouterCredentialFailureMatrix(t, client, &env, runtimeBRelease) - direct := invokeJSON(t, client, env, "runtime-b", "runtime.echo", map[string]any{"fixture": "success", "value": "direct-json-value"}) - if direct.result.Status != "succeeded" || !bytes.Contains(direct.result.Result, []byte("direct-json-value")) { - t.Fatalf("direct JSON result=%s", direct.result.Result) + runtimeBResult := invokeJSON(t, client, env, "runtime-b", "runtime.echo", map[string]any{"fixture": "success", "value": "direct-json-value"}) + if runtimeBResult.result.Status != "succeeded" || !bytes.Contains(runtimeBResult.result.Result, []byte("direct-json-value")) || !bytes.Contains(runtimeBResult.result.Result, []byte(`"instanceId":"runtime-b-directory"`)) { + t.Fatalf("Runtime B JSON result=%s", runtimeBResult.result.Result) } - assertRecord(t, client, env, direct.result.InvocationID, acceptanceWorkspace, "runtime-b", "succeeded", "") + assertRecord(t, client, env, runtimeBResult.result.InvocationID, acceptanceWorkspace, "runtime-b", "succeeded", "") stream := invokeSSE(t, client, env, "runtime-b", "runtime.echo", map[string]any{"fixture": "stream-success", "value": "direct-sse-value"}) if len(stream) < 3 || stream[0].Type != contracts.ResultStreamEventAccepted || stream[len(stream)-1].Type != contracts.ResultStreamEventCompleted { t.Fatalf("SSE sequence=%#v", stream) } + encodedStream, err := json.Marshal(stream) + if err != nil || !bytes.Contains(encodedStream, []byte(`"instanceId":"runtime-b-directory"`)) { + t.Fatalf("SSE did not remain on directory instance: %s error=%v", encodedStream, err) + } for index, event := range stream { if event.Sequence != int64(index) || event.InvocationID == "" || event.RootTaskID == "" || event.TraceID == "" { t.Fatalf("SSE event[%d]=%#v", index, event) @@ -245,6 +251,10 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { if !filepath.IsAbs(composeFile) { t.Fatalf("NEKIRO_E2E_COMPOSE_FILE must be an absolute path") } + configCenterRoot := requiredEnv(t, "NEKIRO_E2E_CONFIG_CENTER_ROOT") + if !filepath.IsAbs(configCenterRoot) { + t.Fatalf("NEKIRO_E2E_CONFIG_CENTER_ROOT must be an absolute path") + } ttlSeconds, err := strconv.ParseInt(requiredEnv(t, "NEKIRO_ENDPOINT_CHALLENGE_TTL_SECONDS"), 10, 64) if err != nil || ttlSeconds < 2 || ttlSeconds > 15 { t.Fatalf("NEKIRO_ENDPOINT_CHALLENGE_TTL_SECONDS must be an acceptance value from 2 through 15 seconds") @@ -260,6 +270,7 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { databaseURL: requiredEnv(t, "NEKIRO_E2E_DATABASE_URL"), composeFile: composeFile, composeProject: requiredEnv(t, "NEKIRO_E2E_COMPOSE_PROJECT"), + configCenterRoot: configCenterRoot, challengeTTL: time.Duration(ttlSeconds) * time.Second, credentialIssuer: requiredEnv(t, "NEKIRO_ROUTER_AGENT_CREDENTIAL_ISSUER"), credentialKeyID: requiredEnv(t, "NEKIRO_ROUTER_AGENT_CREDENTIAL_KEY_ID"), @@ -269,6 +280,55 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { } } +func publishRouterInstanceDirectory(t *testing.T, env acceptanceEnv) { + t.Helper() + agentIDs := make([]string, 0, len(env.releases)) + for agentID := range env.releases { + agentIDs = append(agentIDs, agentID) + } + sort.Strings(agentIDs) + document := contracts.RouterInstanceDirectoryV1{SchemaVersion: contracts.RouterInstanceDirectorySchemaVersion, Revision: "stack-runtime-1", Targets: make([]contracts.RouterInstanceDirectoryTargetV1, 0, len(agentIDs))} + for _, agentID := range agentIDs { + release := env.releases[agentID] + origin, err := url.Parse(release.EndpointOrigin) + if err != nil { + t.Fatalf("parse Release endpoint origin: %v", err) + } + port, err := strconv.Atoi(origin.Port()) + if err != nil || port < 1 || port > 65535 { + t.Fatalf("Release endpoint origin has invalid port: %s", release.EndpointOrigin) + } + address := origin.Hostname() + instanceID := agentID + "-primary" + if agentID == "runtime-b" { + address = "runtime-b-directory" + instanceID = "runtime-b-directory" + } + document.Targets = append(document.Targets, contracts.RouterInstanceDirectoryTargetV1{ + AgentID: agentID, AgentCardVersion: release.AgentCardVersion, ReleaseID: release.ReleaseID, + CardDigest: release.CardDigest, CanonicalEndpoint: release.EndpointOrigin + release.EndpointPath, + Audience: release.EndpointOrigin, + Instances: []contracts.RouterInstanceV1{{ + InstanceID: instanceID, Ready: true, Serving: true, + Endpoints: []contracts.RouterNetworkEndpointV1{{AddressType: "DNS", Address: address, PortName: "a2a", Port: port, Protocol: "TCP"}}, + }}, + }) + } + payload, err := json.Marshal(document) + if err != nil { + t.Fatal(err) + } + const leaf = "cfg-v1-cm91dGVyL2luc3RhbmNlLWRpcmVjdG9yeQ.value" + temporary := filepath.Join(env.configCenterRoot, leaf+".tmp") + destination := filepath.Join(env.configCenterRoot, leaf) + if err := os.WriteFile(temporary, payload, 0o600); err != nil { + t.Fatalf("write Router instance directory: %v", err) + } + if err := os.Rename(temporary, destination); err != nil { + t.Fatalf("publish Router instance directory: %v", err) + } +} + func requiredEnv(t *testing.T, name string) string { t.Helper() value, exists := os.LookupEnv(name) @@ -643,7 +703,7 @@ func assertTrustedPublicationError(t *testing.T, result httpResult, wantStatus i t.Helper() assertNoForbiddenBody(t, result.body, forbidden, "trusted publication error") if result.status != wantStatus { - t.Fatalf("trusted publication status=%d want=%d", result.status, wantStatus) + t.Fatalf("trusted publication status=%d want=%d body=%s", result.status, wantStatus, result.body) } var failure contracts.TrustedPublicationError if err := json.Unmarshal(result.body, &failure); err != nil || failure.Code != wantCode || failure.TraceID == "" { From 8d96bcab751cfba9e8e0f310939b0b18154e94c6 Mon Sep 17 00:00:00 2001 From: Nene7ko_ <1604009816@qq.com> Date: Sat, 8 Aug 2026 12:56:27 +0800 Subject: [PATCH 2/5] chore: pin reviewed runtime routing revision --- components.json | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components.json b/components.json index 4477226..b3b05e8 100644 --- a/components.json +++ b/components.json @@ -1,10 +1,10 @@ { "schemaVersion": "1", - "contractIdentity": "NeKiro/contracts@ce3f6c4881d3d08abfe65ac3da2e0bfa37561f74", + "contractIdentity": "NeKiro/contracts@e883ad4bcfaf75ffe80e9d9e6fb79b2a8c58b18c", "components": { "core": { "repository": "NeKiro-project/NeKiro", - "commitSha": "ce3f6c4881d3d08abfe65ac3da2e0bfa37561f74" + "commitSha": "e883ad4bcfaf75ffe80e9d9e6fb79b2a8c58b18c" }, "console": { "repository": "NeKiro-project/NeKiro-Console", diff --git a/go.mod b/go.mod index b8594ab..f2e7104 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-20260808043100-ce3f6c4881d3 + github.com/NeKiro-project/NeKiro v0.0.0-20260808045545-e883ad4bcfaf github.com/jackc/pgx/v5 v5.10.0 ) diff --git a/go.sum b/go.sum index dcf4250..0d90494 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-20260808043100-ce3f6c4881d3 h1:PQu/ruIcaQtxNl+jexmwgS2uIg3ibosdE85dCAiabVU= -github.com/NeKiro-project/NeKiro v0.0.0-20260808043100-ce3f6c4881d3/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= +github.com/NeKiro-project/NeKiro v0.0.0-20260808045545-e883ad4bcfaf h1:A8V2HXsH4VdiIKjkQKN2gO5abvF3fJykkKTccDoEvFc= +github.com/NeKiro-project/NeKiro v0.0.0-20260808045545-e883ad4bcfaf/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= 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= From 0c1850a90ae105ac3ed2f078b70f8e9be67663de Mon Sep 17 00:00:00 2001 From: Nene7ko_ <1604009816@qq.com> Date: Sat, 8 Aug 2026 13:15:00 +0800 Subject: [PATCH 3/5] test(stack): publish readable runtime directory --- tests/backend/invoke_record_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/backend/invoke_record_test.go b/tests/backend/invoke_record_test.go index abf8ce9..fb91ef4 100644 --- a/tests/backend/invoke_record_test.go +++ b/tests/backend/invoke_record_test.go @@ -321,9 +321,12 @@ func publishRouterInstanceDirectory(t *testing.T, env acceptanceEnv) { const leaf = "cfg-v1-cm91dGVyL2luc3RhbmNlLWRpcmVjdG9yeQ.value" temporary := filepath.Join(env.configCenterRoot, leaf+".tmp") destination := filepath.Join(env.configCenterRoot, leaf) - if err := os.WriteFile(temporary, payload, 0o600); err != nil { + if err := os.WriteFile(temporary, payload, 0o644); err != nil { t.Fatalf("write Router instance directory: %v", err) } + if err := os.Chmod(temporary, 0o644); err != nil { + t.Fatalf("make Router instance directory readable by the Router container: %v", err) + } if err := os.Rename(temporary, destination); err != nil { t.Fatalf("publish Router instance directory: %v", err) } From 8407a2e05baf62986d54880c9b4b931394ba0c9c Mon Sep 17 00:00:00 2001 From: Nene7ko_ <1604009816@qq.com> Date: Sun, 9 Aug 2026 15:56:53 +0800 Subject: [PATCH 4/5] feat(stack): exercise Nacos runtime routing --- components.json | 6 +-- compose.yaml | 80 +++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 4 +- scripts/write-ci-env.sh | 7 ++- tests/backend/invoke_record_test.go | 81 +++++++++++++++++------------ 6 files changed, 138 insertions(+), 42 deletions(-) diff --git a/components.json b/components.json index b3b05e8..e632ac2 100644 --- a/components.json +++ b/components.json @@ -1,10 +1,10 @@ { "schemaVersion": "1", - "contractIdentity": "NeKiro/contracts@e883ad4bcfaf75ffe80e9d9e6fb79b2a8c58b18c", + "contractIdentity": "NeKiro/contracts@3e04a4581bbc7755ef5fa4e487d376d7227e7b8f", "components": { "core": { "repository": "NeKiro-project/NeKiro", - "commitSha": "e883ad4bcfaf75ffe80e9d9e6fb79b2a8c58b18c" + "commitSha": "3e04a4581bbc7755ef5fa4e487d376d7227e7b8f" }, "console": { "repository": "NeKiro-project/NeKiro-Console", @@ -16,7 +16,7 @@ }, "samples": { "repository": "NeKiro-project/NeKiro-Samples", - "commitSha": "5bc9dde207732d25e853520f39144d2bfee8c879" + "commitSha": "8f6842eab413d9cc1926cbb247dc5e26cc0a7fa4" }, "transportGo": { "repository": "NeKiro-project/nekiro-a2a-transport-go", diff --git a/compose.yaml b/compose.yaml index 3e25ffa..e6636e2 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,6 +1,45 @@ name: nekiro services: + nacos: + image: "nacos/nacos-server:v2.5.1@sha256:8987908cb94ed5f9d30522a64493d35732a6c05f216d667a7addb022f3d92e80" + environment: + MODE: standalone + NACOS_AUTH_ENABLE: "false" + networks: + - platform-internal + - local-access + ports: + - "127.0.0.1:${NACOS_PORT:?NACOS_PORT must be set and non-empty}:8848" + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8848/nacos/v1/console/health/readiness"] + interval: 5s + timeout: 5s + retries: 24 + start_period: 20s + + nacos-bootstrap: + image: "curlimages/curl:8.16.0@sha256:463eaf6072688fe96ac64fa623fe73e1dbe25d8ad6c34404a669ad3ce1f104b6" + environment: + NACOS_BINDING_CONTENT: '{"schemaVersion":"1","revision":"stack-bootstrap-1","targets":[]}' + command: + - sh + - -eu + - -c + - | + curl -fsS -X POST http://nacos:8848/nacos/v1/console/namespaces \ + --data-urlencode customNamespaceId=nekiro \ + --data-urlencode namespaceName=nekiro \ + --data-urlencode 'namespaceDesc=NeKiro Stack acceptance' + curl -fsS -X POST 'http://nacos:8848/nacos/v1/cs/configs?dataId=router.nacos-bindings&group=NEKIRO&tenant=nekiro' \ + --data-urlencode content=$$NACOS_BINDING_CONTENT + depends_on: + nacos: + condition: service_healthy + networks: + - platform-internal + restart: "no" + postgres: image: postgres:17.9-bookworm@sha256:47f917f7409eacd22fc5dfb1dee634e1b55cf0c01d1a7eb701be2227a03e0641 environment: @@ -107,6 +146,12 @@ services: NEKIRO_ROUTER_AGENT_CREDENTIAL_PRIVATE_KEY_BASE64URL: "${NEKIRO_ROUTER_AGENT_CREDENTIAL_PRIVATE_KEY_BASE64URL:?NEKIRO_ROUTER_AGENT_CREDENTIAL_PRIVATE_KEY_BASE64URL must be set and non-empty}" NEKIRO_ROUTER_AGENT_CREDENTIAL_TTL_SECONDS: "${NEKIRO_ROUTER_AGENT_CREDENTIAL_TTL_SECONDS:?NEKIRO_ROUTER_AGENT_CREDENTIAL_TTL_SECONDS must be set and non-empty}" NEKIRO_ROUTER_INSTANCE_ROUTING_MODE: "${NEKIRO_ROUTER_INSTANCE_ROUTING_MODE:?NEKIRO_ROUTER_INSTANCE_ROUTING_MODE must be set and non-empty}" + NEKIRO_ROUTER_NACOS_API_ORIGIN: "http://nacos:8848/nacos" + NEKIRO_ROUTER_NACOS_NAMESPACE_ID: "nekiro" + NEKIRO_ROUTER_NACOS_CONFIG_GROUP: "NEKIRO" + NEKIRO_ROUTER_NACOS_AUTH_MODE: "none" + NEKIRO_ROUTER_NACOS_RESPONSE_LIMIT_BYTES: "${NEKIRO_ROUTER_NACOS_RESPONSE_LIMIT_BYTES:?NEKIRO_ROUTER_NACOS_RESPONSE_LIMIT_BYTES must be set and non-empty}" + NEKIRO_ROUTER_NACOS_REQUEST_TIMEOUT_MS: "${NEKIRO_ROUTER_NACOS_REQUEST_TIMEOUT_MS:?NEKIRO_ROUTER_NACOS_REQUEST_TIMEOUT_MS must be set and non-empty}" NEKIRO_ROUTER_CONFIG_CENTER_FILE_ROOT: "/var/lib/nekiro/config" NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES: "${NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES:?NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES must be set and non-empty}" NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY: "${NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY:?NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY must be set and non-empty}" @@ -120,6 +165,8 @@ services: condition: service_completed_successfully control-plane: condition: service_healthy + nacos-bootstrap: + condition: service_completed_successfully networks: - platform-internal - local-access @@ -136,6 +183,7 @@ services: RUNTIME_B_LISTEN_ADDR: "0.0.0.0:8092" RUNTIME_B_AGENT_ID: "runtime-b" RUNTIME_B_INSTANCE_ID: "runtime-b-primary" + RUNTIME_B_REGISTRATION_MODE: "disabled" RUNTIME_B_ROUTER_URL: "http://a2a-router:8081" RUNTIME_B_ROUTER_TOKEN: "${RUNTIME_B_ROUTER_TOKEN:?RUNTIME_B_ROUTER_TOKEN must be set and non-empty}" RUNTIME_B_TARGET_AGENT_ID: "runtime-a" @@ -165,6 +213,17 @@ services: 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_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_ADVERTISED_IP: "172.28.0.12" + RUNTIME_B_NACOS_ADVERTISED_PORT: "8092" + RUNTIME_B_NACOS_HEARTBEAT_INTERVAL_MS: "2000" + RUNTIME_B_NACOS_REQUEST_TIMEOUT_MS: "3000" + RUNTIME_B_NACOS_AUTH_MODE: "none" RUNTIME_B_ROUTER_URL: "http://a2a-router:8081" RUNTIME_B_ROUTER_TOKEN: "${RUNTIME_B_ROUTER_TOKEN:?RUNTIME_B_ROUTER_TOKEN must be set and non-empty}" RUNTIME_B_TARGET_AGENT_ID: "runtime-a" @@ -180,7 +239,8 @@ services: a2a-router: condition: service_healthy networks: - - platform-internal + platform-internal: + ipv4_address: 172.28.0.12 healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8092/readyz"] interval: 5s @@ -193,6 +253,18 @@ services: 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_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_ADVERTISED_PORT: "8091" + RUNTIME_A_NACOS_HEARTBEAT_INTERVAL_MS: "2000" + RUNTIME_A_NACOS_REQUEST_TIMEOUT_MS: "3000" + RUNTIME_A_NACOS_AUTH_MODE: "none" 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" @@ -210,7 +282,8 @@ services: runtime-b: condition: service_healthy networks: - - platform-internal + 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 @@ -224,4 +297,7 @@ volumes: networks: platform-internal: internal: true + ipam: + config: + - subnet: 172.28.0.0/24 local-access: diff --git a/go.mod b/go.mod index f2e7104..d309272 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-20260808045545-e883ad4bcfaf + github.com/NeKiro-project/NeKiro v0.0.0-20260809075040-3e04a4581bbc github.com/jackc/pgx/v5 v5.10.0 ) diff --git a/go.sum b/go.sum index 0d90494..c1fa9f9 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-20260808045545-e883ad4bcfaf h1:A8V2HXsH4VdiIKjkQKN2gO5abvF3fJykkKTccDoEvFc= -github.com/NeKiro-project/NeKiro v0.0.0-20260808045545-e883ad4bcfaf/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= +github.com/NeKiro-project/NeKiro v0.0.0-20260809075040-3e04a4581bbc h1:dnAz5EREuPOplBSChg2/7ti+CnQ/XOWXpZp6sjHYnRM= +github.com/NeKiro-project/NeKiro v0.0.0-20260809075040-3e04a4581bbc/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= 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/scripts/write-ci-env.sh b/scripts/write-ci-env.sh index 53a1eef..b237ecc 100755 --- a/scripts/write-ci-env.sh +++ b/scripts/write-ci-env.sh @@ -21,6 +21,7 @@ write_common() { printf '%s\n' '{"schemaVersion":"1","revision":"stack-bootstrap-1","targets":[]}' >"$config_root/cfg-v1-cm91dGVyL2luc3RhbmNlLWRpcmVjdG9yeQ.value" cat >>"$output" <<'EOF' POSTGRES_PORT=55432 +NACOS_PORT=18848 CONTROL_PLANE_PORT=18080 A2A_ROUTER_PORT=18081 NEKIRO_PUBLIC_AGENT_ORIGIN=https://agents.nekiro.test @@ -50,6 +51,8 @@ RUNTIME_B_EVENT_LIMIT_BYTES=65536 NEKIRO_ROUTER_CONFIG_CENTER_MAX_PAYLOAD_BYTES=1048576 NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY=router/instance-directory NEKIRO_ROUTER_INSTANCE_PORT_NAME=a2a +NEKIRO_ROUTER_NACOS_RESPONSE_LIMIT_BYTES=1048576 +NEKIRO_ROUTER_NACOS_REQUEST_TIMEOUT_MS=3000 EOF printf 'NEKIRO_ROUTER_CONFIG_CENTER_ROOT=%s\n' "$config_root" >>"$output" printf 'NEKIRO_E2E_COMPOSE_FILE=%s/compose.yaml\n' "$stack_root" >>"$output" @@ -85,9 +88,11 @@ NEKIRO_E2E_OWNER_TOKEN=acceptance-owner-token NEKIRO_E2E_USER_TOKEN=acceptance-user-token NEKIRO_E2E_OTHER_TOKEN=acceptance-other-token NEKIRO_E2E_DATABASE_URL=postgresql://nekiro_acceptance:acceptance-only-password@127.0.0.1:55432/nekiro_acceptance?sslmode=disable -NEKIRO_ROUTER_INSTANCE_ROUTING_MODE=config_center_file +NEKIRO_ROUTER_INSTANCE_ROUTING_MODE=nacos +NEKIRO_ROUTER_INSTANCE_DIRECTORY_KEY=router.nacos-bindings EOF printf 'NEKIRO_E2E_CONFIG_CENTER_ROOT=%s\n' "$config_root" >>"$output" + printf 'NEKIRO_E2E_NACOS_URL=http://127.0.0.1:%s/nacos\n' "${NACOS_PORT:-18848}" >>"$output" ;; browser) write_common diff --git a/tests/backend/invoke_record_test.go b/tests/backend/invoke_record_test.go index fb91ef4..0a8a231 100644 --- a/tests/backend/invoke_record_test.go +++ b/tests/backend/invoke_record_test.go @@ -42,6 +42,7 @@ type acceptanceEnv struct { controlPlane string publicAgentOrigin string routerURL string + nacosURL string routerToken string ownerToken string userToken string @@ -49,7 +50,6 @@ type acceptanceEnv struct { databaseURL string composeFile string composeProject string - configCenterRoot string challengeTTL time.Duration credentialIssuer string credentialKeyID string @@ -137,7 +137,8 @@ func TestInvokeToRecordAcceptance(t *testing.T) { registerAndPublish(t, client, &env, runtimeTimeout) registerAndPublish(t, client, &env, runtimeInterrupted) runtimeLifecycleRelease := registerAndPublish(t, client, &env, runtimeLifecycle) - publishRouterInstanceDirectory(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"]}) assertVerificationFailureMatrix(t, client, &env) @@ -251,10 +252,6 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { if !filepath.IsAbs(composeFile) { t.Fatalf("NEKIRO_E2E_COMPOSE_FILE must be an absolute path") } - configCenterRoot := requiredEnv(t, "NEKIRO_E2E_CONFIG_CENTER_ROOT") - if !filepath.IsAbs(configCenterRoot) { - t.Fatalf("NEKIRO_E2E_CONFIG_CENTER_ROOT must be an absolute path") - } ttlSeconds, err := strconv.ParseInt(requiredEnv(t, "NEKIRO_ENDPOINT_CHALLENGE_TTL_SECONDS"), 10, 64) if err != nil || ttlSeconds < 2 || ttlSeconds > 15 { t.Fatalf("NEKIRO_ENDPOINT_CHALLENGE_TTL_SECONDS must be an acceptance value from 2 through 15 seconds") @@ -263,6 +260,7 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { controlPlane: requiredEnv(t, "NEKIRO_E2E_CONTROL_PLANE_URL"), publicAgentOrigin: requiredEnv(t, "NEKIRO_E2E_PUBLIC_AGENT_ORIGIN"), routerURL: requiredEnv(t, "NEKIRO_E2E_ROUTER_URL"), + nacosURL: requiredEnv(t, "NEKIRO_E2E_NACOS_URL"), routerToken: requiredEnv(t, "NEKIRO_E2E_ROUTER_TOKEN"), ownerToken: requiredEnv(t, "NEKIRO_E2E_OWNER_TOKEN"), userToken: requiredEnv(t, "NEKIRO_E2E_USER_TOKEN"), @@ -270,7 +268,6 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { databaseURL: requiredEnv(t, "NEKIRO_E2E_DATABASE_URL"), composeFile: composeFile, composeProject: requiredEnv(t, "NEKIRO_E2E_COMPOSE_PROJECT"), - configCenterRoot: configCenterRoot, challengeTTL: time.Duration(ttlSeconds) * time.Second, credentialIssuer: requiredEnv(t, "NEKIRO_ROUTER_AGENT_CREDENTIAL_ISSUER"), credentialKeyID: requiredEnv(t, "NEKIRO_ROUTER_AGENT_CREDENTIAL_KEY_ID"), @@ -280,55 +277,73 @@ func loadAcceptanceEnv(t *testing.T) acceptanceEnv { } } -func publishRouterInstanceDirectory(t *testing.T, env acceptanceEnv) { +func assertNacosRegistrations(t *testing.T, client *http.Client, env acceptanceEnv) { + t.Helper() + for service, instanceID := range map[string]string{"runtime-a": "runtime-a-primary", "runtime-b": "runtime-b-directory"} { + endpoint, err := url.Parse(env.nacosURL + "/v1/ns/instance/list") + if err != nil { + t.Fatal(err) + } + query := endpoint.Query() + query.Set("serviceName", service) + query.Set("groupName", "NEKIRO") + query.Set("clusters", "DEFAULT") + query.Set("namespaceId", "nekiro") + query.Set("healthyOnly", "false") + endpoint.RawQuery = query.Encode() + result := doRequest(t, client, endpoint.String(), http.MethodGet, "", "", nil) + if result.status != http.StatusOK || !bytes.Contains(result.body, []byte(`"nekiro.instanceId":"`+instanceID+`"`)) || !bytes.Contains(result.body, []byte(`"healthy":true`)) { + t.Fatalf("Nacos registration %s status=%d body=%s", service, result.status, result.body) + } + } +} + +func publishRouterNacosBindings(t *testing.T, client *http.Client, env acceptanceEnv) { t.Helper() agentIDs := make([]string, 0, len(env.releases)) for agentID := range env.releases { agentIDs = append(agentIDs, agentID) } sort.Strings(agentIDs) - document := contracts.RouterInstanceDirectoryV1{SchemaVersion: contracts.RouterInstanceDirectorySchemaVersion, Revision: "stack-runtime-1", Targets: make([]contracts.RouterInstanceDirectoryTargetV1, 0, len(agentIDs))} + document := contracts.RouterNacosInstanceBindingsV1{ + SchemaVersion: contracts.RouterNacosInstanceBindingsSchemaVersion, + Revision: "stack-runtime-1", + Targets: make([]contracts.RouterNacosInstanceBindingTargetV1, 0, len(agentIDs)), + } for _, agentID := range agentIDs { release := env.releases[agentID] origin, err := url.Parse(release.EndpointOrigin) if err != nil { t.Fatalf("parse Release endpoint origin: %v", err) } - port, err := strconv.Atoi(origin.Port()) - if err != nil || port < 1 || port > 65535 { - t.Fatalf("Release endpoint origin has invalid port: %s", release.EndpointOrigin) + serviceName := origin.Hostname() + if serviceName != "runtime-a" && serviceName != "runtime-b" { + t.Fatalf("Release endpoint origin has no owned Nacos service: %s", release.EndpointOrigin) } - address := origin.Hostname() - instanceID := agentID + "-primary" - if agentID == "runtime-b" { - address = "runtime-b-directory" - instanceID = "runtime-b-directory" - } - document.Targets = append(document.Targets, contracts.RouterInstanceDirectoryTargetV1{ + document.Targets = append(document.Targets, contracts.RouterNacosInstanceBindingTargetV1{ AgentID: agentID, AgentCardVersion: release.AgentCardVersion, ReleaseID: release.ReleaseID, CardDigest: release.CardDigest, CanonicalEndpoint: release.EndpointOrigin + release.EndpointPath, - Audience: release.EndpointOrigin, - Instances: []contracts.RouterInstanceV1{{ - InstanceID: instanceID, Ready: true, Serving: true, - Endpoints: []contracts.RouterNetworkEndpointV1{{AddressType: "DNS", Address: address, PortName: "a2a", Port: port, Protocol: "TCP"}}, - }}, + Audience: release.EndpointOrigin, ServiceName: serviceName, GroupName: "NEKIRO", ClusterName: "DEFAULT", }) } payload, err := json.Marshal(document) if err != nil { t.Fatal(err) } - const leaf = "cfg-v1-cm91dGVyL2luc3RhbmNlLWRpcmVjdG9yeQ.value" - temporary := filepath.Join(env.configCenterRoot, leaf+".tmp") - destination := filepath.Join(env.configCenterRoot, leaf) - if err := os.WriteFile(temporary, payload, 0o644); err != nil { - t.Fatalf("write Router instance directory: %v", err) + form := url.Values{"dataId": {"router.nacos-bindings"}, "group": {"NEKIRO"}, "tenant": {"nekiro"}, "type": {"json"}, "content": {string(payload)}} + request, err := http.NewRequestWithContext(t.Context(), http.MethodPost, env.nacosURL+"/v1/cs/configs", strings.NewReader(form.Encode())) + if err != nil { + t.Fatal(err) } - if err := os.Chmod(temporary, 0o644); err != nil { - t.Fatalf("make Router instance directory readable by the Router container: %v", err) + request.Header.Set("Content-Type", "application/x-www-form-urlencoded") + response, err := client.Do(request) + if err != nil { + t.Fatalf("publish Router Nacos bindings: %v", err) } - if err := os.Rename(temporary, destination); err != nil { - t.Fatalf("publish Router instance directory: %v", err) + body, readErr := io.ReadAll(io.LimitReader(response.Body, 4097)) + closeErr := response.Body.Close() + if readErr != nil || closeErr != nil || response.StatusCode != http.StatusOK || len(body) > 4096 || strings.TrimSpace(string(body)) != "true" { + t.Fatalf("publish Router Nacos bindings status=%d body=%s read=%v close=%v", response.StatusCode, body, readErr, closeErr) } } From 70c1166bf05338fe32e4fafcbb953a1108992a0d Mon Sep 17 00:00:00 2001 From: Nene7ko_ <1604009816@qq.com> Date: Sun, 9 Aug 2026 16:33:24 +0800 Subject: [PATCH 5/5] chore(stack): update Core runtime revision --- components.json | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components.json b/components.json index e632ac2..a8545a1 100644 --- a/components.json +++ b/components.json @@ -1,10 +1,10 @@ { "schemaVersion": "1", - "contractIdentity": "NeKiro/contracts@3e04a4581bbc7755ef5fa4e487d376d7227e7b8f", + "contractIdentity": "NeKiro/contracts@6cee25590bb720ed447d70e82204abdad48c5779", "components": { "core": { "repository": "NeKiro-project/NeKiro", - "commitSha": "3e04a4581bbc7755ef5fa4e487d376d7227e7b8f" + "commitSha": "6cee25590bb720ed447d70e82204abdad48c5779" }, "console": { "repository": "NeKiro-project/NeKiro-Console", diff --git a/go.mod b/go.mod index d309272..f5cb5d5 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-20260809075040-3e04a4581bbc + github.com/NeKiro-project/NeKiro v0.0.0-20260809083232-6cee25590bb7 github.com/jackc/pgx/v5 v5.10.0 ) diff --git a/go.sum b/go.sum index c1fa9f9..74d6b7c 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-20260809075040-3e04a4581bbc h1:dnAz5EREuPOplBSChg2/7ti+CnQ/XOWXpZp6sjHYnRM= -github.com/NeKiro-project/NeKiro v0.0.0-20260809075040-3e04a4581bbc/go.mod h1:FMQC8bVPgsnMImg7eHaykOpWTOfQs9uDU6DVMDa1UVQ= +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/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=