Org-level webhooks: fan-out across all org repos #363
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#363
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Survey: crueber/walhub#349 candidate 11.
Evidence
Design
Acceptance criteria
Fix PR: #372
Review of PR #372 (fix/issue-363, commit
d125981) — org-level webhooks fan-out.Scope checked: notify/orghooks.go (new, 1046 lines), notify/http.go handleOrg + ExposedTemplates, notify/notify.go (OrgOwner seam + wakeOrgCh + org watermarks), notify/tasks.go (minute org sweep + retention floor), notify/webhooks.go (dedup to shared helpers), identity/identity.go (OrgEvent seam) + orgs.go/invites.go/http_invites.go (emissions + DeleteOrgInvite), cmd/walhub/collab.go wiring, web/sdk/src/orgs.js + sdk-notifications.test.js, docs/features/06 §§1.5/5.4/6/7/9 + 14 amendment. No browser exercised (API/service change; tests + reasoning suffice — stated explicitly per brief).
THE DEVIATION — push + repo-create excluded (06 §5.4 + PR 'Out of scope'). RULING: JUSTIFIED, with one required follow-up. Pushes never enter the collab-events logs the org pass reads; pulling them in would mean a new WAL/git-hot-path source (law 4/6 cost, Seam 4 bridge is the git-push surface), i.e. a scope expansion, not a fan-out. Repo-create has no single author/timestamp seam (create-twin vs PUT lane), so 'first activity is the signal' is honest. The doc is explicit (member-repo half = collab activity only, NOT pushes; no synthetic create record; decisions entry 2026-09-12). REQUIRED: the issue criterion needs an amendment comment (Design line lists push + repo create; acceptance bullet 1 'events across member repos' reads as including push). Treat this comment as that amendment proposal: #363 delivers membership/team/invite + member-repo collab-activity events with HMAC; git-push delivery stays on the events-bridge TOML sinks and repo-create surfaces via first activity. If the author agrees, keep this note as the amended criterion — no code block on this point.
Observer wiring — PASS. Post-commit only, nil-safe via emitOrgEvent (identity/identity.go). Covered: Set/RemoveMember, Create/DeleteTeam, Set/RemoveTeamMember, CreateOrgInvite, AcceptInvite (org kind: member_added/role_changed via SetMember + own invite_accepted), CancelInvite (org-kind only; repo cancels stay silent — correct), new DeleteOrgInvite owner-cancel (invites.go) wired into http_invites.go owner-cancel path (same 404/400 mapping as the inlined code it replaced + inbox drop + emit). CAS-retry flags (added/roleChanged/removed) re-derived per attempt with exactly-one-emission comment — correct. No other membership-mutation path found (org create/delete/rename are not member/team/invite transitions and correctly emit nothing).
Delivery — PASS. postEvent reused verbatim (keepers/HMAC/10s lane — same wire bytes; org actions carry Kind 'org', Repo=org). Per-scope cursors (org-log cursor + per-(hook,repo) cursors) CAS-advance monotonically per scope; lost CAS redelivers, never skips (at-least-once preserved). No cross-hook contamination (keys are per-hook; finishOrgPass watermarks are per-org/per-(org,repo) gate hints only, delivery cursors stay in bucket). Retention floor folds orgRepoMinCursor in (tasks.go) — sound; fresh hook (cursor 0) holds floor only until first pass. Minute sweep bounded: hookless org = one hooks LIST, early return; active org = LIST + org-state GET + member collab_state probes only when due; hookMu held for maps only, never across I/O. Dedicated wakeOrgCh (notify.go) — no §7 bulk/control sharing (no shared client/semaphore with repo lane; FanoutParallel=8 semaphore is per-pass, pre-acquired per #153).
Gates — PASS. Owner-only CRUD via requireOrgOwner → CheckOrgOwner (server-authoritative identity seam; nil checker fails closed; anon 401 / non-owner 403 / unknown id 404). Both lanes served (api + api-browser twins) + ExposedTemplates/discovery entries. Caps mirror repo limits exactly: same maxHooks() (default 20, 409 past it), 256/scope/pass, FanoutParallel 8, last-25 deliveries ring. Secrets write-only (secret_set).
No-UI — ACCEPTABLE. Issue never demands a UI tab; API+SDK is the v1 contract and 06 out-of-scope explicitly defers the org-settings hooks tab. SDK surface complete (list/create/get/update/remove/ping/deliveries) + unit tests.
Tests/docs — PASS. notify 95.6% / identity 96.4% (≥95 gate), -race clean, gofmt clean, vet clean, go build clean, cmd/walhub suite green, node SDK surface tests 3/3. Table coverage: CRUD/gating/validation/caps/fan-out+HMAC/filter/failure-pending/sweep-gate/retention-floor + e2e collab chain (author-attested; cmd/walhub suite green here). 06 §§1.5/5.4/6/7/9 accurate; 14 amendment follows frozen-overwritable rule 2 (same-revision adoption, key families enumerated, orgevents Create-only correctly excluded from overwritable set).
Deps/behavior — PASS. No go.mod/npm changes. Repo-hook behavior: webhooks.go helpers deduplicated to shared readCursorAt/advanceCursorAt/recordDeliveryAt/readDeliveriesAt — byte-identical logic, existing notify suite green proves no behavior change.
Verified in scratch worktree /tmp/pr372 (removed afterward); main worktree left clean/read-only (fetch only). No live instance/docker touched.
RECOMMENDATION: ready to merge once the criterion-amendment note above is acknowledged (this comment serves as the amendment; no code change required).
Fixed by PR #372 (review clean — deviation ruled justified with amendment note; observer, delivery, gates, caps verified), merged. Closing.