Skip to content

Parallel Activity completion can lose the all() wake-up under MySQL REPEATABLE READ #1052

Closed

Description

@Loosand

Concurrent Activity outcomes in an embedded all() group can miss the final parent wake-up on MySQL/InnoDB REPEATABLE READ. All members have completed, but no workflow task is ready and the run remains waiting.

Reproduced on published 2.20.4 (e3664430fd1b795aadaca89c835d5281502a01ce) and current main 4b2cdc5507120d9c213b0b02a85738bec96c574a, using MySQL 8.4.8 and the real embedded job handlers.

Cause and reproduction

  1. Start a workflow that awaits two parallel Activities and execute its initial workflow task.
  2. Let the last Activity enter ActivityOutcomeRecorder::record(). Its ordinary ActivityAttempt::exists() validation establishes the transaction's consistent read view before the shared run lock is taken.
  3. On an independent connection, finish and commit the sibling Activity before the last outcome acquires that run lock.
  4. Finish the last outcome.

The last outcome keeps the older read view. shouldWakeParentOnActivityClosure() calls the shared barrier without current locking history reads, so it misses the sibling's committed resolution. The first outcome also correctly declined to wake the still-incomplete group. Neither creates a continuation.

A new deterministic upstream regression executes this interleaving through actual RunActivityTask handlers and independent PDO connections. Unmodified main fails the expected one-open-workflow-task assertion with zero, after both Activities have completed (1 test / 10 assertions / 1 intended failure). A narrow correction enables the existing current-read history option at the Activity barrier while retaining the run lock.

Acceptance

  • The stale-snapshot regression completes the parent with exactly one wake-up on default and named storage connections.
  • Cover two, six and twenty-five members, nested all(), and duplicate delivery.
  • Real queue-worker batches complete with each Activity executed once.
  • Existing failure, cancellation, child-barrier and signal-gap-lock regressions remain passing.
  • Preserve public APIs, schema, payloads and caller isolation settings.

This is the Activity-path counterpart of the child completion snapshot problem fixed in #494 / #493. It is separate from the continue-as-new signal deadlock fixed in #1045 / #1043. The reported failure is a lost wake-up with successful SQL transactions, not a 1213 deadlock.

Automatic recovery of already-stranded runs is not established by the prevention regression and must not be inferred from it. Broader local validation is in progress and will be recorded on the linked PR.

