Repository navigation
feat(github): webhook-driven observer refresh - #6326
Open
schickling-assistant wants to merge 5 commits into
Open
schickling-assistant wants to merge 5 commits into
schickling-assistant wants to merge 5 commits into
Conversation
Contributor
Author
|
Burst nextest at d6671eb: https://github.com/compoundingtech/fractal/actions/runs/42914124868 — failed fast after 12/9753 tests; the 6 failures are all |
A loopback receiver ([github].webhook_listen + webhook_secret_file) verifies X-Hub-Signature-256 per delivery and writes observer.refresh-requested claims for matching github.repository / github.ref observers, coalescing on a pending refresh. Their attempt is "github-webhook:" + a digest of delivery and observer, so the claim schema is unchanged and N-1 readers accept it. A webhook-covered repository (webhook refresh within 30 min, read from replicated claims on the polling member) raises the GraphQL floor to the 15-minute settled interval and quiet standing polls go straight to the 10-minute ceiling. agent-identity: unknown agent-persona: unknown agent-supervisor: unavailable agent-tool: unknown agent-runtime: unknown agent-model: unknown
schickling-assistant
force-pushed
the
schickling-assistant/gh-webhook-refresh
branch
from
November 10, 2026 23:15
0b59911 to
5834933
Compare
The poller answers refreshes oldest first, so the oldest unanswered refresh may already be polling and have read GitHub before a new delivery's change. A delivery now queues one more refresh in that case; only an observer with two unanswered refreshes absorbs the delivery, because the second has not started and its poll reads what the delivery announced. agent-identity: unknown agent-persona: unknown agent-supervisor: unavailable agent-tool: unknown agent-runtime: unknown agent-model: unknown
Absorbing a delivery on the receiver judged "a poll has not started" from the receiver's replica, which lags the poller's answers, and counted a redelivered attempt twice; either way a delivery whose change postdated the running poll's reads could be dropped while coverage slowed the backstop. The receiver now writes one request per delivery (a redelivery of an already requested attempt writes nothing) and the poller coalesces: when the oldest pending request is a webhook refresh, it takes the newest pending webhook refresh from the same member, and answering it also answers every earlier webhook refresh from that member, each written after its change and before the poll started. Declared refreshes keep oldest-first order. agent-identity: unknown agent-persona: unknown agent-supervisor: unavailable agent-tool: unknown agent-runtime: unknown agent-model: unknown
…s reads Choosing the newest webhook refresh as the pending attempt renamed the arm on every delivery, so each delivery during a look retired it and discarded its answer; steady deliveries could keep any look from answering. The pending attempt is the oldest unanswered request again and names the arm, so deliveries never retire a running look. Under the look lock, right before the reads, an arm taken for a webhook refresh answers the newest pending webhook refresh from the same member instead, which answers every earlier one from that member. A reconciler test holds a look in flight while deliveries arrive and checks that it keeps its answer and that one more look answers the rest. agent-identity: unknown agent-persona: unknown agent-supervisor: unavailable agent-tool: unknown agent-runtime: unknown agent-model: unknown
Contributor
Author
|
Exact head 0308776 (all commits signed, verified).
|
schickling-assistant
marked this pull request as ready for review
November 11, 2026 00:30
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 00:30 — with
GitHub Actions
Inactive
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 00:30 — with
GitHub Actions
Inactive
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 00:30 — with
GitHub Actions
Inactive
schickling-assistant
had a problem deploying
to
evidence-signing
November 11, 2026 00:30 — with
GitHub Actions
Failure
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 00:36 — with
GitHub Actions
Inactive
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 00:44 — with
GitHub Actions
Inactive
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 00:44 — with
GitHub Actions
Inactive
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 01:13 — with
GitHub Actions
Inactive
…nd_return) agent-identity: unknown agent-persona: unknown agent-supervisor: unavailable agent-tool: unknown agent-runtime: unknown agent-model: unknown
schickling-assistant
requested a deployment
to
evidence-signing
November 11, 2026 01:20 — with
GitHub Actions
In progress
schickling-assistant
requested a deployment
to
evidence-signing
November 11, 2026 01:20 — with
GitHub Actions
In progress
schickling-assistant
requested a deployment
to
evidence-signing
November 11, 2026 01:21 — with
GitHub Actions
In progress
schickling-assistant
temporarily deployed
to
evidence-signing
November 11, 2026 01:21 — with
GitHub Actions
Inactive
This branch was previously deployed
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.
Problem — GitHub observers learn PR head/check/queue facts only through GraphQL polls gated at 5 min (15 min when settled). Polling is both slow for the factory landing loop and the dominant GraphQL spend. REST listings are already conditional (ETag).
Goal — Let verified GitHub webhook deliveries push freshness through the existing refresh-claim machinery, and make polling a slow backstop while a repository is webhook-covered.
Contract
[github] webhook_listen = "127.0.0.1:41871"(loopback, nonzero port, optional) andwebhook_secret_file = "/abs/path"(absolute; required iffwebhook_listenis set). The secret file is re-read per delivery with token-file custody rules (owner-owned regular file, no group/other bits, no symlink follow); absent/unreadable/empty → 503, never unsigned acceptance.POST /fractal/github-webhookon that listener; everything else 404. Body > 5 MiB → 413.X-Hub-Signature-256HMAC-SHA256 over the raw body, constant-time verify; missing/mismatch → 401. MissingX-GitHub-Event/X-GitHub-Deliveryafter auth → 400.ping→ 200. Content typeapplication/json.completed. Others → 204. Payload read:repository.full_name(lowercased),reffor push,ref+ref_typefor create/delete.github.repositoryobserver whose locator equals full_name (ASCII case-insensitive) and, for a push/create/delete of branchB, everygithub.refobserver ofOWNER/REPO@B, gets anobserver.refresh-requestedclaim withattempt = "github-webhook:" + hex(sha256("fractal.github-webhook.v1\0" + delivery + "\0" + observer)), unless that attempt is already requested (redelivery). 202{"refreshed": n}.github-webhook:) accepted within 30 min is webhook-covered — computed on the polling member from its replicated claims. Covered repositories use the 15-min settled interval as the GraphQL floor, and quiet standing polls go straight to the 10-min ceiling. Uncovered behaviour is unchanged.st.github.webhook.deliveries{github.event, outcome}(outcome refreshed|ignored|unauthorized|unavailable|invalid; unknown events labelledother), doctor linegithub-webhook(address, last verified delivery, counts) on daemons running the receiver.Decisions
store::observer_refresh::append_observer_refresh_tx), used by both declared refreshes and webhooks; active-observer listing is shared too. The pending-refresh query moved from fractal-missions to fractal-store (observer_refresh::pending_attempt), next tocoalesced_attempt, which the reconciler calls under the look lock.github-webhook:prefix of its requiredattemptstring. Registry digest unchanged.ObservationRequest.webhook_covered; reconcile computes it per poll viaStore::github_webhook_covered.fractal-kernel::github_webhook; the axum receiver lives infractal-daemon::github_webhook.Verification (local, macOS,
nix develop)cargo test -p fractal-kernel --lib github_webhook— event filter, repo-case/ref matching, attempt binding, doctor line, config validation: passcargo test -p fractal-store --lib observer_refresh— claim with exactly attempt/revision, branch ref observers, redelivery writes nothing, deliveries during a poll coalesce into one answer, declared refreshes keep their place, coverage window and declared refreshes not counting: 3 passcargo test -p fractal-daemon --lib github_webhook— 503 on absent / group-readable secret, 401 wrong/unsigned, 202 + claims, redelivery{"refreshed":0}, ping 200, ignored 204, invalid 400, 413, 404 for other routes/methods, HMAC unit: 3 passcargo test -p fractal-github-base --lib webhook_coverage— covered GraphQL floor 15 min vs uncovered 5 min: passcargo test -p fractal-reconcile --lib standing_watch_cadence— uncovered 180→360→600 unchanged; webhook-covered quiet poll goes to 600 s immediately, change returns to 180 s, provider seeswebhook_covered: passcargo test -p fractal-reconcile --lib ref_watch— incl.webhook_refreshes_during_a_look_keep_it_and_coalesce_into_one_more(a look held in flight while deliveries arrive keeps its answer; one more look answers the rest): 17 passcargo test -p fractal-schema --lib assistant_principal_registry_digest_transition(digest unchanged),cargo check -p fractal --bins --tests, clippy on touched crates (no findings on changed lines),scripts/check-sqlite-large-values: passPushed with
ST_SKIP_EVIDENCE=1(no local evidence signer); hosted CI at the exact head is the proof.Complexity — ~900 changed lines (three new modules with tests); above the usual 300-line target, kept as one PR because the receiver, claim path and cadence are one contract.
Concerns
[github]rejects unknown keys, so remove the two keys before rolling a member back.check_runcompletions) still trigger roughly one refresh look per look duration. Comparest_github_responsesbefore/after enabling.Follow-ups — Operator wiring (secret custody, GitHub hook, proxy) is outside this repo.
References — docs/st3/github-observer-admission.md#webhook-delivery; release-notes/github-webhook-refresh.json.