docs(#265): messaging authorization contract catalogue + realtime C29#328
Merged
Merged
Conversation
"C1–C29" was an aspirational banner, not a populated list. An Explore pass found only 13 clauses ever had rule text (C1,C2,C3,C5,C7–C14,C29); C4,C6,C15–C28 were never authored, and the "#266 plan" the code kept citing never existed. Inventing 16 clauses to hit a round number would be the manufactured-green anti-pattern the project forbids — so this writes down the REAL contract and makes the code's own claims true. - Add docs/messaging/AUTHORIZATION-CONTRACT.md — the canonical catalogue the code referenced but that was never written: the 13 real clauses (rule text + enforced-at RLS/provider/.NET refs + asserted-in-suite), the C29 two-mechanism note (RLS-filtered postgres_changes vs. payload-free poll), and the real deferred seam-expansion backlog (C3 createConversation, group mgmt, keys, GDPR) — the honest "broaden coverage" work for #280. - Add a C29 realtime conformance case: subscribe as the outsider → their authorized refetch still yields [] → any delivered event carries no readable row. Deterministic and non-flaky by design — no live-websocket wait (the conformance stack deliberately omits the realtime container). 20 cases/provider. - Doc-truth: correct the 7 false "C1–C29 / #266 plan" claims (types.ts, contract.ts, dotnet.test.ts, dotnet-provider.ts, README ×3) to point at the new catalogue; refresh the stale dotnet-provider "typed skeleton (server is a follow-up)" status and the README "12/12 / migration currently fails" gaps to reflect the now-live, CI-gated backend. Verified locally: type-check, lint, and dual-provider conformance all green — test:rls 129/129, both [supabase] and [dotnet] runners at 20/20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nnected
The new C29 conformance case exposed a latent realtime-provider bug. When the
realtime socket never connected — offline, or a stack without the realtime
service (e.g. the conformance stack) — SupabaseRealtimeProvider.unsubscribe
rejected asynchronously from realtime-js removeChannel → disconnect
("this.conn.close is not a function"). Unhandled, that hard-fails the whole
vitest run: the CI Conformance gate went red even though all 129 tests passed
(it slipped past locally only because the async reject fired after vitest's
summary — a timing fluke).
Harden unsubscribe: attach a `.catch` SYNCHRONOUSLY (so the promise is never
"unhandled" regardless of when it rejects) and swallow the sync-throw path too.
Unsubscribing a never-connected channel is a no-op, not an error — this also
makes production teardown crash-safe when offline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What & why
#265/#280. The messaging provider seam is guarded by a shared conformance suite that both the Supabase and .NET providers must pass. The roadmap item was "broaden coverage to groups/archive/realtime/C15–C28." Investigation found that "C1–C29" was an aspirational banner, not a populated 29-item list:createConversationin the seam) and C29 (realtime — testable, never exercised).Inventing 16 clauses to hit a round number would be the manufactured-green anti-pattern this project forbids. So this PR writes down the real contract and makes the code's own claims true.
Changes
docs/messaging/AUTHORIZATION-CONTRACT.md— the canonical catalogue the code referenced but that was never written: the 13 real clauses (rule text + enforced-at RLS/provider/.NET refs + asserted-in-suite), the C29 two-mechanism note, and the real deferred seam-expansion backlog (C3createConversation, group management, keys, GDPR) — the honest "broaden coverage" work for [EPIC] Enterprise-ready: make ScriptHammer a repo you can build real work on (eval-3 → eval-1) #280.[]→ any delivered event carries no readable row. Deterministic and non-flaky by design: no live-websocket wait (the conformance stack deliberately omits thesupabase-realtimecontainer, and wall-clock realtime waits are a known flake source). 20 cases/provider.types.ts,contract.ts,dotnet.test.ts,dotnet-provider.ts, README ×3) to reference the new catalogue; refreshed the staledotnet-provider"typed skeleton (server is a follow-up)" status and the README "12/12 / migration currently fails" known-gaps to reflect the now-live, CI-gated backend.Verification
Local dual-provider run (in-container against a local Supabase + .NET stack):
pnpm run type-check✅pnpm run lint✅pnpm test:rls→ 129/129 green, both[supabase]and[dotnet]contract runners at 20/20; the suite does not hang despite the realtime container being absent.No behaviour / interface / migration changes — docs + one test only. The Conformance gate re-runs the full suite against both backends.
Out of scope (tracked follow-up)
The real seam-expansion backlog (C3
createConversation, group mgmt, keys, GDPR endpoints behind the provider + .NET server) is named in the new doc — each is its own future increment for #280. This PR does not invent C4/C6/C15–C28.🤖 Generated with Claude Code