Skip to content

fix(scripts): coverage-ratchet sentinel matching immune to prose mentions + write sanity check - #2081

Merged
2witstudios merged 6 commits into
masterfrom
pu/fix-ratchet
Jul 15, 2026
Merged

2witstudios merged 6 commits into
masterfrom
pu/fix-ratchet

Conversation

@2witstudios

@2witstudios 2witstudios commented Jul 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes a reviewer-caught defect from #2075: scripts/coverage-ratchet.mjs's
sentinel-region regex was fooled by apps/web/vitest.config.ts's own
explanatory comment, which contains the literal substrings
/* ratchet:start */ and /* ratchet:end */ inside a single-line prose
comment. The non-greedy regex matched that fake 40-char span instead of the
real threshold block below it.

Consequences (both verified independently before this fix):

  • --dry-run reported fabricated current thresholds (0 instead of the
    real 44/85/56/44) — it exited clean while lying about the underlying state.
  • A real (non-dry-run) invocation would rewrite the fake span in place,
    producing unparseable syntax and breaking every test in apps/web.

This PR went through a self-triggered multi-angle review (8 finder agents),
two rounds of external bot review (CodeRabbit, Codex), and a final
/simplify cleanup pass — all real findings across those passes are fixed
here, across five commits.

Fix

  • sentinelRegex requires the ratchet:start/ratchet:end markers to
    occupy their own line (only leading/trailing whitespace besides the
    marker, CRLF-tolerant for Windows/core.autocrlf checkouts). Real usage
    always looks like that; a prose line mentioning the markers has other text
    sharing the line and is correctly rejected.
  • Two "refuse to guess" guards beyond the line-anchoring:
    • More than one well-formed sentinel block in the same config file throws
      (ambiguous — which one is real?) instead of silently taking the first match.
    • Marker text present but NOT forming a well-formed block (e.g. an
      autoformatter appends a trailing comment to the ratchet:end line)
      throws instead of silently falling back to the plain thresholds match —
      that fallback is only safe for configs with no sentinel at all.
  • Pre-write syntax check: before any write (dry-run or real), the
    rewritten config string is validated as parseable TypeScript via
    ts.transpileModule() (in-process, no file I/O, no subprocess — correctly
    TS-aware, unlike an earlier iteration that ran node --check on a renamed
    .mjs file and would have wrongly rejected TS-only syntax like
    satisfies or a type assertion, per Codex review feedback).
  • Extracted all of this into scripts/lib/coverage-ratchet-sentinel.mjs
    (matchThresholdBlock, buildThresholdBlock, parseThresholds,
    assertValidSyntax) so it's directly unit-testable; a final /simplify
    pass consolidated a double regex scan into one matchAll() and removed
    two unused exports.
  • 14 unit tests in scripts/__tests__/coverage-ratchet-sentinel.test.ts,
    wired into scripts/vitest.config.ts's include glob and the CI
    backfill-script test step.

Board: D.1 (arn4y1wscy7w5rptcc40xilg) and leaf 3.9
(lrkb1gu9j3qxtu4eqafsqplo) on E1 PR3 flipped to completed with a note on
the PR page (osazdx1f2zxo5dmeylyxs0qo).

Review status

  • CodeRabbit: passed (rate-limited on deep incremental review, no blocking
    status at any point).
  • Codex (chatgpt-codex-connector): two real findings across two review
    rounds — a P2 (JS-only syntax check on a .ts config) and a P3 (missing
    CRLF tolerance) — both fixed and confirmed clean on re-review of the
    exact fix commits ("Didn't find any major issues"). Both threads resolved
    after reviewer-verified fixes.
  • Self-review: 8-angle finder pass caught a real bug (temp file written to
    repo root instead of OS temp dir) independently confirmed by 5 of 8
    finder agents; a follow-up /simplify pass found four minor cleanups,
    two of which were applied.

