Skip to content

Commit db7cdeb

Browse files
fix(gateway): backward-compat /peer/announce + auto-redeploy on SDK version bump (#167)
## Problems ### 1. World containers never appear on homepage (403 signature mismatch) The `PROTOCOL_VERSION` used in Ed25519 domain separators is derived from `packages/agent-world-sdk/package.json` (major.minor). The production gateway was built with SDK 1.4.x (`"1.4"` separators). New world containers run SDK 1.5.1 (`"1.5"` separators). Every `POST /agents` announce → **403 Invalid X-AgentWorld-Signature**. ### 2. CI gap: SDK version bumps didn't trigger gateway redeploy `deploy-gateway.yml` only watched `packages/agent-world-sdk/src/**` — not `package.json`. So upgrading SDK 1.4→1.5 never redeployed the gateway. ### 3. Old SDK (< 1.4) containers use wrong endpoint SDK < 1.4 POSTs to `/peer/announce` which no longer exists. Gateway returned 404 silently. ## Fixes - **`gateway/server.mjs`**: Add `POST /peer/announce` backward-compat route (returns legacy `{peers:[]}` shape); raise default `STALE_TTL_MS` from 90s → 15 min - **`deploy-gateway.yml`**: Add `packages/agent-world-sdk/package.json` to path triggers so any SDK version bump auto-redeploys the gateway --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent 0492d75 commit db7cdeb

3 files changed

Lines changed: 71 additions & 1 deletion

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@resciencelab/agent-world-sdk": patch
3+
---
4+
5+
fix(gateway): add /peer/announce backward-compat route and auto-redeploy on SDK version bump
6+
7+
- Add `POST /peer/announce` backward-compat route for SDK < 1.4 world containers (returns legacy `{peers:[]}` shape)
8+
- Raise default `STALE_TTL_MS` from 90 s to 15 min to prevent old SDK worlds (10 min announce interval, no heartbeat) from being pruned between announces
9+
- Add `packages/agent-world-sdk/package.json` to `deploy-gateway.yml` path triggers so any SDK minor version bump automatically redeploys the gateway (fixes 403 signature mismatch caused by `PROTOCOL_VERSION` changing without gateway redeploy)

.github/workflows/deploy-gateway.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
paths:
77
- "gateway/**"
88
- "packages/agent-world-sdk/src/**"
9+
- "packages/agent-world-sdk/package.json"
910
workflow_dispatch:
1011

1112
concurrency:

gateway/server.mjs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const DEFAULT_HTTP_PORT = parseInt(process.env.HTTP_PORT ?? "8100")
6262
const DEFAULT_PUBLIC_ADDR = process.env.PUBLIC_ADDR ?? null
6363
const DEFAULT_PUBLIC_URL = process.env.PUBLIC_URL ?? null
6464
const DEFAULT_DATA_DIR = process.env.DATA_DIR ?? "/data"
65-
const DEFAULT_STALE_TTL_MS = parseInt(process.env.STALE_TTL_MS ?? String(90 * 1000))
65+
const DEFAULT_STALE_TTL_MS = parseInt(process.env.STALE_TTL_MS ?? String(15 * 60 * 1000))
6666
const WEBHOOK_URL = process.env.WEBHOOK_URL ?? null
6767
const MAX_AGENTS = 500
6868
const REGISTRY_VERSION = 1
@@ -811,6 +811,66 @@ export async function createGatewayApp(opts = {}) {
811811
return { ok: true, agents: getAgentsForExchange(20) };
812812
});
813813

814+
// Backward-compat: SDK versions < 1.4 post to /peer/announce instead of /agents.
815+
// Accepts the same body, registers the same way, but returns the old {peers:[]} shape.
816+
peer.post("/peer/announce", {
817+
schema: {
818+
summary: "Legacy peer announce (SDK < 1.4, maps to POST /agents)",
819+
operationId: "postPeerAnnounce",
820+
tags: ["gateway"],
821+
body: { $ref: "AnnounceRequest#" },
822+
response: {
823+
200: {
824+
type: "object",
825+
properties: { peers: { type: "array", items: { $ref: "AgentRecord#" } } },
826+
},
827+
400: { $ref: "Error#" },
828+
403: { $ref: "Error#" },
829+
},
830+
},
831+
}, async (req, reply) => {
832+
const ann = req.body;
833+
if (!ann?.publicKey || !ann?.from) return reply.code(400).send({ error: "Invalid announce" });
834+
835+
const awSig = req.headers["x-agentworld-signature"];
836+
if (awSig) {
837+
const authority = req.headers["host"] ?? "localhost";
838+
const result = verifyHttpRequestHeaders(req.headers, req.method, req.url, authority, req.rawBody, ann.publicKey);
839+
if (!result.ok) return reply.code(403).send({ error: result.error });
840+
} else {
841+
const { signature, ...signable } = ann;
842+
const domainOk = verifyWithDomainSeparator(DOMAIN_SEPARATORS.ANNOUNCE, ann.publicKey, signable, signature);
843+
if (!domainOk && !verifySignature(ann.publicKey, signable, signature)) {
844+
return reply.code(403).send({ error: "Invalid signature" });
845+
}
846+
}
847+
848+
if (agentIdFromPublicKey(ann.publicKey) !== ann.from) {
849+
return reply.code(400).send({ error: "agentId mismatch" });
850+
}
851+
852+
const worldCap = Array.isArray(ann.capabilities)
853+
? ann.capabilities.find((cap) => typeof cap === "string" && cap.startsWith("world:"))
854+
: undefined;
855+
if (worldCap) {
856+
const protocolWorldId = agentIdFromPublicKey(ann.publicKey);
857+
upsertWorld(protocolWorldId, ann.publicKey, {
858+
slug: typeof ann.slug === "string" && ann.slug.length > 0
859+
? ann.slug
860+
: worldCap.slice("world:".length) || ann.alias || protocolWorldId,
861+
endpoints: ann.endpoints,
862+
lastSeen: ann.timestamp,
863+
persist: true,
864+
});
865+
} else {
866+
upsertAgent(ann.from, ann.publicKey, {
867+
alias: ann.alias, endpoints: ann.endpoints, capabilities: ann.capabilities, persist: true,
868+
});
869+
}
870+
// Return legacy shape: {peers:[...]} instead of {ok, agents:[...]}
871+
return { peers: getAgentsForExchange(20) };
872+
});
873+
814874
peer.post("/agents/:agentId/heartbeat", {
815875
schema: {
816876
summary: "Lightweight liveness heartbeat",

0 commit comments

Comments
 (0)