Reviews the cross-cutting SignalR seam tying dotnet-realtime (hub) to angular-realtime (client), as auth-token-contract and api-design-standards govern theirs. Flags hub method/event names duplicated as magic strings instead of a shared contract, push payloads with no matching typed Angular interface, the hub token diverging from REST auth, no hub-contract versioning tied to client regeneration, and reconnect gaps where missed messages are neither replayed nor re-fetched. Outputs pilot-core s...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add AgenticPawan/FullStack-Pilot --skill realtime-contract --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Realtime Contract?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/agenticpawan-realtime-contract)More formats (shields.io, HTML) on the badges page.
---
name: realtime-contract
description: Reviews the cross-cutting SignalR seam tying dotnet-realtime (hub) to angular-realtime (client), as auth-token-contract and api-design-standards govern theirs. Flags hub method/event names duplicated as magic strings instead of a shared contract, push payloads with no matching typed Angular interface, the hub token diverging from REST auth, no hub-contract versioning tied to client regeneration, and reconnect gaps where missed messages are neither replayed nor re-fetched. Outputs pilot-core standard IDs (RTC-*).
when_to_use: SignalR contract, hub method name drift, client event name mismatch, real-time payload contract, hub DTO typed interface, accessTokenFactory hub auth, SignalR reconnect missed messages, hub versioning, server push shape, on() event string, real-time seam SPA API, HubConnection typed
---
## Purpose
`dotnet-realtime` reviews the C# hub; `angular-realtime` reviews the Angular `HubConnection`.
Neither can see the **seam**: the method names, event names, and payload shapes the two sides
must agree on, plus the auth token and reconnection semantics that span both. This is the
real-time analogue of `auth-token-contract` (auth seam) and `api-design-standards` (REST seam).
Drift here fails silently — a renamed hub method or a reshaped payload compiles cleanly on both
sides and only breaks at runtime for connected users.
## Standard IDs
| ID | Severity | What it checks |
|----|----------|----------------|
| RTC-001 | P1 | Hub method / client-event names are magic strings duplicated on both sides with no shared source of truth |
| RTC-002 | P1 | Server push payload (C# type) has no matching typed Angular interface — received as `any` |
| RTC-003 | P2 | Hub access token not propagated consistently with `auth-token-contract` — hub auth diverges from REST auth |
| RTC-004 | P2 | No hub-contract versioning / regeneration trigger — client and hub can drift across a deploy |
| RTC-005 | P2 | Reconnect gap not reconciled — messages missed during a drop are neither replayed nor re-fetched |
---
## Check A — Magic-string method/event names (RTC-001)
### Detection
Compare the hub's method names (`public Task SubscribeToOrder(...)`) and its
`Clients.*.SendAsync("OrderUpdated", ...)` event names against the Angular
`connection.invoke('...')` and `connection.on('...')` strings. Each name appears independently on
both sides with no shared constant — a rename on one side is a silent break.
### BAD — the string `'OrderUpdated'` is authored twice, unlinked
```csharp
await Clients.Group(id).SendAsync("OrderUpdated", dto); // C#
```
```typescript
connection.on('OrderUpdated', (dto) => this.apply(dto)); // TS — nothing links these
```
### GOOD — a single generated/shared contract both sides consume
Generate the event/method names (and payload types, see RTC-002) from one source — e.g. emit a
`hub-contract.ts` from the C# hub definitions, or share a small hand-maintained
`RealtimeEvents` const on each side reviewed as a pair — and reference the constant, never a
literal:
```typescript
import { HubEvents } from './hub-contract'; // generated from the C# hub
connection.on(HubEvents.OrderUpdated, (dto: OrderUpdatedEvent) => this.apply(dto));
```
---
## Check B — Untyped payloads (RTC-002)
The server sends a C# DTO; the Angular handler must receive a matching interface, not `any`.
Reuse the `angular-api-client-codegen` discipline: the real-time payload types belong in the
same generated/typed contract as the REST DTOs, so a field rename on the server surfaces as a
TypeScript compile error on the client.
```typescript
// BAD — payload is 'any'; server renaming dto.total → dto.amount is invisible here
connection.on(HubEvents.OrderUpdated, (dto) => this.total.set(dto.total));
// GOOD — typed; the rename breaks the build
connection.on(HubEvents.OrderUpdated, (dto: OrderUpdatedEvent) => this.total.set(dto.amount));
```
---
## Check C — Hub auth token divergence (RTC-003)
A SignalR WebSocket cannot send an `Authorization` header — the token goes through
`accessTokenFactory`. That token MUST be the same audience/issuer/scope the REST API validates
(`auth-token-contract` AUTH-*). Flag a hub connection with no `accessTokenFactory`, or one
sourcing a different token than the HTTP interceptor, so hub authorization and REST authorization
can silently diverge.
```typescript
// GOOD — hub uses the same access token the REST interceptor uses
new HubConnectionBuilder()
.withUrl('/hubs/orders', { accessTokenFactory: () => this.auth.getAccessToken() })
.build();
```
Server: the hub's `[Authorize]` and permission checks resolve claims from that token exactly as
the REST pipeline does — no separate hub token scheme. See `dotnet-realtime` RT-001 for the
permissions-only hub authorization itself.
---
## Check D — Contract versioning (RTC-004)
There is no HTTP status/`Accept` header to version a hub. Flag the absence of a deliberate
strategy: a `hub-contract` regenerated in CI when the C# hub changes (build fails on drift), or
an explicit hub-version negotiated on connect so an old client is rejected/upgraded rather than
receiving a payload shape it can't parse. Ties to `zero-downtime-deployment`: during a rolling
deploy, N-1 clients connect to N hubs — the payload must stay backward-compatible or be versioned.
---
## Check E — Reconnect reconciliation (RTC-005)
`angular-realtime`/`dotnet-realtime` cover *automatic reconnect*; the seam concern is what happens
to messages sent **during** the drop. After `onreconnected`, the client must reconcile — re-fetch
current state or replay from a last-seen cursor — not silently assume it missed nothing. Flag a
reconnect handler that restores the connection but never re-syncs state.
```typescript
connection.onreconnected(() => this.refetchCurrentState()); // reconcile the gap, don't assume continuity
```
---
## Read budget
≤ 8 files: the C# hub(s) and their `SendAsync` call sites, the Angular service building the
`HubConnection` with its `on`/`invoke` calls, and the shared/generated contract if one exists.
Reference `dotnet-realtime` (hub mechanics), `angular-realtime` (client mechanics), and
`auth-token-contract` (token audience/scope) rather than re-deriving them. Budgets bound
exploration, not quality — if the payload types live in a generated client, read it and say why.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!