Test plan

  • bun run typecheck — 16/16 tasks green (verified after every commit)
  • bunx vitest run __tests__/coverage-ratchet-sentinel.test.ts (from
    scripts/) — 14/14 passing
  • node scripts/coverage-ratchet.mjs --dry-run before fix: reported
    lines: 0 -> 60 (fabricated); after fix: lines: 44 -> 60 (real)
  • Real (non-dry-run) run against a disposable copy of
    apps/web/vitest.config.ts: minimal diff (only the 4 scalars change,
    indentation/comments preserved), file still passes node --check
  • CI: Lint & TypeScript Check and Unit Tests both green on the final commit
  • Branch merged with master (no conflicts), mergeStateStatus: CLEAN
  • Both review threads resolved after reviewer re-verification

🤖 Generated with Claude Code

https://claude.ai/code/session_01AEdW8TG2KMWjB5wTf5qB31

…ions + write sanity check

coverage-ratchet.mjs's sentinel regex was fooled by apps/web/vitest.config.ts's
own explanatory comment, which contains the literal substrings
`/* ratchet:start */` and `/* ratchet:end */` in a single-line prose comment.
The non-greedy regex matched that fake 40-char span instead of the real
threshold block: --dry-run reported fabricated zeros for current thresholds,
and a real (non-dry-run) invocation would have rewritten the wrong span,
corrupting vitest.config.ts into unparseable syntax and breaking every
apps/web test.

- sentinelRegex now requires the ratchet:start/ratchet:end markers to occupy
  their own line, so prose mentioning the marker text no longer matches.
- Extracted the matching/rewrite logic into
  scripts/lib/coverage-ratchet-sentinel.mjs so it's directly unit-testable.
- Added a node --check write-sanity-check: before any write (dry-run or
  real), the rewritten config is validated as parseable syntax; a mismatch
  aborts with a non-zero exit instead of writing corrupted output.
- New tests in scripts/__tests__/coverage-ratchet-sentinel.test.ts, wired
  into scripts/vitest.config.ts's include glob and the CI backfill-script
  test step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@2witstudios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 23 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32289dd3-411b-48e1-b5a5-f4c4e8b0c112

📥 Commits

Reviewing files that changed from the base of the PR and between 117d33d and 5d9c069.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • scripts/__tests__/coverage-ratchet-sentinel.test.ts
  • scripts/coverage-ratchet.mjs
  • scripts/lib/coverage-ratchet-sentinel.mjs
  • scripts/vitest.config.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/fix-ratchet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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: 6e5096453c

ℹ️ 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 scripts/lib/coverage-ratchet-sentinel.mjs Outdated
2witstudios and others added 3 commits July 15, 2026 00:51
Self-triggered 8-angle review (no external reviewer available yet) on
the coverage-ratchet fix surfaced two real residual hazards and some
cleanup, all fixed here:

- assertValidSyntax was writing its throwaway syntax-check file into
  the repo root (tmpDir=root passed from the caller) instead of the OS
  temp dir. If the process were killed between the write and the
  finally's cleanup, a stray file would be left in the working tree.
  Now uses mkdtempSync(os.tmpdir()) internally; the tmpDir param is
  gone. (Independently flagged by 5 of 8 finder agents.)
- matchThresholdBlock now refuses to guess in two ambiguous cases
  instead of silently doing the wrong thing:
  - More than one well-formed sentinel block in the same config file
    throws (which one is real?).
  - Marker text present but NOT forming a well-formed block (e.g. an
    autoformatter appends a trailing comment to the ratchet:end line)
    throws instead of silently falling back to the plain thresholds
    match — that fallback is only safe for configs with no sentinel at
    all, and applying it to a malformed sentinel reproduces the
    original truncation bug on apps/web's per-glob keys.
- Extracted parseThresholds() as its own tested pure function instead
  of four inline regex lines duplicated at the call site.
- Simplified indent handling: matchThresholdBlock now returns `indent`
  directly (empty string for the non-sentinel case) instead of callers
  re-deriving it from `isSentinel ? match[1] : ''`.
- Dropped the speculative `\r?` CRLF tolerance in the regex — this
  repo has no CRLF line endings (no .gitattributes eol=crlf, CI runs
  ubuntu-latest), so it was untested dead-code generality.
- Small CI comment fix: the "Run backfill script tests" step's comment
  said it was "scoped to backfill-*" when it already listed several
  non-backfill one-off files by name; reworded to describe the actual
  explicit-list mechanism.

