Skip to content

fix(serve): refuse an intent-recording ref update when the hook cannot wire its eventing - #724

Merged
ThomasK33 merged 17 commits into
mainfrom
fix-pg-hook-open-fallback
Nov 6, 2026
Merged

ThomasK33 merged 17 commits into
mainfrom
fix-pg-hook-open-fallback

Conversation

@ThomasK33

@ThomasK33 ThomasK33 commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Why

Every accepted write to a forward_on_push repository must record its forward intent with the ref update. On PostgreSQL the ref CAS records it (#679). On the local schedule (SQLite and S3-WAL) the hook records it before the CAS (#678). When a git hook could not set up its eventing (its secrets codec, meta.Open, or its River insert-only client failed), it fell back to the eventless ref store, which records no intent. The push then landed with no forward intent and no events. A failed meta.Open does not prove that the CAS fails too: the database can be back a moment later, and the CAS then commits the push alone. #678 closed this for a local-schedule store that does not open. The secrets-codec path still had the gap on every backend, and every path had it on PostgreSQL.

This PR is the one place for the hook's refusal rule on both backends.

Fix

A hook whose eventing wiring fails refuses every update that would record forward intent, before its CAS, on every backend. The client sees the push fail and can push again.

  • What still goes through. Updates that record no intent even with healthy eventing keep failing open: ephemeral updates (the +ephemeral repository key or the ephemeral ref namespace), a sync run's own loopback push (OPENSTORAGE_SYNC_CLAIM; on main a refused loopback push fails the inbound run with FailPush), and a caller whose token may not forward (hookCallerDeniedForward: no git:read, or ref-policy ops).
  • What is still refused. Without the store the hook cannot tell whether the repository has an upstream link, so it also refuses such an update to a repository without one. ADR-33's amendment says so.
  • One mechanism. wireHookEventing ends every failed wiring stage through one disabled helper. It closes what the wiring opened, and it wraps the ref store in fix(sync): record git forward intent before the CAS and refuse stale held snapshots #678's intentlessRefStore. fix(sync): record git forward intent before the CAS and refuse stale held snapshots #678's separate refusal for a local-schedule store that does not open now goes through the same helper.
  • IdempotentCAS stays. intentlessRefStore passes the updates it accepts to ref.IdempotentCAS, as the eventless default does, so a CAS that commits and then loses its reply still wins. It overrides CASRef and CASRefGen alike.
  • No store detail for the pusher. git relays the hook's stderr and its error to the pusher, and a store error names the database host, user and name, or the S3 endpoint. The refusal now names only the failed step (refusing the ref update: hook eventing unavailable (open meta store); push again later). The hook logs hook eventing unavailable: open meta store; refusing only updates that would record forward intent, with no store error. The same holds for fix(sync): record git forward intent before the CAS and refuse stale held snapshots #678's BeforeCAS refusal (hook could not record the forward intent; push again later) and the format-check warning.
  • What the operator loses. serve logs a store error only when its own connection fails. When only the hook's connection fails, neither the pusher nor the operator sees the cause, only the failed step. ADR-33's amendment says so.
  • Follow-up PR. Older hook paths still pass store errors to the pusher: the ref store opens in the pre-receive hook, the reservation probe and the update hook, the update hook's CAS error, the S3-WAL probe error (it names the endpoint host and the bucket), and the events decorator's push event lost and sync poke lost log lines. A follow-up PR gives those paths a fixed message and gives the cause back to the operator through a channel that only serve reads.
  • fix(sync): record git forward intent before the CAS and refuse stale held snapshots #678's read path. On the local schedule, BeforeCAS no longer runs for a caller whose token may not forward. It records nothing for such a caller, so a failed read of the upstream link no longer refuses that caller's update.
  • Docs. ADR-33 gets an amendment. ADR-44 and the ADR-50 amendment link to it, and the serve README and the WrapHookRefStore doc comment state the rule.

Tests

TestHookEventingRefusesIntentWithoutEventing (cmd/openstorage) runs each shape on PostgreSQL (an unresolvable canary host, so meta.Open fails) and on SQLite (a canary-named database path under a file). Each shape creates a ref, updates an existing ref and deletes one, through CASRef and CASRefGen, over a ref store that commits each create or update and then reports an EOF:

Shape On main (1996cc3) On this PR
branch, tag PostgreSQL: accepted without intent. SQLite: refused, naming the store error refused with the fixed message, and the ref is unchanged
secrets config invalid accepted without intent refused with the fixed message
ephemeral namespace, ephemeral key accepted accepted, and a create's or update's lost commit reply recovered
sync loopback accepted accepted, and recovered
write-only token PostgreSQL: accepted. SQLite: refused accepted, and recovered

The main column comes from a run of a branch creation. On main, both the eventless default and #678's refusal look only at the repository key and the ref name, so a tag, an update and a delete take the same path (I read the code and did not run them on main).

The test also checks that the hook's log and errors name no canary part: the PostgreSQL URL's user, password, host and database, or the SQLite path (match counts of 0). A control first checks that meta.Open's own error names each part except the password, so a count of 0 proves something.

  • TestHookEventingRefusesUnreadableLocalSchedule adds a write-only token (accepted) and an unresolvable S3 endpoint, whose host must not appear in the hook's output. It pins BeforeCAS's refusal, push again later suffix included, and its log line hook refuses ref updates: record forward intent.
  • TestHookEventingRiverFailureRefusesAndCleansUp (real PostgreSQL) replaces the River constructor with a failing one (newHookRiver): the update is refused, and the metadata store's connection is closed (pg_stat_activity).

Twenty-two source mutations each turn at least one of these tests red: no refusal; a refusal of the loopback push, of every update, of forward-denied callers, or on PostgreSQL only; the old eventless return for the secrets codec, meta.Open or the River client; no cleanup(); the refusing store over the raw ref store; the store error in the refusal or in the log line; BeforeCAS for forward-denied callers; BeforeCAS's raw error; CASRef passing intent updates through; the store error in the format-check warning; a refusing store that accepts deletes, tags or updates of existing refs; no push again later suffix on either refusal; and no BeforeCAS log line.

The verify-lane-schedule model does not describe the hook's failure handling. check.sh passes, and check.sh --record recorded the new sync commit, because wireHookEventing changed.


Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: xhigh

…its forward intent

When the hook's meta.Open (or its secrets codec) fails on PostgreSQL,
wireHookEventing falls back to the eventless store. That store records no
forward intent in the CAS, so a forward_on_push push lands with no intent
and no events.

Change-Id: If642ce461c7bea3b58d07107f66fdc0e5b74937f
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…its eventing

On PostgreSQL the ref CAS records a forward_on_push repository's forward
intent, and a failed meta.Open does not prove the CAS fails too: the
database can be back by then. The hook's eventless fallback then accepted
the push with no intent and no events. A hook whose secrets codec,
meta.Open or River insert-only client fails now refuses the ref update
before its CAS on PostgreSQL, and the client can push again. SQLite and
S3-WAL keep failing open. ADR-33 and ADR-44 get amendments, and the
serve README states the new rule.

Change-Id: I8dbf5b106e0df5064dfb640b8b3e38dd1eb54555
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…fusal on PostgreSQL

Change-Id: I4f88970108351e63faeccb6aa8b4e77fe50cd8cb
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…back

Change-Id: Ife7e65a1328120e16be1cb57582ecca7703191a4
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T12:58:07.433535Z bc9d666 Manual request
🔒 Security Review ✅ Completed 2026-11-06T12:59:26.402458Z bc9d666 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 3493678a79

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: 3493678a79

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…back

every update that would record forward intent (intentlessRefStore), and
let ephemeral updates and a sync run's loopback push fail open. The
disabled helper now applies the same rule on every backend for every
failed wiring step (secrets codec, meta.Open, River client), instead of
refusing all updates on PostgreSQL only. The PostgreSQL fail-open test
and its helper go; TestHookEventingRefusesIntentWithoutEventing covers
both backends, ephemeral updates and the loopback push.

Change-Id: I2c6060d2056019d735f45eeb85eaa6e5c1685691
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Change-Id: I1fd9c385e65039187215901f1c51b84328255e40
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33 ThomasK33 changed the title fix(serve): refuse a PostgreSQL ref update when the hook cannot wire its eventing fix(serve): refuse an intent-recording ref update when the hook cannot wire its eventing Nov 6, 2026
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: d35594ad02

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d35594ad02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmd/openstorage/wire_eventing.go Resolved
Comment thread cmd/openstorage/wire_eventing.go Resolved
…the hook refuses intent

The refusing store wrapped the hook's raw store, so a fail-open
ephemeral update lost ref.IdempotentCAS's recovery of an ambiguous CAS
outcome. It now wraps IdempotentCAS, as the eventless default does. A
caller whose token may not forward records no intent even with eventing,
so its updates now fail open instead of being refused. #678's separate
local-schedule refusal for a failed meta.Open goes through the same
disabled helper, so both backends follow one rule.

Change-Id: If3db34488697ffcf59b012c8338dd712bd89e8de
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…tail for the pusher

- The hook's local-schedule BeforeCAS no longer runs for a caller whose
  token may not forward: it records no intent, so a failed read of the
  upstream link cannot refuse that caller's update.
- The refusal and the hook's log name only the failed step. git relays
  the hook's stderr and its error to the pusher, and a store error names
  the database host, user and name, or the S3 endpoint. The same holds
  for BeforeCAS's refusal and the format-check warning.
- newHookRiver lets a test reach the River client's failure.
- Tests: every shape (persistent, secrets failure, ephemeral namespace
  and key, sync loopback, write-only token) on PostgreSQL and SQLite,
  through CASRef and CASRefGen, over a store that commits and then
  reports an EOF; a canary database URL that the pusher's output must
  not name; the River failure and its cleanup on a real PostgreSQL.
- ADR-33, ADR-44, ADR-50, the serve README and the WrapHookRefStore doc
  say what the hook does now.

Change-Id: I49051297eed7ed15e0890d5184739affba9242b1
Signed-off-by: Thomas Kosiewski <tk@coder.com>
A down fake bucket answers 503, whose error names no host, so a warning
that logged the read error passed. An unresolvable endpoint's error
names the host.

Change-Id: I64b02ada00c005d64f49a77a1a6a8fcfa0ac6dce
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…error where it is wired

The lane-schedule fact MarkBeforeCas reads the local-schedule branch's
beforeCAS = enqueueSyncOrForward(...) assignment. quietBeforeCAS now
hides the store error where the options are built.

Change-Id: I2c35fc57702e7aa476255c4d32cf72be47429293
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…k refusal rule

Change-Id: Id7a2d27b8643eeb744d4aa354ad4282fcb0ea540
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…back

Change-Id: I61315965de08eee34ccd9e7432dcec99586d7e9d
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: d0ab54fe64

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: d0ab54fe64

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…back

Change-Id: I0561bf38eaf94bd9e0de8cdcd25b61758e582ead
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…e, and say eventing is unavailable

- The table now creates a branch and a tag, updates and deletes a ref,
  on PostgreSQL and SQLite, so a rule that accepted any of them fails.
- The refusals' "push again later" suffix and BeforeCAS's log line are
  pinned.
- The SQLite rows open a canary-named path, with a control that
  meta.Open's error names it.
- The wiring's log line no longer says the hook refuses updates, because
  it still accepts the ones that record no intent.

Change-Id: I8f6078e5b2901562290244db9779a17bc9798af8
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…'s connection fails

ADR-33's amendment and the disabled helper's comment said serve logs its
own store errors. serve logs a store error only when its own connection
fails, so when only the hook's connection fails, neither the pusher nor
the operator sees the cause.

Change-Id: Ia9eed12d6266fa033807d6f61ad655ebe5eab1da
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…comment fix

Change-Id: I6ac584aa23065a4dfb5f7b686dce9951e3aa854d
Signed-off-by: Thomas Kosiewski <tk@coder.com>
…back

Change-Id: I6aff803447c09f1fe94348567cdb48815e9b6156
Signed-off-by: Thomas Kosiewski <tk@coder.com>
@ThomasK33

Copy link
Copy Markdown
Member Author

@codex review

@ThomasK33

Copy link
Copy Markdown
Member Author

@codex security review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: bc9d666650

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review

Security review completed. No security issues were found in this pull request.

Reviewed commit: bc9d666650

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@ThomasK33
ThomasK33 added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit 03cc9f2 Nov 6, 2026
11 checks passed
@ThomasK33
ThomasK33 deleted the fix-pg-hook-open-fallback branch November 6, 2026 14:35
ThomasK33 added a commit that referenced this pull request Nov 6, 2026
…nd the REST gate order

check.sh --record: 12 facts TRUE, LaneSchedule.cfg 3535660, OtherWriters.cfg
56705945 and Liveness.cfg 348735 distinct states pass, NoLeaseAssumption.cfg
fails as expected.

Change-Id: I379a587269e49a62c4cfad785535848fd5e83a5f
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant