Mode: hands-off Branch: feat/phase5a-webhooks Beads: spec-3m9 (epic), spec-8y4 (conventions), spec-45j (webhook feature)
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).
user table + owner seed; webhook_event / auth_method
enums; gql_user_wh_sub + gql_user_wh_delivery tables. schema.sql +
migrations/0003.authn.Principal →
core-go auth.AuthContext (owner→AUTH_COOKIE+UserID; agent→owner's UserID),
factored to lift into a future sr-ht-ext module.webhook payload root field; gqlgen regen.feat/phase5a-webhooks; worktree skipped — session is
configured for in-place work; isolation via the branch, master untouched.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).gql_user_wh_sub), matching pages.sr.ht's
user-scoped pattern; the single owner is the only user.[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://), but it is
lazy and outbound webhook delivery never touches it — no redis server needed.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.go generate ./graph (pulls gqlgen at generate-time
and rewrites graph/api/generated.go). DONE in phase 4; reproducible.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.