4 new tests added (12 total, up from 8) covering the uniqueness guard,
the malformed-marker guard, and parseThresholds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d .mjs

Review feedback from chatgpt-codex-connector (P2): assertValidSyntax wrote
the rewritten config to a .mjs file and ran `node --check` on it, which
validates plain JavaScript syntax — but all five configs are .ts files. If
a future PR added TS-only syntax to any of them (satisfies, a type
assertion, a generic), coverage-ratchet.mjs would refuse to ratchet that
package forever with a misleading "fails a syntax check" error, even though
the config is valid TypeScript that Vitest loads fine.

Replaced the node --check subprocess (temp dir + write + spawn + cleanup)
with an in-process ts.transpileModule() syntax-only check (no
type-checking, no file I/O, no subprocess) using the TypeScript compiler
already a devDependency of this workspace. This is strictly better than
the previous fix: correctly TS-aware, faster (no subprocess spawn), and
has no temp-file lifecycle to get wrong.

New test proves the exact scenario the reviewer described: a config using
`satisfies`/type assertions is accepted (13 tests total, up from 12).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@codex review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

@2witstudios: Sure, I'll review the changes now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 20b870c7e3

ℹ️ 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 scripts/lib/coverage-ratchet-sentinel.mjs Outdated
Review feedback from chatgpt-codex-connector (P3): apps/web/vitest.config.ts
is not forced to LF by .gitattributes, so a Windows/core.autocrlf checkout
can have \r\n line endings. The line-anchored sentinel regex required a bare
\n after the start marker and only [ \t]* before end-of-line on the end
marker, so a CRLF checkout's well-formed sentinel block would fail to match
— and the malformed-marker guard added earlier would then treat it as
corrupt and refuse to update thresholds, a false failure on an otherwise
valid config.

Restored \r? tolerance around both line anchors (this had been present in
an earlier iteration and was dropped as "unexercised generality" by an
internal simplification pass before external review caught the actual
portability gap). New test proves a CRLF-normalized copy of the real
sentinel fixture still matches (14 tests total, up from 13).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 2426c4e055

ℹ️ 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".

Ran /simplify (4 parallel cleanup agents: reuse, simplification,
efficiency, altitude) against the full diff. Reuse, efficiency, and
altitude all came back clean (altitude explicitly confirmed the
regex+guards+ts.transpileModule approach is proportionate to the
problem for this manually-run, git-diff-reviewed internal tool — an
AST-based rewrite would be over-engineering here). Simplification found
four real items; applied the two cheap, in-scope ones:

- matchThresholdBlock scanned the config twice (once via a global regex
  just for the ambiguity count, once via a plain regex to recover the
  indent capture group). Replaced with a single matchAll() pass that
  yields both the count and the capture group.
- SENTINEL_REGEX/PLAIN_REGEX were exported with no consumer outside this
  file (confirmed via grep) — un-exported them.
- Simplified a test that re-implemented parseThresholds's regex
  extraction inline to just call parseThresholds directly.

Skipped: dropping the unused `indent: ''` field from the non-sentinel
branch — real but marginal (an unread destructured field, not actual
complexity), not worth the interface churn.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 5d9c069cb8

ℹ️ 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".