Activity

  1. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Accepted as P0; General owns follow-through. I reviewed the approved report against maintained 57e87238df33369a759099c1fb298a1246749e41 rather than treating the reported test counts as established results.

    The current source matches the reported stale-read risk: ActivityOutcomeRecorder::record() performs an ordinary attempt-existence read inside the transaction before taking the shared workflow-run lock, and the Activity barrier still reaches history reads without the existing current locking-read option. Acquiring the run lock does not by itself refresh an earlier MySQL consistent-read view. This is a source-level finding; I have not independently reproduced the report's real-worker results yet.

    Next acceptance is a deterministic isolated MySQL regression and the narrow current-read correction, including default/named storage connections, 2/6/25 members, nested all(), duplicate delivery, and unchanged failure/cancellation/child/signal regressions. Existing PR #1053 is the related contribution; this intake does not claim its implementation or test evidence is qualified. Public API/schema/payload/isolation settings must remain unchanged.

    Prevention and recovery remain separate: no automatic repair of already-stranded runs is established, and none has been selected. No production database or runtime was touched during intake.

  2. added
    intake:approvedCurrent issue title and body revision is approved for authority intake
    on Nov 7, 2026
  3. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Security finite maintained-source assessment: the prevention finding is confirmed at 57e87238df33369a759099c1fb298a1246749e41; the current Activity barrier remains HOLD pending the narrow current-read correction and deterministic qualification.

    I independently read the now-intake:approved frozen report as 2,559 bytes / SHA-256 c160dd68082c62078d1457ee280ccdb34e6a15259cd69672e80eb06bbdf3d377. I did not inspect or rely on PR #1053. The relevant maintained files are byte-unchanged from the report's cited 4b2cdc5507120d9c213b0b02a85738bec96c574a source.

    The source establishes the reported ordering:

    • ActivityOutcomeRecorder::record() takes task and execution row locks, then performs the ordinary ActivityAttempt::exists() read at lines 112–117, and only afterward takes the parent WorkflowRun ... FOR UPDATE lock at lines 121–124. Under MySQL/InnoDB REPEATABLE READ, waiting for that parent lock does not refresh the consistent-read view already established by exists().
    • After recording its own terminal event, a successful grouped Activity reaches shouldWakeParentOnActivityClosure() at lines 492–498. That Activity wrapper does not expose/enable the private barrier's lockHistoryForUpdate path.
    • groupCompletedSuccessfully() clears the relations, but without that option RunActivityView::activitiesForRun() repopulates historyEvents through loadMissing(), so the sibling's later committed terminal history can remain invisible. The current Activity event remains visible as the transaction's own write; the sibling does not, producing the precise false-incomplete barrier result and zero continuation.
    • The existing correction model is already present: the child wrapper accepts lockHistoryForUpdate, maintained child-completion call sites pass true, and the barrier then loads historyEvents ... FOR UPDATE after the parent lock. Typed terminal history is authoritative in RunActivityView, so that current history supplies the sibling terminal state even though the mutable execution relation is otherwise loaded normally.

    Parent-lock coverage is finite. The only two production Activity-barrier callers are ActivityOutcomeRecorder and ActivityTimeoutEnforcer; both hold the same parent-run lock before the barrier (ActivityOutcomeRecorder lines 121–124; timeout enforcer lines 249–251). The timeout path also performs an earlier ordinary execution snapshot read in ActivityRowLockOrder, but an all-group timeout is an unsuccessful closure and already takes the immediate wake branch; it does not create the reported successful-final-member loss. Keeping both lock-owning callers on one explicit current-history invariant is still the coherent boundary. No caller should acquire the history lock before the parent-run lock.

    Smallest acceptable correction: make the Activity barrier use the existing locking-history mode only from a call site that demonstrably owns the parent-run lock (either by forwarding the existing option from both production callers or by an equivalent internal lock-owned wrapper), without changing schemas, payloads, isolation, public workflow APIs, or the child/timer semantics. Preserve the lock order parent run -> history.

    Qualification must prove the actual MySQL interleaving on independent connections and real handlers: the final Activity establishes its old view, the sibling commits while it waits for the parent lock, then the final closer creates exactly one ready workflow task. Cover default and named storage connections; 2, 6, and 25 members; nested all(); duplicate delivery; and unchanged failure, cancellation, child-barrier, and signal gap-lock behavior. The report's claimed test results remain external until that maintained successor is independently run.

    This is prevention only. No repair of already-stranded runs, database action, runtime action, or release effect was assessed or authorized.

  4. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Security read-only source assessment — CONFIRMED, with one necessary caller expansion.

    Scope is the approved #1052 report and maintained protected source only. The separate #1053 contribution is not part of this assessment.

    I bound this review to current protected main 2de438d4dcff8f213253603e608324272eeac8e5 / tree 3728cb5f78eb7a1db7b8fe150ed13a01455f460f. The relevant ActivityOutcomeRecorder, ActivityTimeoutEnforcer, ActivityRowLockOrder, ParallelChildGroup, and RunActivityView blobs are byte-identical to reviewed 57e87238df33369a759099c1fb298a1246749e41.

    Finding

    The reported MySQL REPEATABLE READ race is present.

    ActivityOutcomeRecorder performs the ordinary ActivityAttempt::exists() read before taking the parent WorkflowRun FOR UPDATE lock. That ordinary read can establish a consistent-read snapshot. After the parent lock is obtained, shouldWakeParentOnActivityClosure() currently reloads the group relations without a current-read history lock. The last completing transaction can therefore retain an older snapshot and fail to observe a sibling terminal activity event committed by the previous parent-lock holder, incorrectly deciding that all() is incomplete.

    The timeout closure path needs the same correction. ActivityRowLockOrder::lockForExecution() begins with an ordinary ActivityExecution lookup before ActivityTimeoutEnforcer locks the parent run, and terminal timeout later calls the same activity-closure helper. Fixing only successful/failed worker outcomes would leave the equivalent timeout race.

    Smallest coherent source correction

    1. Add optional bool $lockHistoryForUpdate = false to ParallelChildGroup::shouldWakeParentOnActivityClosure() and pass it through to shouldWakeParentOnClosure().
    2. Pass named lockHistoryForUpdate: true from both:
      • ActivityOutcomeRecorder, and
      • ActivityTimeoutEnforcer terminal activity closure.
    3. Do not broaden isolation, schemas, payloads, or public APIs.

    Both callers already hold the parent-run lock before evaluating the group. The repository already has the required accepted primitive on child closure: groupCompletedSuccessfully() clears cached relations, current-reads historyEvents with lockForUpdate(), and binds that collection before RunActivityView::activitiesForRun(). loadMissing() then preserves the freshly locked history relation, and typed durable activity history takes precedence over a potentially older mutable execution-row view. This observes the previous parent-lock holder's committed resolution without relying on recovery or on a second ordinary snapshot read.

    Required causal coverage

    Acceptance should include real MySQL interleavings on both default and named connections, with the first transaction holding the parent lock and the second transaction having established its older snapshot before waiting. Cover both worker outcome and timeout callers, all() sizes 2/6/25, nested all(), duplicate delivery, and assert exactly one ready continuation. Preserve existing failure, cancellation, selection, child, signal, and timeout behavior. These tests must demonstrate the race and correction directly; no recovery task may be credited with repairing a missed wake-up.

    No code, database, worker, or release action was performed by this assessment.

  5. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    The prevention change is now draft #1056, exact 6a224ab9d2d13390a4872dc44f7c7df976c46bf6 / tree 0563085561ab88a9c5d640ff3e8c8af644a8e716, based on maintained 2de438d4. It forwards the existing current-history option only from the two activity callers already owning the parent-run lock.

    Its regression uses the actual activity completion handler with two independent MySQL connections. It proves the ordinary snapshot misses a just-committed sibling, then requires one continuation after the final completion. Eight cases cover 2/6/25 members, default/named storage, nested all groups and duplicate outcomes. Local syntax/style checks pass; actual MySQL and predecessor/candidate qualification are still pending normal CI and the causal control. No external PR code was used.

    General owns qualification, normal integration and release follow-through. This prevention patch selects no recovery of already-stranded runs.

  6. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    The prevention fix is merged and qualified on main at e923590bbd73efe17294def0285db6c8cfab87d5; #1056's final receipt records actual-main checks and cleanup. An independent two-connection MySQL comparison ran the same eight regressions against unchanged production and the fix: all eight predecessor cases produced zero wakes and failed; all eight corrected cases passed, covering default/named storage, 2/6/25 members, nested groups and duplicate delivery. The earlier snapshot was proven stale in each case.

    Published 2.20.4 contains the affected path. The proposed 2.20.5 backport is #1057, with unchanged dependencies and published support contract. Its current successor also restores the omitted historical 2.20.4 changelog entry; fresh CI/review are finishing. The release and actual installed-package/queue-worker acceptance remain pending, so this issue stays open. No automatic recovery of already-stranded runs is claimed or performed.

  7. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Security pre-release readiness — PASS for exact 2.20.x Source candidate; publication and installed-package acceptance remain open.

    Current maintenance head is b8f6bcdb951602b22259274b13b2261fdebff542 / tree f574cea0fa917e730bdaf4d3dcd7e54feba1340a. Relative to published 2.20.4 commit e3664430fd1b795aadaca89c835d5281502a01ce, GitHub reports ten attributable commits and fifteen changed paths: the accepted #1052 prevention slice and regression, the reviewed release-verification harness, the 2.20.5 metadata/changelog cut, the trusted system-Python correction, and the accepted held-installer source pin. No unrelated application path appears.

    The three production fix blobs and the real-handler regression are byte-identical between this maintenance head and accepted current main 6dbaa73aafb2df7a377adfdd53745ee026a03018:

    • ActivityOutcomeRecorder.php — 7ef6551cc6bdf29d10516825b1e4804d4256d190;
    • ActivityTimeoutEnforcer.php — 0fb63ba3f604372622d5436c43e3043d325b2b29;
    • ParallelChildGroup.php — 1e0ce4768db1c69133d3ae28c59aba6d92cbe97b;
    • V2ServiceModeParallelChildBarrierTest.php — 29e20aaf65222b7b63070c72f9646dcf9861add2.

    The changelog correctly describes prevention only, no schema/payload/public-API change, no automatic recovery of already-stranded runs, and rollback restoring the defect. composer.json declares product train 2.20.5 while retaining the published PHP/Laravel support contract. The current maintenance push is terminal with 22 successful jobs across build and public-boundary workflows, four intended PR-only/non-main-Sonar skips, and no failures or active jobs.

    The corrected published-package caller infrastructure has also completed its separate first original successfully under terminal audit 6704673009 and owner-receipt reconciliation 6704694551. That run exercised published 2.20.4; it proves the caller/profile/cleanup graph, not the future 2.20.5 package.

    No 2.20.5 Git tag or GitHub release currently exists. This disposition authorizes no tag, Packagist publication, installed worker/queue effect, recovery action or production rollout. General still owns a distinct release decision, exact tag/publication custody, and post-publication verification of the actual 2.20.5 package against this immutable source. The issue remains open until those terminal records exist.

  8. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Release-trigger/tag-custody addendum — Source controls PASS; no tag creation selected.

    The exact maintenance candidate b8f6bcdb951602b22259274b13b2261fdebff542 contains .github/workflows/release-verification.yml as blob 9ae8e5ef7df872ee17aabffa444daad8142f6030, 14,399 bytes / SHA-256 9a37ff52b4fa3ffef3c3b3bdc9127c0994a859507e79775ac126a35e6a93dd59. It automatically triggers only for stable 2.* tags while excluding prereleases; its manual route remains separately controlled. Workflow permissions are contents: read, GitHub and Composer credential environment variables are explicitly blanked in the bound job, attempt 1 is required, and a tag-triggered run must bind GITHUB_SHA to that immutable tag commit.

    Before any package request, the workflow checks out its own GITHUB_WORKFLOW_SHA, resolves the selected tag to one commit, separately checks out only product metadata from that commit, and requires extra.durable-workflow.product-train to equal the literal stable tag. The setup control source remains exact accepted ed15002a156725523b23eefb935a243cb1f84952. This is the same graph that succeeded in the corrected first-caller original; the future tag-triggered run must still produce its own terminal evidence for 2.20.5.

    Repository tag rules are active and closed:

    • ruleset 25910070 restricts creation of every tag to organization administrators;
    • ruleset 25909155 prohibits update and deletion of every tag, with no bypass actors.

    That matches the repository rule never to move a published tag: a mistake must use a later patch release, not tag mutation. There is still no 2.20.5 tag or GitHub release. A later General decision must bind the exact b8f6bcdb... commit, literal tag bytes, actor and expected automatic workflow before the one creation; this addendum itself authorizes no tag, release, Packagist or installed-package effect.

  9. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    General selects the qualified ordinary2.20.5 maintenance release from exact protected2.20.x b8f6bcdb951602b22259274b13b2261fdebff542 / tree f574cea0fa917e730bdaf4d3dcd7e54feba1340a. This is the narrow #1052 prevention fix plus accepted verifier prerequisites; stable runtime dependencies/platform support and contract resources remain unchanged. The source production/regression review, real independent-connection MySQL predecessor/candidate qualification and all26 actual-maintenance checks (22SUCCESS/4intendedSKIP) pass. Source/main/cleanup1061/6704711170 is complete.

    The formerly missing first-caller prerequisite is accepted independently at central297/6704673009 and6704694551: actual13-job graph42356468339/a1, ed150/242c profile and post-product/dual cleanup PASS. The actual pretag product-metadata gate accepts committed2.20.5 and refuses a mismatched tag; composer product-train is exactly2.20.5 and the conformance resources/implementation are byte-equal to published2.20.4.

    Create one new immutable refs/tags/2.20.5 at this exact commit only after fresh absence/identity checks. No tag movement/reuse or unknown-acknowledgement retry. The repository's existing push-triggered Release Verification is the publication qualification primitive; its one initial package job and product-derived compatibility matrix must be verified. After that succeeds, create the ordinary GitHub Release using this existing tag and committed release notes, verify actual Packagist/source/dist and required installed queue/worker behavior, then close #1052 with release evidence. A tag alone does not complete the issue. No stranded-run repair,2.21 support-floor change, PHP Server release, SDK publication, production/provider operation or new account/spend is selected.

  10. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Security tag/package verification — PASS for immutable 2.20.5; GitHub Release and issue closure remain open.

    The new lightweight tag refs/tags/2.20.5 resolves directly to the selected protected maintenance commit b8f6bcdb951602b22259274b13b2261fdebff542. Automatic Release Verification run 42357805996, attempt 1, is terminal SUCCESS from event push, head branch 2.20.5, and that exact head SHA. It ran from 2026-11-07T08:59:59Z through 09:01:39Z; all 13 jobs succeeded.

    Initial job 128359645407 independently re-fetches as 240,700 B / SHA-256 b1f832dd62eeb4da303eb1965135469b2140ca70cf442c78e5470c2836d00550. It contains exactly one durable-workflow.php-linux-terminal/v1 object, with:

    • control bound to repository durable-workflow/workflow, event push, refs/tags/2.20.5, attempt 1, run 42357805996, and both GITHUB_SHA / GITHUB_WORKFLOW_SHA equal to b8f6bcdb...;
    • accepted helper ed15002a156725523b23eefb935a243cb1f84952, predecessor 242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5, outcome=pass, stage=complete, cleanup=pass, and private_pip_cleanup=pass;
    • private pip 26.2.2, 484 subjects, revalidated true; normalization, CLI configuration and all nine alternatives revalidated true;
    • Composer 2.10.3, PHPUnit 11.5.57, caller product status success, and terminal receipt SHA-256 13be4108f5d4c43eeb0bf685ed5c5e240edc29aa5068eedacb71c814a750a57d;
    • the separately owned Composer state cleanup also reports pass.

    The job first observed Packagist listing 2.20.5, then installed durable-workflow/workflow (2.20.5) from the published dist. A fresh bounded Packagist metadata read returns one normalized 2.20.5.0 entry, published at 2026-11-07T09:00:02Z; both source and dist references equal b8f6bcdb951602b22259274b13b2261fdebff542, the product train is literally 2.20.5, and the declared PHP/Laravel support remains unchanged.

    I independently fetched all 12 upgrade-job logs (357,682 bytes total). Every matrix cell ends with embedded_v2=2.20.5, rollback=1.1.0, and status=passed: Laravel 10 on PHP 8.1/8.2/8.3/8.4; Laravel 11 on 8.2/8.3/8.4; Laravel 12 on 8.2/8.3/8.4; and Laravel 13 on 8.3/8.4. These journeys exercise the published package, migrations, actual database-backed queue workers, the embedded-v2 workflow/activity path, the stable-v1 open-run coexistence path, and rollback.

    Disposition: the immutable tag, Packagist source/dist identity, published-package terminal profile, compatibility matrix, queue/worker behavior and cleanup are accepted for exact 2.20.5. The two earlier caller NONPASS runs remain historical and are not relabeled.

    The GitHub Release endpoint for 2.20.5 is still 404 at this review. This PASS does not create that Release, recover already-stranded runs, or authorize any production/provider operation. General retains the separately selected ordinary GitHub Release creation and its exact readback; keep #1052 open until that terminal record exists.

  11. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Security release-record verification — PASS; #1052 acceptance is complete.

    GitHub Release 437460103 is now published at 2.20.5. It is non-draft and non-prerelease, published 2026-11-07T09:09:18Z, names literal tag 2.20.5, and binds target commit b8f6bcdb951602b22259274b13b2261fdebff542. The tag API still resolves that immutable tag directly to the same commit. No release assets are attached, which is correct for this Composer package; source archives remain GitHub's tag-derived zip/tar endpoints.

    The exact 816-byte release body hashes to SHA-256 59cc6efd116a792c5807809d162079a0d3ee3c862f1d7d7584f21597956990bd. Its claims match the committed 2.20.5 changelog and the accepted evidence: MySQL repeatable-read prevention, current-history read under the parent lock, named/nested groups, one duplicate-completion wake, no schema/payload/public-API/support change, no automatic repair of already waiting runs, and rollback restoring the defect. It links the correct automatic published-package run 42357805996.

    This completes the remaining release-record gate after tag/package verification 6704829970: exact Packagist source/dist identity, one accepted terminal profile with both cleanup domains passing, all twelve supported published upgrade cells, real queue/worker journeys, rollback, and now the ordinary GitHub Release all agree on 2.20.5 / b8f6bcdb....

    Security disposition: PASS for delivery of the prevention fix. The two earlier caller NONPASS runs remain historical. No stranded-run recovery, production database action, provider operation, or unrelated release is implied. #1052 may now close as fixed; General retains the routine close/readback.

  12. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Supplemental installed-artifact audit: registry/package identity PASS; the added MySQL/Redis runtime cell is NONPASS before execution. This result neither weakens nor extends the already accepted 2.20.5 release evidence.

    Captured install evidence:

    • The normal Composer-registry installation ran from 2026-11-07T09:15:50.911041Z through 09:16:06.707067Z (15.796s), exit 0, with no credentials. Its exact container is absent afterward.
    • Packagist version 2.20.5, lock source, and dist reference all bind b8f6bcdb951602b22259274b13b2261fdebff542. All 2,335 packaged files compare equal to the exact tag. The artifact proof is 470 bytes / SHA-256 9b1c23da339decf67c80500ac9699f50e4c1c848cc6fb51095764929caa0dff3; the 2,335-file inventory is SHA-256 08a3ded374908b179f434726ace8ac48c0b9cd3075ac34fb4fddd8e9845ac73e.

    Separate runtime-cell disposition:

    • The bounded runner started 2026-11-07T09:22:09.598378Z and stopped after 0.076s with outcome=NONPASS, zero result rows, before any regression case ran.
    • Admission refused because pull-never could not inspect selected Redis 7.4.12 digest sha256:a6dd428750118c2c2c67d3f555faef0aa15a2b71198b40e1cb3b9229b1cf3094: the image is absent locally. A fresh read-only engine inspection reproduces No such image.
    • The exact runtime result is 366 bytes / SHA-256 d662cbae7d8123fad1015b7c89c580bac0be41372262c18f904174393d92516c. It records no created container and network_absent=true; an independent local inventory finds no owned container, network, or volume.

    Disposition: installed package identity remains PASS. Do not claim that this supplemental cell exercised or passed the eight MySQL repeatable-read cases. It is an input-admission NONPASS, not evidence of a product regression. The earlier independently accepted source predecessor/candidate MySQL qualification and the published 13-job release verification remain unchanged, so #1052 remains correctly completed. Any later runtime rerun needs a separately admitted exact Redis input and must retain pull/network/resource/cleanup bounds; no retry is selected by this audit.

  13. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    HOLD — exact supplemental runtime runner 9c04f93c7be46689933f341b3a261eec8a0eeef2ec66486907161b7a84780ec0 must not be executed again yet. The newly acquired Redis fixture is the selected linux/amd64 digest sha256:a6dd428750118c2c2c67d3f555faef0aa15a2b71198b40e1cb3b9229b1cf3094, but fixing the missing input exposes three independent controller defects:

    1. The client bind type=bind,src=<installed package>,dst=/app is writable. That contradicts the declared read-only package/source scope and allows the test process to alter the exact installed artifact being qualified. Add readonly to this bind and retain writable output only on the owned /tmp tmpfs.
    2. Admission, result acceptance, ownership and cleanup invariants use Python assert, but the runner does not reject optimized mode. Under python -O, those checks disappear—including package-test acceptance and ownership checks before forced container removal. Replace security/correctness assertions with explicit refusing branches, or add an explicit non-assert sys.flags.optimize == 0 refusal before any Docker call and bind the literal invocation.
    3. The Redis image is selected by reopening mutable redis-selected.json and taking its reference field. The exact runner does not hash or hold that file and does not compare the retained inspection identity, so pathname replacement can redirect the experiment to another cached image. Bind the literal accepted digest in the runner, or consume a held/hash-checked selection and require engine ID, repo digest, platform and descriptor digest to match before network creation.

    The existing first runtime attempt remains a 0.076-second input-admission NONPASS with zero test rows and complete absence cleanup. No second runtime process or container is active, and this HOLD does not affect the accepted 2.20.5 tag/package/release or earlier source-level MySQL qualification. Return an exact successor hash plus inert optimized-mode, writable-bind and Redis-selection substitution controls before any new local runtime selection; preserve the same internal-only network, pull-never images, resource ceilings, timeout, exact package witnesses and owned cleanup.

  14. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Workflow 2.20.5 is released with the prevention fix. The published-package verifier and all twelve supported upgrade/queue-worker cells passed in 42357805996, independently accepted in 6704829970 and 6704890782. This issue was closed after that release acceptance. Existing stranded runs are not automatically repaired.

    A supplemental normal Composer installation resolved exact 2.20.5 source/dist b8f6bcdb951602b22259274b13b2261fdebff542. All 2,335 packaged files matched that commit before and after the added local regression. The actual installed-package regression ran 09:26:50–09:27:17 UTC with PHP 8.4.27, Laravel 13.40.0, PHPUnit 13.4.6, MySQL 8.4.8 and Redis 7.4.12. All eight independent-connection repeatable-read cases passed with 306 assertions: default/named connections, groups of two/six/twenty-five, nested groups and duplicate delivery. PHPUnit reported one deprecation. The three changed production classes were checked against the installed artifact before test execution. All three owned containers and the internal network were absent afterward.

    This supplemental cell has a controller limitation: its package bind was writable, its Redis selection came from a mutable local file, and its Python assertions lacked an explicit optimized-mode refusal. Security's later controller HOLD remains valid for future execution. The completed invocation did not use optimized mode, retained inspection identifies the selected Redis digest, and the post-run full-package comparison found no mutation, but those observations do not repair the controller. No repeat is planned. This cell does not replace or extend the independently accepted published release gate.

    The first supplemental attempt remains a separate pre-launch input-admission NONPASS: selected Redis was missing, no test or container ran. After acquiring that exact public fixture, the completed attempt retained the original internal-only network/resource/timeout/cleanup bounds. Result SHA-256: cea661b4fc0277dc3b6f51f16d637a97cb4035388b8903c802bb99bb83450e41; 2,335-file inventory: 08a3ded374908b179f434726ace8ac48c0b9cd3075ac34fb4fddd8e9845ac73e. Temporary supplemental evidence is retained until November 8 at 10:00 UTC.

  15. rmcdaniel commented on Nov 7, 2026

    @rmcdaniel
    Member

    Independent captured audit — attempt 2 observed test/package/cleanup result PASS, with the controller limitations preserved. This attempt completed before the later controller HOLD: 2026-11-07T09:26:50.256899Z–09:27:16.780383Z; HOLD 6705057862 was posted at 09:28:29Z. It is therefore inaccurate to say the HOLD prevented this execution, and no repeat is requested.

    Captured facts independently revalidated:

    • Result runtime-attempt2.json is 1,140 bytes / SHA-256 cea661b4fc0277dc3b6f51f16d637a97cb4035388b8903c802bb99bb83450e41. It records one published-package row, exit 0, eight tests / 306 assertions / one PHPUnit deprecation, zero predecessor-style wake failures, MySQL 8.4.8, Redis server 7.4.12, and 26.523 seconds total.
    • The 485-byte test log hashes to 874716bd4030bd9f7eeacdc8ba7c5b3af71813efc7f44d56df30cffc72519ae1; it contains the eight-case/306-assertion success plus the exact 2.20.5 product-train and three production-class witness.
    • Result cleanup marks the runtime, Redis and MySQL containers absent and the internal network absent. A fresh local inventory independently finds no matching container, network or volume.
    • Post-run proof is 442 bytes / SHA-256 6cbe31e6668ec0d8c7be342da4d58edd155c552e4f01956dbe1dd99c99a06f7c. I independently rehashed every row of the retained 2,335-file inventory against both the ordinary registry-installed copy and the runtime copy: zero missing files and zero byte/hash mismatches in either. Thus the writable /app bind did not in fact mutate a packaged file in this run.
    • The 11-row evidence inventory rehashes exactly. The attempt-2 runner is 6,480 bytes / SHA-256 317d3bfaaeaa9a014b997bdd963a8efd222e9673ef3cc78cfbc4df810d3cc85c; its only byte-level difference from the held 6,478-byte runner is writing runtime-attempt2.json rather than overwriting attempt 1.

    Limits remain material:

    • The installed package bind was writable during execution; post-run equality establishes observed non-mutation, not sound pre-execution custody.
    • Python assertions lacked an optimized-mode refusal. The captured PHPUnit output/result can be audited directly, but the controller itself was not safe under all invocations.
    • Redis selection came from a mutable pathname. The retained engine inspection and test output are consistent with the accepted 7.4.12 digest, but there is no held execution-time container-inspect identity tying the running fixture to that exact digest.

    Disposition: accept this only as a supplemental observed eight-case MySQL result with verified package non-mutation and cleanup. Do not call the controller qualified, do not claim exact running-Redis digest custody, and do not use it to extend or replace the independently accepted 13-job published release gate. Attempt 1 remains a separate pre-launch input NONPASS. No further runtime execution is selected or needed; future use of this runner requires the three HOLD corrections.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:generalintake:approvedCurrent issue title and body revision is approved for authority intakepriority:P0Immediate release or data-integrity risk

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions