Repository navigation
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
thymikee
left a comment
There was a problem hiding this comment.
Request changes.
- (a) Holds: allocate validates each caller before joining; rent publishes the owner before its start microtask, so joiners observe the same creation without inheriting caller validation errors.
- (b) Holds: release/shutdown call the same owner.end; a late create after the 120s budget re-enters end, records rent, and stops once. Evidence: lease-owner.test.ts pending-create release/shutdown case.
- (c) Holds: create and stop waits are bounded (120s/30s); uncertain stop remains stop-failed with the stop promise retained for retry. All disposal provider calls are behind start/readiness budgets or the bounded stop wait.
- (d) Holds: startDevice uses POST and Revyl client defaults retries only GET; start has one attempt on unknown outcome.
- (e) Holds: startRevylSession calls recordRent immediately after start_device returns, before readiness polling.
- (f) Holds: ready allocation reads entry.session and returns synchronously before any await.
- (g) Holds: shutdown gathers outcomes from this runtime instance's own lease map.
- (h) Not independently verified here: eager-closure guard was not run. PR body reports 882 passing.
- Finding [P1]: packages/provider-revyl/src/runtime.ts:149 — hasLiveSession/getInteractor still expose the session once shutdown begins and while release is stopping or stop-failed. These read paths use sessionForDevice without either guard, so callers can continue binding/dispatching against a device being disposed. Refuse access when shuttingDown or the lease owner is releasing, and add stopping/stop-failed coverage.
- Tests: provider-revyl focused unit suite passed once (147). New lifecycle assertions fail on base: base returns allocation after concurrent release, while head rejects it as provider-lease-releasing. Unit evidence supports the local ownership races but not live provider behavior; the live run got HTTP 401 and created zero sessions, so live device behavior remains unverified.
Size Report
Startup median (7 runs, lower is better):
|
thymikee
left a comment
There was a problem hiding this comment.
Independent review at head 1f1ae76b279fff1db30e0554958ebc8a30c941cc; merge-base a0e7c7dc239922bf93adaceff20a2f7e78579146.
Verdict: no actionable code regressions found. The owner-local RevylLeaseState union has one transition owner (RevylLeaseOwner), and a lease in stopping or stop-failed cannot supply a ready device.
Checklist, with concrete traces (paths below are under packages/provider-revyl/src/):
- (a) Holds.
runtime.ts:200-242validates each caller synchronously, checks request compatibility, and publishes the owner without awaiting.lease-owner.ts:60-76defers creation to a microtask, which runs after publication. Thus A's invalid profile fails before an owner exists; valid B starts independently. If A is already starting, B's incompatible profile fails in B's call without poisoning A's shared creation. - (b) Holds. Release or shutdown calls
end(), which synchronously replaces starting/ready with stopping and shares one disposal promise (lease-owner.ts:117-145). Disposal waits for creation before stopping the returned identity. If creation exceeds 120 seconds, it reports uncertainty, retains the owner, and installs a late continuation that re-entersend(). Concurrent release/recovery/shutdown and the failed-start continuation join the same stopping owner.lease-owner.test.ts:98-138covers both release and shutdown crossing that budget, a late successful create, and one stop;runtime.test.ts:273-288,397-427covers shared disposal and stop-after-create ordering. Exactly-once here means one shared logical stop attempt; failed confirmed attempts can be retried, and the HTTP client can retry the idempotent stop. - (c) Holds for owner disposal. Creation and stop are wrapped by independent 120-second and 30-second waits (
lease-owner.ts:138-177,216-228), including a transport promise that ignores abort. An unanswered stop enters stop-failed withcleanup: 'uncertain'and its pending stop handle. A retry joins that handle; late confirmation closes/removes the owner, while late failure leaves it available for a new attempt. A settled refused stop also retains the owner. Release propagates uncertainty instead of reporting success (runtime.ts:289-296). The separate restart-recovery path uses the existing HTTP client's finite per-attempt timeout/retry count (revyl-client.ts:106-119,228-239). - (d) Holds.
startDeviceis a POST without retry opt-in (revyl-client.ts:152-159);sendtherefore permits exactly one attempt (106-110). This is the provider's own fetch client, with no SDK retry layer. Unknown create outcomes carry the deterministic session identity into cleanup rather than issuing another create. Stop retries are explicitly idempotent and do not retry creation. - (e) Holds. Immediately after the create result returns,
revyl-session.ts:123-127parses the remote run/session identity and callsrecordRent, before identity checks or any readiness await. The owner records it in starting/stopping, or updates the uncertain identity in stop-failed (lease-owner.ts:106-110). - (f) Holds. Existing-ready allocation follows
runtime.ts:207-230without an await. A starting allocation does await creation, then re-readsentry.sessionbefore answering; if release overtook that wait, the getter supplies no session and allocation fails withprovider-lease-releasing. - (g) Holds by source trace. Each runtime constructs its own leases map and shutdown flag (
runtime.ts:96-100); shutdown aggregates failures only from that map (167-177). Plugin cleanup invokes and captures each registration's shutdown separately (packages/plugins/src/registry.ts:182-191,325-342). No module-global warning accumulator or cross-instance lease state was introduced. - (h) Holds. I compared the production eager-import walker at merge-base and head for all 437 measured entries: zero changed closure module sets. The new runtime imports remain behind the existing function-scoped plugin setup seam.
hasLiveSession and getInteractor both go through sessionForDevice (runtime.ts:149-157,312-316). The owner getter exposes a session only in ready (lease-owner.ts:98-103). Therefore both refuse during stopping and stop-failed; inventory also returns null. The new test exercises immediate retirement and post-failure refusal.
Validation: install/build passed; the single permitted provider test invocation, pnpm exec vitest run --project unit-core packages/provider-revyl, passed all 147 tests in 11 files. Reviewed source remains clean. I did not run further tests or wait for CI.
Base sensitivity: the PR reports five executed base-red regressions. I independently checked their failure traces against the base sources, but did not execute a second suite under the one-run limit: the immediate-retirement test fails because base still exposes the session; release before the first allocation await misses base's unpublished entry and allocation succeeds; both late-create budget cases fail because base waits indefinitely for start; the unanswered-stop case fails because base has no outer disposal budget. These are source-checked counterfactuals, not an independently reproduced base-red run. The bot's proposed extra stop assertion in the immediate-publication test is optional strengthening: other tests already assert the stop count and stop-after-create ordering, while that test specifically detects admission before the first await.
The unit evidence is sufficient to support this scoped in-memory ownership/lifecycle fix. It is not sufficient to claim live Revyl allocation, device commands, or remote release were verified. The reported HTTP 401 / zero-created-session run exercises authentication refusal only; successful provider integration and live cleanup remain unverified. CI was still pending when inspected and is not counted as passed evidence here.
|
Addressed r4441116491 at |
Apex SentinelVerdict: Since the last review: 4 new · 0 still open · 5 no longer raised 4 finding(s): 4 nit. Verdict: approve. reached max rounds (1). 🔵 Nit (4)
Reply under an inline comment with |
There was a problem hiding this comment.
Apex Sentinel: 5 new findings on 240b050. The verdict and the full list are in the summary comment.
|
Final round at |
There was a problem hiding this comment.
Apex Sentinel: 4 new findings on a8ebe1f. The verdict and the full list are in the summary comment.
|
Thanks for the work here. I found two lifecycle defects at a8ebe1f, and the change still needs live Revyl evidence. CI is green: 21 checks pass and none fail. There are no conflicts. Before merge, the two defects need fixes and the live run needs to be attached. I read the code only. I did not run the tests or reach Revyl, and both defects are traced from code, not reproduced. A failed start's stop is no longer bounded by the caller's patience. At base,
This change rewrites allocate, release, recovery, shutdown and when the interactor is handed out, so it needs live validation. The only live attempt returned HTTP 401 'Invalid API key' with zero sessions, so no changed route ran against Revyl. Please run with a valid key at the final head and attach output for three routes. First, connect to ready, run one device command, then disconnect. The output should show release data with Can this PR land first without a second Revyl-only lifecycle owner beside the shared one? #4158 already moves the same bookkeeping into Not blocking, and fine to take or leave: On the earlier review threads: the one on |
A caller that gave up in `join()` while a release, recovery or drain was ending its lease got its own `gaveUp` reason (`request_canceled` or `provider-not-ready`) instead of `provider-lease-releasing`, and lost `runtime-shutting-down` during shutdown. One private `refusal(lease, entry, fallback)` now decides every refusal: shutdown first, then a lease that is ending or replaced, else the site's own reason. Allocate, delivery, the start's failure and join's give-up all go through it. A start that failed on its own still reports its own error. Join builds its refusal before the caller leaves, so a caller's own departure is never read as someone else's release, and a throwing driver factory still settles the caller. Regression (from #4169): a caller that gives up while a release is in flight is told the lease is being released, and exactly one `/stop` is sent. It fails at dce5c56 with `request_canceled`. The table notes that it does not bound `findByLease`, and the failed-start test schedules its rejection on the next tick instead of asserting a wall-clock bound. Test comments that restated their names are trimmed to keep the change within its budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) * refactor(providers): share one lease table across cloud providers Revyl kept its own lease bookkeeping: one start per lease, joins bounded by each caller's budget, stop-then-forget release with one shared stop, a shutdown drain, and expired-lease recovery by the lease's own id. That bookkeeping moves to `@agent-device/capture-kit/provider-lease-table`, and Revyl now supplies only start, stop, find-by-lease and its errors. The table bounds every caller by the earlier of its deadline and the provider's start timeout, and stops a ready answer that arrives after the starting caller's deadline or cancellation. The provider runtime instance fingerprint moves next to `providerRuntimeOwner` as `providerRuntimeInstance`; Limrun's digest is unchanged, Revyl's widens to the shared 32-byte length. Limrun's lease ownership stays as is until #4141 lands, so it moves onto the table in a follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(providers): move the lease table to provision-kit Renting a cloud device prepares it for execution, which ADR 0019 gives to provision-kit; capture-kit owns capture, snapshot and recording. The table, its tests and its export move unchanged, and Revyl depends on provision-kit instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(providers): fence lease teardown and keep uncertain rentals owed Review of the shared lease table found routes that could forget or hand out a billed device: - Release, recovery, a failed start and shutdown now end a lease through one teardown that they all join. It fences the lease in the same step, so an allocation that arrives or is still waiting is refused with `releasing` instead of handed a device that is being stopped. - A recovery of a lease this process holds nothing for publishes an entry before its lookup, so concurrent routes share one lookup and one stop, and a failed stop keeps the handle it found. - A start that rejects and that find-by-lease cannot name stays owed: the lease is refused and kept pending until a teardown finds it. - A start can publish its rental before it answers, and a teardown waits for an answer for at most the start timeout before it stops that rental. An optional stop timeout bounds each wait for a stop, and the next teardown joins the stop still in flight. - A ready device goes to any caller still waiting; it is stopped only when every caller gave up. `gaveUp` reports the caller's own budget. - Joinability is an `allocate` option instead of a `requestOf` read, and join clears its timer once the caller is answered. Both providers pin their runtime-instance digest through their own wrapper, and ADR 0007 and ADR 0019 describe the table as built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(provider-revyl): reference provision-kit from the TypeScript project The lease table moved to provision-kit, and the layering suite requires each package's project references to match its manifest dependencies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(provision-kit): split the lease table's start and teardown steps Fallow's cognitive-complexity bar flagged runStart and runTeardown. Each step now has its own method (call the start, record its answer, name a failure, end a failed start; name a rental, finish a teardown), with no change in behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(providers): hand a ready device only to callers still within budget Second review round on the shared lease table: - A ready answer goes to a caller only while that caller's own signal and deadline still hold, so a caller whose deadline passed before its timer ran is not handed a device; nobody waiting means it is stopped. - The session a start rented is recorded even when nobody takes it, so a release that ended the start reports the device (Revyl: `revylRunId`). - A caller whose budget is already spent sends no start. - Shutdown repeats its drain until it has ended every entry, including one a recovery or release opened while it ran. - Revyl carries its platform in the lease request, names its cap `REVYL_ALLOCATION_TIMEOUT_MS` with a margin over start plus readiness, and builds its driver with `createRevylLeaseDriver`. - ADR 0007 records the inventory and interactor effect of the fence, the `provider-lease-releasing` reason and Revyl's in-process digest change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(providers): name each lease refusal reason and scope the digest warning ADR 0007 names `provider-lease-releasing` for a lease that a release, recovery or failed start is ending and `runtime-shutting-down` after shutdown began, and says shutdown re-drains leases a recovery opens. The digest comment covers only derived instances and the inputs that change them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(revyl): record only Revyl API calls in the fixture backend The fixture listens on an ephemeral localhost port that any process on the host can reach, and a stray `GET /` from one made the exact-path assertion in the lease-start test fail under host load. Requests outside `/api/` are answered 404 and not recorded as the runtime's calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(revyl): keep a shared start running when its first caller hangs up The Revyl driver passed the starting caller's signal into the start, so that caller's cancellation ended readiness for every caller that joined it. The start now gets only the caller's deadline; the table stops the device once it is ready if no caller still wants it. A lease without a Revyl platform is refused with a typed error, the fixture ignores only a bare `GET /` probe, and the table documents which waits it bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(revyl): record a fixture request only when it carries the API key Every runtime request carries the key, so a request without one is another host process probing the fixture's ephemeral port. It is answered 404 and not recorded; any runtime request is recorded at any path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(providers): run a shared start for every waiter, and type the principal Coordinator follow-up on the Apex Sentinel findings at d4151ce: - The start no longer sees any single caller's signal or deadline. It runs under the provider's start timeout, and the table aborts the start's own signal only when no caller still waits. A first caller that hangs up or runs out of time ends only its own wait; a joiner still waiting gets the device. Revyl passes that signal to its start. - `providerRuntimeInstance(provider, principal)` takes an ordered list of named facts. An unset fact is the same account as an absent one; an empty or repeated name, or a principal with no value, is refused. The serialization is unchanged, so Limrun's and Revyl's pinned digests stay byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): keep every named rental owed, and check budgets at delivery Codex's final check at fc94661 reproduced three table bugs: - A rental published before a stop of an earlier one confirmed could be stopped and let the entry go while the earlier stop later failed. The entry now keeps every rental it was told of, stops them one at a time in the order named, and forgets the lease only once all are confirmed. - A start that answered between a teardown deciding `pending` and the teardown being cleared joined that finished teardown, so nothing stopped its device. A late answer or publication now runs a teardown after the one in flight instead of joining it. - A caller whose deadline passed before its timer ran, or that reused a ready session after hanging up, was still handed the session. Each caller's own signal and deadline are checked at delivery; the session stays for callers within budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): fence an abandoned start, and harden the principal - Abandoning a start nobody waits for now also fences the lease and runs its teardown, so a caller that arrives later is refused instead of joining a start the provider is giving up; once its rental is stopped, a new allocation starts afresh. - `join` listens on the caller's signal directly, so a long-lived caller signal keeps no link to the entry after the wait. - `providerRuntimeInstance` builds its facts on a null-prototype object, refuses integer-like field names that JSON would reorder, and states its synchronous scrypt cost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(providers): name Revyl's readiness margin and correct the scrypt cost The allocation cap's 10 s margin is a named constant with its reason, the scrypt note gives the real 16 MiB (64 MiB is only `maxmem`), and a table test settles instead of sleeping 40 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): report every rental a teardown leaves rented A teardown returned at the first rental whose stop failed, so a second rental of the same lease was neither stopped nor named in the cleanup report. A stop that settled unconfirmed no longer holds later rentals back; only a stop still in flight does, so stops never overtake one another. Each teardown tries each rental once and reports every one that stays rented. Only one follow-up teardown is queued per entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): report rentals that wait behind a stop still in flight When a stop was still in flight, the rentals named after it were left untried, as intended, but also left out of the cleanup report. The pending report now names every rental still rented, with the ones waiting behind the in-flight stop given that as their cause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(providers): name the scrypt parameters and record what an unauthenticated probe is - scrypt's block size and parallelism are named beside its cost and key length, under one comment saying all four feed the durable digest. - `RentalRecord.stopping` documents that it is the fence that keeps a later rental's stop from overtaking an in-flight one. - The Revyl fixture ignores only a request with neither the API key nor a path under Revyl's API, so a runtime call that drops the key is still recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(provision-kit): drive each lease through one explicit phase Each lease entry now has a generation and one phase, `starting`, `ready` or `ending`, replacing the flags that encoded it before. Only `starting` → `ready` and any phase → `ending` happen, each in one synchronous step. After every await, the table checks that the entry is still its lease's current generation and in the expected phase before it hands out a session or forgets the lease, so a stale start answer only ever stops its device. Shutdown is a synchronous fence: an allocation, or a recovery of a lease with no entry, is refused with `shuttingDown` once it begins, so the drain ends every entry in one pass. ADR 0007 states the fence, and the Revyl troubleshooting guide names `provider-lease-releasing`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(provision-kit): sweep seeded interleavings of the lease table 400 seeded runs issue allocations, aborts, releases, recoveries, one shutdown and time passing in bursts, and answer every start and stop in a seeded order. Each run checks that no session is handed out after its lease ended or its device stopped, that no device the table took on is left running or unreported when shutdown settles, that at most one stop of a device is in flight, and that a failed stop is retried until the final drain confirms it. A failing seed names itself for replay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(provision-kit): name the interleaving sweep for its module's aspect R84 colocation reads a test named <module>-<aspect>.test.ts as its module's; a dotted aspect names no module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): look for new rentals before a teardown forgets its lease The seeded sweep, extended to starts that publish their device early, found a teardown that awaited its stops, found none owed, and then forgot the lease even though the start had recorded a rental meanwhile: shutdown resolved with that device still rented (seed 92). The teardown now re-checks synchronously for an unnamed or unstopped rental before it forgets the lease, and loops if one appeared. The sweep now publishes some devices before their start answers, does not rent a published device twice, and accepts a shutdown report that names a still-unnamed rental by its lease. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(revyl): retry a released open with open, not connect `connect` saves the profile and rents nothing; `open` allocates the new lease that rents a fresh device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(revyl): point a released open at open, and state the 370 s ceiling The `provider-lease-releasing` hint now names `open`, as the guide does, since `connect` rents nothing. The guide states that an `open` with no deadline stops waiting after 370 s, and Revyl's instance wrapper keeps the reason its principal is digested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(adr): say that a release only joins a lease entry that already exists Shutdown's drain needs one pass because nothing registers an entry once it begins: allocation and entryless recovery are refused, and a release only joins an existing entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(providers): stop a released Revyl start without waiting out readiness A release, recovery or shutdown that took over a lease mid-start waited for the start to finish readiness before it could stop the device, up to Revyl's whole allocation timeout. The table now aborts the start's own signal as soon as a teardown takes the lease, since no caller can receive that device any more, and reports the lease's end, not the aborted start, to callers still waiting. Revyl publishes its session through `control.rented` the moment `start_device` answers with it, the first moment Revyl holds it, and a release of a session that never became ready still reports its run id. New test: a release during a stalled readiness stops the device at once. The two table tests the seeded sweep fully covers are folded into it to keep the change within its budget, and Revyl's lease tests share one refusal helper to stay within the file's line cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(providers): let teardown alone abort a start, and pin readiness aborts Teardown is now the one place that aborts a start's signal: when a release, recovery, shutdown, failed start or the last waiter leaving ends a lease whose start has not answered. `stillWanted` is false for an ending entry, so nothing can count a fenced lease's waiters, and the last-waiter path only ends the entry. Two Revyl regressions pin that the abort reaches readiness: a sole caller that hangs up during readiness has its session stopped before the delayed health answer, and a release that lands while the first caller still waits stops the session and refuses that caller before health answers. Both fail if the teardown abort, or Revyl's `signal: control.signal`, is removed. The table test those cover is folded in to keep the change within its budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): stop a late rental even when a driver's error factory throws When a late start answer lands on an ending lease, the table builds the waiters' error with the driver's `releasing()` before it runs the follow-up teardown that stops the late rental. A driver whose factory threw skipped that teardown and left the device rented. The failure is now built inside a guard, and the follow-up teardown always runs. Tests: a start that fails 50 ms in answers its caller at once, stops what it rented and frees the lease for a new allocation; and a driver whose `releasing()` throws still has a late answer's device stopped. The table test that the seeded sweep's stop-overlap check covers is folded in, and the table's type docs are condensed to stay within the change's budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(provision-kit): name the lease's end in every refusal a caller gets A caller that gave up in `join()` while a release, recovery or drain was ending its lease got its own `gaveUp` reason (`request_canceled` or `provider-not-ready`) instead of `provider-lease-releasing`, and lost `runtime-shutting-down` during shutdown. One private `refusal(lease, entry, fallback)` now decides every refusal: shutdown first, then a lease that is ending or replaced, else the site's own reason. Allocate, delivery, the start's failure and join's give-up all go through it. A start that failed on its own still reports its own error. Join builds its refusal before the caller leaves, so a caller's own departure is never read as someone else's release, and a throwing driver factory still settles the caller. Regression (from #4169): a caller that gives up while a release is in flight is told the lease is being released, and exactly one `/stop` is sent. It fails at dce5c56 with `request_canceled`. The table notes that it does not bound `findByLease`, and the failed-start test schedules its rejection on the next tick instead of asserting a wall-clock bound. Test comments that restated their names are trimmed to keep the change within its budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Revyl kept handing out devices after release started or failed. One lease owner now makes starting, ready, stopping, stop-failed and closed explicit. It retires admission synchronously, shares bounded disposal, retains uncertain cleanup for retry, and disposes late creates once.
Seven files; scope stays within Revyl lifecycle, adjacent tests and its guide. Related: #4102; follows #4130 and #4141. Eager closure is preserved.
Final review round derives disposal's stop budget from the HTTP client's full retry budget plus a margin, preserves typed retirement when a waiting caller gives up, and caches ready-session reuse. Field and callback-signature nits are explained in their threads.
Validation
Base
a0e7c7dc239922bf93adaceff20a2f7e78579146; heada8ebe1f45f164249fc8d4a04221000ab1dd9d180; previous reviewed head240b05012f6895ea90406d974c80102b94745903.pnpm check:affected --run --head HEADpassed: receiptrun-trpcM0,exactCommit=true,checkoutChanged=false; 16,972 unit tests passed, one skipped. Full-repository format passed.Invalid API key: one rejected create, zero sessions created. Command/release/listing were blocked. Host cleanup had no pending/unknown releases; private-state daemon stop reported known cleanup with no warnings. No new live run this round.