@2witstudios
2witstudios merged commit bfd5c5a into master Jul 15, 2026
3 checks passed
@2witstudios
2witstudios deleted the pu/fix-ratchet branch July 15, 2026 16:44
2witstudios added a commit that referenced this pull request Jul 17, 2026
…e, poll-fallback) + notify mentions on interrupted replies (#2097)

* test(ai): RED — materializeInterruptedStream must fire mention notifications like the finalize path

D task st3pyh9q4zwnmae00j195o97: an interrupted reply that @mentions a user
currently vanishes without notifying anyone — the normal finalize path
(saveMessageToDatabase) fires notifyMentionedUsers behind the route's gate
(page.driveId present, conversation explicitly shared, non-empty content),
but a materialized interrupted reply never does. Spec: exactly one
notification per actually-written materialization, never a duplicate when
the CAS upsert skipped (the row's own onFinish already notified), fail
closed on missing page/conversation, best-effort + warn on lookup failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ai): GREEN — close R.4 coverage gaps + notify mentions on interrupted replies

Four items from the E2 PR5 R.4 audit (D.1/D.2/D.3 + the epic-level mention
D task):

1. materializeInterruptedStream now fires notifyMentionedUsers for a
   page-chat reply it materializes, mirroring the finalize path's gate
   (page.driveId, conversation isShared, non-empty content; stream owner
   as triggeredBy, page title as mentioner). The upsert gained .returning
   so the notification only fires when the CAS actually wrote — a row
   already terminalized by its own onFinish (which already notified) can
   never double-page the mentioned user. Best-effort: lookup/notify
   failures warn and never un-succeed the materialization. Module stays
   at 100% branch (36/36).

2. advisory-lock.ts 90% -> 100% branch: the non-Error unlock-rejection
   wrap (pg release(err) destroy contract + operator-visible log) is now
   tested. The last uncovered branch was a v8 artifact — the catch->finally
   junction of a catch that exits abruptly on every path, unreachable by
   any input — so the function was flattened per the epic rail ("a branch
   you didn't design; fix the design"): try-lock gets its own catch, the
   clientPoisoned flag is gone, unlock/destroy lives in a named helper.
   All 7 pre-existing behavior tests pass unchanged.

3. checkpoint-serialize.ts 93.33% -> 100% branch: mismatched/truncated
   originRawIndex falls back to survivingFromRawIndex 0 — the under-skip
   (self-correcting duplicate) direction, never over-skip (content loss).

4. stream-join-poll-fallback.ts 78.94% -> 100% branch: abort landing at
   each await point inside an in-flight tick discards the stale snapshot
   and never fires a bogus onNotFound; an abort before the clearInterval
   listener registers is caught by the per-tick entry guard; AbortError
   rejections with a live signal stay silent but keep polling; a body
   with no streams field is terminal, not a crash.

Thresholds are ENFORCED in config: per-glob 100% entries for all three
modules. packages/db's vitest.config.ts gained the #2081 whole-line
ratchet sentinel first — scripts/coverage-ratchet.mjs covers that config,
and its plain-regex fallback would truncate a thresholds block containing
per-glob sub-objects. Verified with coverage-ratchet --dry-run on both
configs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ai): RED — every terminal write must carry the mention gate exactly once (Codex P2)

Codex review on PR #2097: the route has three writes that can flip the
assistant placeholder out of 'streaming' (execute-end, onFinish, outer-catch
cleanup) but only onFinish carried mentionNotify. execute-end's own docblock
says "when onFinish never runs, this write stands as the sole record" — in
that documented gap the @mention notification was permanently lost, and
materialize-interrupted-stream's CAS-gated notify (which assumes "the route
flipped it => the route notified") could never recover it.

Spec: whichever terminal write lands FIRST carries mentionNotify, exactly
once per request; the once-flag latches only on a successful save so a
failed execute-end persist still lets onFinish notify; the private-
conversation gate holds on every path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ai): GREEN — mention gate travels with whichever terminal write lands first (Codex P2)

Route-scoped mentionNotifyFor helper + once-flag, hoisted out of the try so
the outer-catch cleanup can use it too. All three writes that can flip the
assistant placeholder out of 'streaming' (execute-end, onFinish, cleanup)
now attach mentionNotify when they are the request's first successful
terminal write; the flag latches only after a successful save so a failed
execute-end persist still lets onFinish notify. The gate is unchanged
(page.driveId + requester + explicitly-shared conversation) plus
saveMessageToDatabase's own content.trim() firing condition, so the flag
can only latch when a notification would actually have been dispatched.

This makes materialize-interrupted-stream's CAS-gated notify premise true:
any row the route flipped out of 'streaming' WAS notified by the route, so
skipping notification on a CAS miss can no longer lose an @mention in the
execute-end-then-death window Codex flagged. Comment there updated to cite
the real contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(db): CodeQL — lock key never reaches the format-string position; review-pass cleanups

