Skip to content

feat(contract): add the Hub wire frames and event envelope - #479

Open
oxwen11 wants to merge 2 commits into
mainfrom
feat/hub-contract
Open

oxwen11 wants to merge 2 commits into
mainfrom
feat/hub-contract

Conversation

@oxwen11

@oxwen11 oxwen11 commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Requirement

Slice 1 of Hub Phase 1 (RFC, #466, section 5 and 10): the shared wire vocabulary a daemon and a Hub speak, as Effect Schemas in @getpie/contract. Decisions 1 to 3 of the RFC are settled.

Expected behavior

@getpie/contract/hub exports:

  • HubEventSchema: the vendor's event name and action as type, the vendor's JSON as payload (unchanged; its size is bounded by Hub's event size limit at the HTTP and WebSocket layer, not here), an optional webhookId, Hub-set receivedAt and expiresAt.
  • Daemon to Hub frames: hub.hello (protocol version, environmentId UUID, optional features), hub.event.ack (accepted | duplicate, or rejected with a stable code and nothing else), hub.pong.
  • Hub to daemon frames: hub.welcome (the acknowledged handshake that activates an enrollment), hub.event.deliver (event plus deliveryAttempt), hub.ping.
  • HUB_PROTOCOL_VERSION and HUB_CLOSE_REVOKED (4403).

Nothing else changes: no runtime, no storage, no oRPC procedure, no other export. hub.welcome is not in the RFC's frame list yet; it is added to #466 in the same change set because section 3 needs an acknowledged handshake.

Changes and risks

One new module, one exports entry and one test file. No callers yet, so no compatibility or data risk. No trust boundary is crossed in this slice; the schemas are what the later Hub and connector slices will validate frames with at the receiving boundary (UUIDs, field lengths, discriminants, direction-specific unions).

Verification

Tested revision 2b00d78a on origin/main a617dcea.

  • pnpm --filter @getpie/contract test: 74 passed, no type errors (includes the new hub.test.ts: accepts each valid frame; rejects an unknown version, a bad source, an empty or oversized event id, a rejection without a code or with an unstable one, a non-UUID Environment, a zero delivery attempt, and frames sent in the wrong direction).
  • pnpm exec turbo run typecheck --filter=@getpie/contract: passed. oxfmt --check packages/contract: clean.
  • Not verified: pnpm lint:check reports 121 errors in unrelated files on this checkout (for example packages/server/src/daemon/launcher.ts) and none under packages/contract; I did not investigate whether they reproduce on main or are local. Full pnpm test and CI were not run locally.

@pkg-pr-new

pkg-pr-new Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
npx https://pkg.pr.new/oxwen11/pie/@getpie/cli@479

commit: bd9bb78

@oxwen11

oxwen11 commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

Correction to the lint note: the 121 pnpm lint:check errors are all in tools/oxlint itself and reproduce on a tree with no contract change; none are in packages/contract. Running oxlint directly needs turbo run build --filter=@getpie/oxlint first. oxlint packages/contract was not re-run after building the plugin; I will run it and record the result.

@oxwen11

oxwen11 commented Oct 7, 2026

Copy link
Copy Markdown
Owner Author

Review record for head 2b00d78a8847 (base main a617dcea, rules 2c10c91b): not merged. The design this PR implements has not landed on main yet.

CI (gate 1): passed on this head. Required checks: Check, react-doctor, and both Publish @getpie/cli preview runs. State was MERGEABLE / CLEAN.

Blocker (context, not a code defect)

  • @getpie/contract/hub implements the wire contract from RFC revision 7 in docs: redesign Hub as a single-deployment event broker #466, which is still open. That revision has a single-deployment broker, hub.welcome, hub.event.deliver/ack, and decisions 1 to 3 marked "settled".
  • On main, docs/rfc/pie-hub.md is a different and incompatible proposal:
    • :223 puts relationship ids and eligible schedules in hub.hello, and the frames are hub.execution.*.
    • :554 decision 1 keeps one Environment per Hub.
    • :486 says "Implementation starts only after section 10 and the persistence worksheet are confirmed". Section 10 on main is still a list of open questions.
  • The rules ask for design decisions on the trusted base, and PR content cannot grant that exemption. So this slice's required behavior cannot be checked against an agreed design on main.
  • docs: redesign Hub as a single-deployment event broker #466 is unmerged and CONFLICTING. Its latest review record (c5e03980) still lists blocking items. One of them, single-token enrollment (the enrollment token becomes the long-lived credential), directly shapes the hub.hello / hub.welcome handshake defined here.
  • The PR description also says hub.welcome "is added to docs: redesign Hub as a single-deployment event broker #466 in the same change set".

What would warrant another review: #466, or at least its §0/§3/§5/§11 decisions, lands on main with those decisions confirmed. Alternatively, this PR is restacked on #466, so the RFC merges first. Any frame change that follows from resolving #466's enrollment-credential item also needs a new head. Review restarts at CI on the new head or base.

Code review: no blocking defect found on this head. The change is additive and isolated: one module, one exports entry and one test file. It has no callers and nothing runs at runtime, and @getpie/contract is private. It respects the leaf boundary (imports only effect). Non-blocking notes:

  1. packages/contract/src/hub.ts:22-23: receivedAt/expiresAt are documented as "timezone-aware ISO-8601", but the only check is max length 40. "", "not a date" and a timestamp without a timezone all decode. Nearby schedule.ts uses NonEmptyString plus a comment. A later receiver that compares expiresAt should parse it and fail closed. Alternatively, add a check here.
  2. packages/contract/test/hub.test.ts:64: the title says it "refuses [a code] elsewhere", but no assertion covers that. In practice an accepted ack that carries code decodes successfully, and the code is stripped (excess keys are ignored). A rejected ack with an extra message decodes the same way, and the message is dropped. So the decoded value still has "nothing else", but the title overstates what is tested.

Independent verification (gate 3), in a clean reviewer worktree pinned to 2b00d78a

  • Ran pnpm install --frozen-lockfile and pnpm run build (Turbo, 8/8 tasks, none cached).
  • pnpm --filter @getpie/contract test: 9 files, 74 tests passed, no type errors. hub.test.ts ran 8 of those tests, all passing.
  • turbo run typecheck --filter=@getpie/contract --force passed. Repo-wide turbo run typecheck --force passed (19/19).
  • oxfmt --check packages/contract: clean.
  • pnpm lint:check (builds the oxlint plugin, then lints the whole repo): 0 warnings, 0 errors on 935 files. The 121 errors in the description and in the follow-up comment do not reproduce on a clean checkout of this head, so they appear to be local to the author's tree.
  • A scratch decode→encode→JSON→decode probe (not committed) round-tripped every frame unchanged:
    • daemon frames: hello, ack accepted/duplicate/rejected, pong;
    • Hub frames: welcome, deliver (with and without webhookId), ping.
  • The same probe confirmed rejection of:
    • frames sent in the wrong direction (welcome/deliver as daemon frames, hello/ack as Hub frames) and an unknown type;
    • a non-integer or zero protocol or delivery attempt;
    • an uppercase feature and a 33rd feature (32 is accepted);
    • an empty or 65-character webhookId, an empty type, a 65-character source (64 is accepted), and a 201-character eventId (200 is accepted);
    • an empty ack eventId, a 65-character rejection code, and a missing payload.
  • Exported constants: HUB_PROTOCOL_VERSION = 1, HUB_CLOSE_REVOKED = 4403.

Gaps: none in verification. No runtime surface is affected, so runtime, UI and real-model proof does not apply. Merge is held only by the design blocker above.

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.

2 participants