Skip to content

Move the Runtime gateway into Core and check the wire contract against shared scenarios - #295

Merged
SaladDay merged 2 commits into
mainfrom
refactor/core-gateway-ownership
Sep 30, 2026
Merged

SaladDay merged 2 commits into
mainfrom
refactor/core-gateway-ownership

Conversation

@SaladDay

@SaladDay SaladDay commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

internal/ holds code that both Core and the daemon use. Of internal/agentdaemon, only proto, the Core–Runtime wire protocol, is shared. gateway and device are Core's, but they stayed there because the daemon's wire contract test ran Core's production gateway, and Go's internal-package rule stops the daemon importing services/core/internal.

This moves them into Core and replaces that cross-import with the method AGENTS.md prescribes: define each exchange once, and check each side against the shared definition.

Move (commit 1, mechanical):

  • internal/agentdaemon/gateway → services/core/internal/runtimegateway
  • internal/agentdaemon/device → services/core/internal/runtimedevice
  • proto stays where it is.
  • The swag annotations on the moved WebSocket handler are removed. Inside services/core they would have put /agent-daemon/* into the public /v1 OpenAPI. make openapi shows no diff.

Wire contract (commit 2):

  • internal/agentdaemon/proto/prototest/wire.go lists each scenario once, as data: the ordered frames each side sends, built from the real proto types, plus native settlement, connection loss and reconnection. The scenarios are version rejection, cancellation after settlement, preparation failure, and disconnect without replay.
  • Core side (services/core/internal/runtimegateway/wire_test.go): the real gateway runs against a scripted Runtime.
  • Runtime side (apps/daemon/internal/wireconformance/wire_test.go): the real transport and dispatcher run against a scripted Core, using the same controlled adapter as before.
  • Every assertion from the old contracttest/wire_test.go now sits on the side that owns the behavior. Both sides compare the same frames as JSON, so encoding compatibility is still covered.
  • Makefile (check-runtime-contract), docs/runtime-protocol.md, services/core/IMPLEMENTATION.md and scripts/build-core.sh are updated to match.

Checks run (focused):

  • go build ./...
  • go vet on the moved packages and all importers, plus darwin and windows vet for the daemon packages
  • go test on the moved packages, runtime, runtimeenrollment, execution, api and cmd/... without a database, and the new wire tests on both sides, including 20 runs with -race
  • make check-runtime-contract, make openapi (no diff), make build-core, check-names

go vet ./services/core/... still reports two copylocks warnings in store test files. They are the same on main.

The implementer also injected eight regressions to confirm the tests fail. Each was caught by the side that owns it:

  • Core side: the gateway invents done on disconnect, acks a cancellation before settlement, or keeps the Run route after close; Core accepts any version.
  • Runtime side: the dispatcher sends the receipt before native settlement or skips Executor close on setup failure; the transport drops ErrIncompatibleVersion; started changes the Executor ID.

Review: no blind review. Commit 1 is a mechanical move. Commit 2 is test-only, and the coordinator read it and relied on the injected-regression evidence above.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Only Core uses internal/agentdaemon/gateway and internal/agentdaemon/device in
production, so they now live at services/core/internal/runtimegateway and
services/core/internal/runtimedevice. internal/agentdaemon/proto stays shared.

The daemon's WebSocket contract test imported Core's gateway directly and cannot
cross Go's internal-package boundary any more; it is removed here and replaced by
shared-scenario conformance tests in the next commit.

The gateway's swag annotations are removed: the moved handler is now inside the
OpenAPI scan, and these routes are documented as having no generated schema.
internal/agentdaemon/proto/prototest/wire.go defines each exchange once: the
ordered frames Core and the Runtime send, built from the proto types, plus the
native settlement, connection loss, reconnection and silence between them, and
the incompatible-version handshake.

Each side replays the other side's frames from a scripted peer over a real
WebSocket and asserts what it owns:

- services/core/internal/runtimegateway/wire_test.go runs Core's gateway: the
  426 rejection, delivery to preparation, Run and receipt waiters, no receipt
  before the Runtime's acknowledgement, no invented terminal events on
  disconnect and no inherited routes or replay after reconnect.
- apps/daemon/internal/wireconformance runs the production transport and
  dispatcher with the controlled adapter: permanent stop on 426, the exact
  frames it sends, no input during preparation, cleanup on failure, no receipt
  before native settlement, and settled cleanup after connection loss.

Runtime-generated values (Executor ID, handle, expiry) are placeholders that the
Runtime side binds to the values its Runtime sends.
@SaladDay
SaladDay merged commit 17bbffa into main Sep 30, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant