feat(client): the profile slot carries its own loopback port - #954
Closed
chughtapan wants to merge 2 commits into
Closed
feat(client): the profile slot carries its own loopback port#954chughtapan wants to merge 2 commits into
chughtapan wants to merge 2 commits into
Conversation
A profile record was exactly {agentId, apiKey, agentName}, so nothing
could tell a daemon which port to bind and moltzapd required a
caller-supplied --port. No production code could compute an MCP URL.
The record becomes a slot: agentName and a required mcpPort, plus the
identity pair agentId and apiKey, which are written together at Registry
commit or absent together before it. moltzapd resolves its own port from
the slot it was named with, so --port is gone from the published binary.
loadServiceConfig now distinguishes an absent slot from a slot with no
committed identity, which the registration surface will branch on.
Strict decode has no compatible intermediate: a record either carries
mcpPort or fails to decode. Every writer moves in this commit, and the
simulator and packaged-daemon fixtures choose a port before writing the
slot rather than handing one to the daemon.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P76aaa1STr3WPZ3nDascta
This was referenced Aug 5, 2026
Owner
Author
|
Superseded by #974, which collapses this stack into one PR against |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plan step 8 — the head of the critical path. Stacked on #953. Atomic by construction, not by preference.
The blocker this removes
profileRecordSchemawas exactly{agentId, apiKey, agentName}. Nothing could tell a daemon which port to bind, somoltzapdrequired a caller-supplied--portand only the test helper ever passed one — by binding port 0, reading the number, and closing. No production code could compute an MCP URL, which is the reasonharnessClientForAccounthas zero production callers and both adapters still constructMoltZapServicedirectly.The shape, landed once
A slot exists before registration and carries no AgentId; Registry commit adds the identity pair. The invariant is a schema filter, and
isRegisteredProfilenarrows toRegisteredProfileRecordat the three places that need credentials.loadServiceConfignow distinguishes no slot (ProfileNotFoundError) from slot with no committed identity (ProfileNotRegisteredError) — the branch the registration surface needs later.moltzapdresolves its own port from the slot it was named with.--portis gone from the published binary, and the packed-tarball check now asserts its absence rather than its presence.Why there is no green intermediate
STRICT_PARSE_OPTIONSusesonExcessProperty: "error", andmcpPortis required, so the old and new shapes are mutually undecodable. There is no ordering in which both work — a split is impossible by construction. Every writer moves here:register.ts(gains an operator-supplied--mcp-port),config.test-utils.ts, simulatorworkspace.ts, and the openclaw container fixture.Pre-launch, so the
~/.moltzap/config.jsoninvalidation is accepted with no shim and no migration.Port selection stays out of the daemon
The precedent rejects port-zero allocation and bind fallback, so the daemon never selects a port. Where a test or simulator needs one, the harness acts as the operator: it reserves a free port and writes it into the slot before the child starts.
reservePortis gone from the daemon spawn path;reserveTestMcpPortand the simulator'sreserveSlotMcpPortsit on the writer side, which is a different thing.The EADDRINUSE test was restructured accordingly — the blocker now binds the slot's port, since that is the only port the daemon will try.
Gates
git grep '"--port"' packages/client/→ nothingmcpPortfails decode rather than defaulting — new testslotWithoutMcpPortFailsDecode, plushalfCommittedSlotFailsDecode(agentId without apiKey) anduncommittedSlotDecodespnpm typecheck+pnpm nx run-many -t typecheck:tests— 0 errorspnpm lint— 0 warnings, 0 errors (knip, arch:check, arch:config:check)pnpm nx run-many -t test— all 9 projects greendocs/cli/reference.mdxnow advertises--mcp-portDeliberately not here
createProfileSlot,commitProfileIdentity, andresolveHarnessEndpointwere drafted and removed: knip flagged them as unused exports, and they have no consumer until steps 9 and 12. Landing them now would be the same dead-code-ahead-of-its-consumer problem this stack already has in #945. They come with their callers.ADR conformance (R-pass)
mcpPort… port-zero allocation and bind fallback are rejected"20260728-endpoint-daemon-speaks-modern-mcpCONFORMSmcpPortandgrep mcpPort packages/returned zero before this PR. Implemented to the same shape and the same rejections regardless.docs/spec/harness/daemon.mdCONFORMSNo new divergence introduced. This PR does not by itself admit the production contract — the main-owned ADR bundle (plan step 5) is still outstanding, and this implements the shape the maintainer selected.
🤖 Generated with Claude Code
https://claude.ai/code/session_01P76aaa1STr3WPZ3nDascta