CodeQL flagged the unlock-failure console.error (js/tainted-format-string
high, js/log-injection medium): lock keys can be derived from request-
supplied ids, so interpolating one into console.error's first argument
would let a crafted key smuggle %-directives or forge log lines. Now a
constant format string with the key newline-stripped as a %s argument,
pinned by a test (evil\nkey\r%s -> 'evil key %s', never in the format).

Review-pass cleanups (self-review finders, behavior pinned by existing
tests throughout):
- advisory-lock: fn-error/success unlock duplication collapsed to one
  try/finally (unlockAndRelease never throws, so it cannot mask fn's
  rejection); still 100% branch.
- route.ts: the gate+attach+latch trio, hand-copied at all three terminal
  writes, now lives in one saveTerminalAssistantMessage helper — a fourth
  terminal write can't get the exactly-once protocol wrong. Dead
  page.driveId ternary removed (schema NOT NULL).
- materialize-interrupted-stream: the two inline gate selects replaced
  with the readers the live paths already use — pageRepository.findById
  (also closes a real gap: a page trashed between stream death and reap
  no longer pages drive members) and conversationRepository.getConversation
  (the route's own isConversationShared source, so the two gates can't
  drift). Test mocks moved to the repository boundary; redundant inner
  beforeEach deleted. Still 100% branch.
- Cross-route mention-gate extraction (v1 completions still builds its
  gate inline) filed as an epic-level D task (class 4) rather than done
  as a drive-by.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(db): unlockAndRelease honors its never-throws contract even when release() throws

Second self-review pass (finders A/B/C on the final shape; the two
correctness finders independently re-confirmed the cross-process duplicate
already filed on the epic board):

- advisory-lock: if the success-path client.release() throws (release
  hook, pool shut down), the catch's destroy-release then throws pg's
  synchronous double-release error, which escaped the catch and replaced
  a successful fn() result with a rejection inside withAdvisoryLock's
  finally. The destroy-release is now guarded and logged; two new tests
  pin the contract (Error and non-Error release throws). Still 100%
  branch (10 tests).

- route.ts: the exactly-once mention latch documented honestly as
  best-effort — the latch flips only after the save resolves, so
  overlapping same-process terminal writers and the cross-instance
  materializer race both resolve to a DUPLICATE ping, never a lost one
  (the epic's chosen direction; idempotent-notification fix filed as an
  epic D task). Execute-end comment also names the content source caveat
  (buffered snapshot vs onFinish's refined message), filed likewise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(db): releaseQuietly on every advisory-lock exit; JSON.stringify the logged key

CodeRabbit review + CodeQL alert 262 on PR #2097:

- Every exit of withAdvisoryLock now funnels release() through a shared
  never-throw releaseQuietly helper — the lock_busy and try-lock-failure
  paths were still calling release() bare, so a throwing release there
  replaced the promised resolved outcome with a rejection (inconsistent
  with the contract the previous commit hardened on the acquired path).
  Two new tests pin lock_busy and connection_error resolving through a
  throwing release. 12 tests, still 100% branch.

- The logged lock key switches from replace(/[\r\n]/g,' ') to
  JSON.stringify: CodeQL's log-injection model did not recognize the
  replace form (alert 262 fired on the sanitized line itself), and JSON
  escaping is strictly stronger — newlines become literal \n, the
  constant format string keeps %-directives inert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ai): re-pin start-generation-exclusive to advisory-lock's never-throws release contract

CI caught a cross-file regression from 518876d: withAdvisoryLock's release
machinery (unlockAndRelease/releaseQuietly) now never throws, so the #2080
double-failure scenario (unlock query rejects + destroy release(err) throws)
no longer escapes the finally and reject an already-successful run. The test
pinning that old contract failed.

The property the test guards is unchanged and still asserted: run() executes
exactly once and is never re-invoked unlocked. Updated to also assert the new,
stronger outcome — the caller keeps the successful locked result instead of
losing an already-started generation to lock-cleanup noise, the destroy is
still attempted, and no lock_error degrade telemetry fires. Stale docblock
references to the old finally-can-throw behavior refreshed.

Audited every other real-advisory-lock consumer: machine-storage-billing
branches on the resolved connection_error outcome (23/23 pass); the processor
workers use their own pre-helper raw-pg copies (out of scope, unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.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