Repository events and outbound notifications
Golf treats inbound event intake and outbound notification delivery as separate portable capabilities. Cloudflare is optional. The current increment implements native HMAC push intake with durable delivery records, with a bounded native receipt/replay proof passing. Intake alone does not dispatch. Explicit automation grants and the separately qualified opt-in automatic push slice supply unattended authority; an event configuration UI and outbound HTTP/Slack/email delivery remain planned.
Current inbound push intake
An operator registers an explicit connection through GOLF_REPOSITORY_EVENT_CONNECTIONS, a bounded JSON array. Each entry has these exact fields:
| Field | Binding |
|---|---|
id | A unique canonical nonzero UUID for the connection. |
repositoryId | Exact immutable native repository ID as a decimal string. |
ownerId | Exact immutable native owner ID as a decimal string. |
repository | The bound owner/name. |
secret | A distinct random signing secret using the accepted bounded URL-safe character format; never a sample/default secret. |
The current parser accepts at most 128 connections and 128 KiB of configuration. Secrets use 32–256 URL-safe alphanumeric/underscore/hyphen characters. Keep this configuration in the deployment’s private secret source, never Git, a command argument, an inventory response or a screenshot. It is process configuration, not an encrypted browser-managed connection profile. Connection IDs persist with immutable forge/repository/owner/name bindings and cannot be repurposed for another repository.
Configure a supported native repository push webhook targeting POST /hooks/gitea/{connectionId} with the same signing secret. The server verifies the SHA-256 HMAC over exact raw body bytes before normalizing bounded JSON and matching the configured repository/owner/name. Only the supported push event/header shape is accepted. Body size is at most 1 MiB; malformed/duplicate headers, query strings, credential cookies/authorization, unsupported encodings and mismatched bindings fail visibly. This hook uses its signature boundary rather than borrowing a Golf browser session.
Accepted records bind the connection, delivery UUID, payload hash and normalized push metadata. A duplicate delivery ID with the same body is deduplicated; reuse with a different body fails. Database insertion completes before acceptance is acknowledged. Intake does not start a runner, grant repository access or authenticate the payload’s sender as an executing Golf principal.
Replay and rotation limits
The native signature authenticates the body; the delivery header is unsigned. A captured valid signed body replayed with a fresh valid delivery UUID can therefore create another intake record. There is no cryptographic timestamp/freshness guarantee or complete semantic replay defense in this slice. Automatic execution must additionally deduplicate the exact connection/repository/commit/grant/workflow input and recheck its current authority; header uniqueness alone cannot authorize another run.
Signing secrets are loaded per process. Rotation/removal requires all Golf replicas to be drained and restarted with the intended consistent configuration; no rolling overlap or live UI rotation is qualified. Preserve immutable connection bindings, keep prior delivery records and configure the native hook deliberately. Removing a connection is not proof that an already received external delivery was recalled.
Automatic CI and local agents: next work
The existing manual pipeline uses a current user session. A push event cannot create or extend that cookie, use its signature as a runner credential or borrow an administrator’s token. Unattended jobs require a durable repository-bound automation grant, encrypted versioned credential, separate push policy/trigger support, materialization identity and shared assignment/heartbeat/result revocation fences. The automatic push slice implements this integration behind an explicit default-false operator switch; its final disposable native proof has passed, while the retained local runtime now runs schema021 with an explicitly scoped private demo.
See manual pipelines, the product’s local automation implementation design and plan sequence N1–N10. Agent tasks remain portable: customer machines, Docker or Kubernetes may run a supported native harness without Cloudflare. Claude Code, Codex, OpenCode, Grok and pi each need actual protocol/authentication/execution qualification; an installed binary or saved login is not a working Golf adapter.
Planned outbound webhooks, Slack and email
The next notification slice targets generic HTTP and an actual Slack incoming-webhook or scoped bot adapter, followed by email and separately qualified Teams/Discord destinations. No Slack message or external notification is sent by the current intake increment.
Planned subscriptions have explicit installation/organization/repository/project ownership, event filters, destination/audience approval and current source-access checks before delivery and manual replay. Incoming-webhook URLs and bot tokens are secrets: encrypt them, mask inventory/history and keep path/query/header credentials out of logs. Payload templates use bounded allowlisted metadata; private source, diffs, model context and secret values are excluded by default. A Slack channel is not evidence that its members may read the linked repository.
Planned delivery uses a durable outbox in the owning Golf state transaction where possible, stable event/delivery IDs, at-least-once semantics, receiver deduplication, bounded retries/backoff/jitter, dead-letter inspection and explicit manual redelivery. External provider acceptance is distinct from human receipt. Disable/revoke fences queued work; retry or re-enable cannot silently broaden disclosure or replay an old backlog. Lost response after delivery may produce duplicates, not exactly-once messaging.
Destination validation must resist SSRF and DNS rebinding, recheck resolved/dialed addresses on each attempt, reject redirects and bound response/time/concurrency. Private/metadata endpoints are denied unless a separately approved operator egress policy qualifies an intentional receiver. Generic webhooks use scoped signing keys; provider adapters use their actual authentication. Correlation/causation IDs, generated-comment markers and bounded fan-out prevent recursive webhook/agent loops. Test delivery uses the same protections and redacted history.
Cloudflare Workers/Queues may implement transport and Email Service may implement mail, while portable HTTP workers and SMTP/API mail adapters remain independent. A Cloudflare token does not enable any of these consumers automatically. Exact tasks, dependencies and acceptance tests remain in plan section 9.9.
Qualified native receipt proof
The disposable Docker Desktop proof passes on image sha256:798e232c939a4d07815ef22c8c63bed7a7772416508883399d5c77a7537f7052, recorded at product bded04f. Two actual Git pushes produced native signed deliveries accepted with HTTP 202. Exact captured transport was held only in private memory and replayed after restart, returning duplicate HTTP 204; changed-body delivery conflict, invalid signature, mismatched binding and unknown connection returned 409/401/400/404. Metadata privacy, absence of automatic runs and config-removal denial with retained history passed; disposable resources were removed.
The recorder replay is not a native redelivery API proof. This qualifies receipt/deduplication on Docker Desktop, not semantic replay prevention, Linux/Kubernetes origin networking, automatic CI, notification delivery or unattended principals. That historical intake deployment was on schema 202610020017 with manual pipeline capability ready and event intake disabled because no operator connection is configured. At that historical intake increment, automatic execution was unavailable. The new automatic push slice has separate disposable qualification; coding agents, outbound delivery and Cloudflare adapters remain unimplemented. See the event contract.