~bigbes/sr-ht-spec

ref: 7cc652d57e9b4fb8962318f36fdab737bb0b1be8 sr-ht-spec/.up/phase5a-webhooks.md -rw-r--r-- 5.3 KiB
7cc652d5 — Eugene Blikh fix(mcpsrv): gate read tools to owner+agents (spec-jjo) 25 days ago

#Phase 5a — GraphQL-native webhooks (proposal lifecycle)

Mode: hands-off Branch: feat/phase5a-webhooks Beads: spec-3m9 (epic), spec-8y4 (conventions), spec-45j (webhook feature)

#Design

Emit webhooks on proposal open/merge/reject, GraphQL-native, matching the core-go webhook engine used by pages.sr.ht / lists.sr.ht. spec.sr.ht keeps its own authn (owner/agent/anonymous + provenance) as the single auth policy — core-go's auth model has no agent concept — and a thin adapter derives the core-go auth.AuthContext the webhook engine consumes. Webhooks are owner-scoped (gql_user_wh_sub, one user = the owner); events are PROPOSAL_OPENED / PROPOSAL_MERGED / PROPOSAL_REJECTED; the agent identity that triggered an event rides in the payload (the proposal), not the auth context.

Reference: /Users/blikh/data/home/tmp/pages.sr.ht (same fork).

#Plan (phases)

  1. DB foundationuser table + owner seed; webhook_event / auth_method enums; gql_user_wh_sub + gql_user_wh_delivery tables. schema.sql + migrations/0003.
  2. AuthContext bridge — an in-spec adapter mapping authn.Principal → core-go auth.AuthContext (owner→AUTH_COOKIE+UserID; agent→owner's UserID), factored to lift into a future sr-ht-ext module.
  3. Server + context wiring — install core-go database + webhooks context on /query and the delivery worker; keep spec's surfaces/auth intact (no core-go 401-by-default).
  4. Webhook SDL + models + resolvers — WebhookEvent/Subscription/Delivery/ Payload types, create/delete mutations, webhooks/webhook queries, the webhook payload root field; gqlgen regen.
  5. Firing — service event hook → webhooks.Schedule on open/merge/reject.

#Conclusion

#Hands-off decisions

  • make: Mode hands-off (user invoked /up:handsoff).
  • make: dedicated branch feat/phase5a-webhooks; worktree skipped — session is configured for in-place work; isolation via the branch, master untouched.
  • udesign: keep spec's authn as the single auth policy; derive a core-go auth.AuthContext for the webhook engine only (core-go auth cannot model agents/provenance without breaking Phases 3-4).
  • uplan: adopt core-go's webhook ENGINE (queue/delivery/Ed25519 signing/ GraphQL-native Exec) + the pages.sr.ht schema/SDL shape; do NOT rewrite the existing read resolvers onto database.Model — only the webhook models need it.
  • uplan: webhooks are owner-scoped (gql_user_wh_sub), matching pages.sr.ht's user-scoped pattern; the single owner is the only user.
  • uexecute: FAITHFUL wiring chosen by user — /query moves onto core-go's authenticated router (WithDefaultMiddleware → core-go auth + server/database contexts) so the core-go webhook engine works unchanged. Accepted trade-off: AGENTS lose GraphQL /query reads (they keep MCP + REST; owner keeps /query; web/MCP/REST stay on the anon router with spec's authn). A cookie→INTERNAL override on /query makes NewAuthConfig/FilterWebhooks accept the owner (they refuse AUTH_COOKIE). Owner "user" row seeded at startup so core-go's LookupUser never calls out to meta.sr.ht.

#New deploy requirements (faithful path)

  • [mail] smtp-from is now REQUIRED — WithDefaultMiddleware builds core-go's notification/email queue, which panics without it. A valid address; SMTP need not actually work for webhooks.
  • Redis: WithDefaultMiddleware creates a client (default redis://), but it is lazy and outbound webhook delivery never touches it — no redis server needed.
  • Agents can no longer read GraphQL /query (core-go auth is OAuth2/cookie only); they use MCP + REST. Owner keeps /query (cookie).

#Deferred (needs user input)

  • extensions-go / sr-ht-ext module — extracting spec's agent model into a shared module (location, name, publish target, CI wiring). User said it's "too early to judge"; building the bridge in-spec, factored for later extraction. Revisit when a second consumer exists or after this ships.
  • gqlgen regeneration runs go generate ./graph (pulls gqlgen at generate-time and rewrites graph/api/generated.go). DONE in phase 4; reproducible.
  • Phase 3 runtime wiring — STRUCTURAL BLOCKER, needs user decision. The core-go webhook engine's delivery (WebhookContext.Exec) requires server.ForContext(ctx) for Schema+MaxComplexity. serverCtxKey is UNEXPORTED with no public installer — the server context is installed ONLY by WithDefaultMiddleware (request path) and WithQueues (worker path, which needs WithDefaultMiddleware's db). WithDefaultMiddleware also installs core-go auth.Middleware on /query, which 401s spec's AGENT bearer tokens (core-go's bearer path is OAuth2-only). Net: faithful engine ⟹ /query behind core-go auth ⟹ agents lose GraphQL /query reads (they keep MCP/REST; owner keeps /query; web/MCP/REST unaffected on the anon router). Redis is a non-issue (lazy client, unused by outbound delivery). Two paths: (a) Adopt WithDefaultMiddleware for /query (faithful; accept the agent /query-read regression). (b) Spec-local firing: reuse dowork + crypto.SignWebhook + the gqlgen executor, skip core-go's server-context-coupled Exec (keeps agent /query, no core-go auth on /query; diverges from the "standard" engine). Phases 1-4 (DB, bridge, schema/models/resolvers) are committed and green regardless of this choice.