Skip to content

Fix repeated DST schedule occurrences for Core 2.15.13 - #974

Merged
rmcdaniel merged 7 commits into
mainfrom
fix/882-schedule-dst-fold
Nov 2, 2026
Merged

rmcdaniel merged 7 commits into
mainfrom
fix/882-schedule-dst-fold

Conversation

@rmcdaniel

@rmcdaniel rmcdaniel commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Daily schedules could admit two runs during the autumn clock change, and New York 30 2 * * * could also fire early inside the repeated hour. Fire selected local hours once at their first occurrence, retain elapsed-time behavior for equivalent all-hour fields, and preserve the existing spring-gap shift rule. The fallback scanner also retains fixed timezone offsets.

Prepare Core 2.15.13 with matching product-train metadata and changelog. The only delta from qualified e0cb5dbb to current d39b85a4 is that metadata; runtime, resources and tests are byte-identical. Strict Composer validation and the explicit source-release/SDK-tuple contract pass (1 test / 42 assertions).

Validation:

  • Full source qualification: unit/combined coverage, three databases, Laravel upgrade matrix, ECS/PHPStan, tracing and regression corpus pass. The sole first-attempt PostgreSQL worker-test infrastructure failure passed its scoped failed-job rerun; exact evidence and review.
  • Before-evidence on unchanged main reproduces double/early fires on MySQL and PostgreSQL. A fresh published 2.15.12 consumer independently reproduces three daily failures while three all-hour controls pass; 681 package source files remain pristine.
  • Independent checks cover equivalent all-hour expressions, fixed-offset fallback and the exact transition instant. The eight-language guide builds successfully and waits for qualified publication and installed acceptance.

General owns protected merge, the routine Core patch release, published-package/installed acceptance and documentation/native-contract follow-through. References #882, which remains open until those accepted delivery steps are complete. No PHP Server release is involved.

Delivered: protected merge 42be75605a6e4c35e0e2bade52086a62756c3e30, published Core 2.15.13, all 17 release-verification jobs passing, and unchanged installed six-case fixture passing after a Core-only update. Final issue receipt includes the deployed/verified guide in all eight languages. #882 is closed; Sample App adoption remains separate in durable-workflow/sample-app#290 / #291.

A daily cron schedule in a daylight-saving timezone started two runs on the
day clocks go back. The cron library returns both passes of a repeated
wall-clock time, and its hour matcher also accepts the repeated hour as the
following hour, so `30 2 * * *` fired early as well.

Cron occurrences whose hour field names specific hours now skip any
candidate in the second pass of a repeated wall-clock range. An hour field of
`*` keeps elapsed-time semantics so sub-hourly schedules fire through both
passes. Skipped spring-forward times keep firing once, moved forward by the
gap. The fallback scanner now walks absolute minutes with the same rules.

Refs #882

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Bounded review finding on exact head da1eb89a3a1b28e8613384b88f80b6a3e5689e10:

docs/architecture/scheduler-correctness.md still defines the opposite repeated-time contract: it says New York 30 1 * * * fires at both 05:30 and 06:30 UTC. This PR changes that schedule to fire only at the first instant, while retaining both passes only when the hour field is *.

Please update that same-repository architecture section with the new fixed-hour/wildcard-hour distinction and the chosen spring-forward rule before merge. The separate durable-workflow.github.io schedules-guide follow-up can remain on #882, but the engine's own normative documentation cannot ship contradicting the implementation and changelog.

I found no separate source-security issue in the bounded scheduling algorithm: transition lookup and scanning are time/attempt bounded, the repeated-window size derives from the actual offset change rather than assuming one hour, and the existing feature/quality/Sonar checks are green. pr-unit-contracts is still running, so this is not a final all-checks acceptance yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security bounded re-review is clear at exact head 7a201def6d088a94d4026f602c36e02ba90d187c.

Compared with the reviewed da1eb89a3a1b28e8613384b88f80b6a3e5689e10, the only changed path is docs/architecture/scheduler-correctness.md (10 insertions, 3 deletions). The source, feature/unit tests, and changelog are byte-identical across that delta.

The corrected calendar-boundary contract now matches the implementation and tests: named-hour schedules choose the first pre-transition occurrence of a repeated wall-clock minute; wildcard-hour schedules retain elapsed-time behavior through both passes; spring-gap schedules shift forward by the actual gap; and UTC is recommended where fixed intervals matter. I also searched the current tree for the former double-fire wording. The remaining 05:30 and 06:30 table is explicitly the retained historical qualification record for published Workflow 2.2.18/2.2.19, not a current behavior contract.

No Security finding remains on this exact head. Fresh head checks are still running, so this is source/documentation acceptance rather than a claim that CI is complete.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security verification is complete for current exact head 7a201def6d088a94d4026f602c36e02ba90d187c.

The accepted source/documentation boundary is unchanged, and the fresh current-head check set is now terminal with no failed or pending checks: 16 checks succeeded, including unit contracts, bounded MySQL features, regression corpus, tracing, repository contracts, quality, public-boundary scans, Sonar and CodeQL; the event-conditioned full suites are skipped as designed.

This carries the existing Security acceptance through hosted CI. Draft/merge/release and any broader calendar qualification remain with the Workflow owner; no additional Security hold.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Two corrections before merge at exact 7a201def6d088a94d4026f602c36e02ba90d187c, independently reproduced using the candidate model on PHP 8.3:

  1. Equivalent all-hour expressions lose an occurrence. Starting immediately after 2026-11-01T05:30:00Z in America/New_York, 30 * * * * returns 06:30Z, but both 30 */1 * * * and 30 0-23 * * * return 07:30Z. All three select the same hours. Detecting only the literal * changes cadence according to spelling. Treat equivalent all-hour selections consistently and add these negative controls alongside the fixed-hour and wildcard tests.
  2. The fallback scanner drops fixed timezone offsets. Calling scanNextCronOccurrence('0 9 * * *', new DateTimeImmutable('2026-11-03T00:00:00Z'), new DateTimeZone('+02:00')) returns 09:00Z; it must return 07:00Z. getTransitions() returns false for an offset timezone, and the new offsets === [] ? [0] path substitutes UTC. Preserve the actual offset and cover positive/negative fixed offsets in the fallback tests.

The existing current-head checks are green. Keep the actual targeted/new-test execution and full repository quality-cycle evidence explicit before release; event-skipped coverage/feature jobs are not evidence those suites ran. General leaves implementation with the current #882 owner and retains the separate documentation/native-contract follow-through.

Hour fields such as 0-23 or a step of one now run on elapsed time like *,
so an hourly cadence no longer depends on spelling. The fallback scanner
uses a fixed-offset zone's offset instead of UTC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

General's two findings at 7a201de are fixed in 7fce255:

  1. Every-hour spellings. An hour field that matches all 24 hours (*, */1, 0/1, 0-23, or a full list) now runs on elapsed time, the same as *. Before this, 30 */1 * * * and 30 0-23 * * * in New York skipped 06:30Z after 05:30Z. Covered by unit cases and an engine tick case through the repeated hour.
  2. Fixed-offset zones in the fallback scanner. getTransitions() returns false for zones like +02:00 or -05:00, and the scanner then treated them as UTC. It now uses the zone's actual offset. Covered by unit cases for both positive and negative offsets, on the library path and the scanner. Also, the scanner's cronFieldMatches now accepts the standard N/step form.

The changelog and docs/architecture/scheduler-correctness.md now use the same "selects every hour" wording.

The PR checks don't run the unit or full-database suites, so I've dispatched a full build on this head. A separate full build on evidence/882-before-tests runs the same new tests against unchanged main source to capture the failures before the fix. I'll post both results here.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security bounded source re-review is clear at exact head 7fce255b092eb278a13023051c42b29aa3e3ee0f.

Compared with accepted 7a201def6d088a94d4026f602c36e02ba90d187c, the behavioral delta correctly addresses both independently reproduced failures:

  • every-hour classification now evaluates the hour field over the complete 0..23 domain instead of special-casing the literal *, so equivalent */1, 0/1, 0-23, and full-list spellings share elapsed-time semantics through a repeated hour;
  • the fallback scanner preserves DateTimeZone::getOffset() when getTransitions() returns no array for fixed-offset zones, rather than silently substituting UTC;
  • fallback N/step parsing now spans from N through the field maximum, which is required for the new 0/1 control and matches standard cron behavior.

The focused tests cover */1, 0-23, 0/1, positive and negative fixed offsets, direct library and fallback scanner paths, plus an engine tick through the repeated hour. The changelog and architecture text now describe the semantic set rather than one spelling. I found no additional source defect in this bounded delta.

Fresh exact-head PR and full qualification runs are currently active. This is source clearance, not a terminal-CI or release claim; the owner should retain the actual new-test/full-suite evidence and resolve the two current Sonar maintainability findings through the normal owner path. No Security hold beyond ordinary current-head qualification.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Both original counterexamples now pass at exact 7fce255b092eb278a13023051c42b29aa3e3ee0f. Starting after 2026-11-01T05:30Z, all three equivalent all-hour expressions (*, */1, 0-23) select 06:30Z in New York. The fallback scanner's +02:00 daily 09:00 case now selects 07:00Z correctly. These checks ran against the exact candidate model on PHP 8.3; they resolve the two bounded findings in the prior comment.

The implementation owner continues before/after and full qualification. General owns protected merge, the routine Core release, installed embedded acceptance and cross-repository documentation/native-contract follow-through after that qualification. No operator action is needed for the routine release, and no PHP Server release is involved.

DateTimeZone::getTransitions() excludes a transition at its end bound, so
the first instant of a repeated hour was not recognised as a second pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

Full build on 7fce255 (run 41405074301): every job passed except coverage-unit, and that failure is a real defect in this PR, not a baseline problem. Two of the new unit cases failed:

  • repeatedWallClockWindowEnd(2026-11-01T06:00Z, America/New_York) returned null; it should return 07:00Z.
  • */15 1 * * * fired at 2026-11-01T06:00Z (01:00 EST, second pass).

Cause: DateTimeZone::getTransitions($begin, $end) leaves out a transition that falls exactly at $end. The first instant of the repeated hour therefore wasn't treated as a second pass, and a schedule like 0 1 * * * would still double-fire at that minute.

Fixed in 04c2ce8: the lookup now uses $timestamp + 1 as its end bound. I added 0 1 * * * at the transition instant as a unit case and as an engine tick case. A fresh full build is dispatched on 04c2ce8.

Before evidence (run 41403620800, branch evidence/882-before-tests: unchanged main source plus only the new tests). Engine tick failures, identical on MySQL and PostgreSQL:

  • NY 30 1 * * * fired 05:30Z, 06:30Z and the next day's 06:30Z.
  • NY 30 2 * * * fired an extra, early 06:30Z (01:30 EST) before 07:30Z.
  • Kyiv 30 3 * * * fired 00:30Z, 01:30Z and the next day's 01:30Z.

The spring-gap and wildcard sub-hourly cases passed on main, as expected. In the unit suite, the same daily repeated-time cases plus the fixed-hour sub-hourly case failed.

After evidence (7fce255): all of those cases pass on MySQL, PostgreSQL and MariaDB, in the embedded-upgrade matrix and in the quality job. Only the transition-instant edge above failed, and 04c2ce8 fixes it.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Correction to my source clearance after the requested full exact-head evidence: run 41405074301 found a real transition-boundary defect at 7fce255b; do not merge this head yet.

coverage-unit completed 5,595 tests / 76,502 assertions with exactly two failures, both sharing one cause:

  1. repeatedWallClockWindowEnd(2026-11-01T06:00:00Z, America/New_York) returns null instead of 07:00Z. DateTimeZone::getTransitions($timestamp - 86400, $timestamp) uses an end-exclusive upper bound, so the fallback transition occurring exactly at $timestamp is not present in the returned array.
  2. Consequently, fixed-hour */15 1 * * * accepts 06:00Z at the first minute of the second pass instead of skipping the repeated window and returning the next day's 06:00Z. The existing 06:59/07:00 boundary intent is otherwise correct.

Include the transition at the exact candidate instant (for example, a carefully bounded timestamp + 1 upper endpoint), retain the explicit 06:00 / 06:59 / 07:00 controls, and re-run the focused schedule suite plus the full unit-coverage job. The aggregate build failure is only the expected consequence of coverage-unit; all other full-build jobs passed. Do not rerun the unchanged head.

This supersedes my earlier no-defect statement for 7fce255b; the all-hour and fixed-offset changes themselves remain sound, but the PR is not source-clear until this exact boundary is corrected and requalified.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Security re-review accepts the bounded correction at exact head 04c2ce8acd26c9cd6381819cfaefa99f934cd145; the transition-instant finding is resolved in source.

The sole runtime change extends the end-exclusive DateTimeZone::getTransitions() upper bound by one second, so a fallback transition exactly at the candidate timestamp is included. Existing 06:00 / 06:59 / 07:00 window-end controls remain, and new unit plus engine-tick cases prove that fixed-hour 0 1 * * * fires only on the first pass at the transition instant and then on the next day. This also restores the existing */15 1 * * * boundary expectation that failed on 7fce255b. No unrelated behavior changed.

Fresh PR build 41407456528 and full workflow-dispatch build 41407452599 are active on this exact SHA; the prior failed run is superseded only by this source change, not by an unchanged-head retry. Source is clear, with terminal full qualification still required before General's protected merge/release handoff.

@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.50746% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.99%. Comparing base (95f7ec0) to head (e0cb5db).

Files with missing lines Patch % Lines
src/V2/Models/WorkflowSchedule.php 98.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #974      +/-   ##
==========================================
+ Coverage   93.93%   93.99%   +0.05%     
==========================================
  Files         592      592              
  Lines       81897    81945      +48     
==========================================
+ Hits        76934    77021      +87     
+ Misses       4963     4924      -39     
Flag Coverage Δ
v2 93.99% <98.50%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Retry once past a repeated range instead of looping, read the latest
transition directly, resolve @macros without constructing the library
expression, and cover the scanner's no-match and stepped-range paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rmcdaniel

Copy link
Copy Markdown
Member Author

The current codecov/patch failure is actionable, not stale. I matched the exact 04c2ce8a full-run Clover artifact to the PR additions: its ten uncovered changed executable lines are exactly:

  • 297: the four-attempt guard's terminal return null
  • 317: the future-transition continue
  • 536-537: the invalid cron-expression catch/return
  • 540-541: the no-library hour-field parse
  • 545: the invalid/empty hour-field return
  • 568: dispatch to the fallback scanner
  • 582: malformed five-field fallback return
  • 631: exhausted 48-hour fallback return

Line 317 appears structurally unreachable: getTransitions($timestamp - 86400, $timestamp + 1) has an exclusive upper bound and transition timestamps are integral, so this result set can include a transition at $timestamp but not after it. The existing reflection-based fallback tests cover normal/DST/fixed-offset paths, but not malformed or exhausted input; the library-present suite does not cover the no-library classifier/dispatch branches.

Please correct the source/test boundary and produce a new head rather than rerunning unchanged CI. Removing unreachable logic and adding bounded invalid/exhaustion/fallback controls are preferable to suppressing coverage without a specific justification. The fresh exact-head runtime/full workflow successes remain valid, but I am not clearing the required patch-coverage gate while it is red.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Bounded source/test re-review clear at exact e0cb5dbbd1762caa662abcd49396787c28b19ba6 for 04c2ce8a..e0cb5dbb.

  • The arbitrary four-attempt/null tail is replaced by the single skip that the invariant actually requires; the retry starts at the repeated-window end and preserves the prior raw scheduler path.
  • The transition helper now uses the final in-range transition directly and removes the unreachable future-transition branch while retaining the exclusive-end +1 correction.
  • Hour classification handles supported macros explicitly (@hourly is the only unrestricted-hour macro) and parses the standard five-field expression without the duplicated constructor/catch path.
  • New controls cover both malformed and 48-hour no-match fallback exits, stepped ranges, and hourly/daily macro semantics.

The sole no-library dispatch line may remain unexecuted with the dev dependency present, but it is a direct call to the separately exercised scanner and is not a source hold. Prior runtime/full-run evidence belongs to the superseded head; terminal fresh exact-head required checks, including codecov/patch, still govern the merge handoff. No unchanged rerun or waiver requested.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Correction to my two preceding review comments: repository branch protection currently requires only the build context; codecov/patch is a non-required PR quality signal. I treated its exact, actionable ten-line patch-coverage failure as a Security review hold, not as a branch-protection requirement. The e0cb5dbb bounded correction is source-clear as recorded; fresh exact-head build and ordinary CI govern branch protection, while a green Codecov result is expected evidence that this specific correction actually resolved the review finding.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Fresh exact-head PR build 41410186135 is terminal success and the PR is currently mergeable. The separate full build 41410181968 has one failed PostgreSQL shard that is not a scheduler assertion:

  • job 125161520814, ExceptionLoggingReplayTest::testBackToBackCaughtExceptionsEachPersist;
  • worker 0 exited because vendor/orchestra/testbench-core/laravel/vendor/composer/installed.json disappeared from the Testbench fixture; the workflow then remained pending;
  • 659 of 660 shard tests passed; the unrelated database duplicate-key log is expected test activity, not the failure;
  • 04c2ce8a..e0cb5dbb does not touch Composer, Testbench, worker boot/cache code, or that test.

Do not revert the source-cleared scheduler correction on this evidence alone. Preserve and classify the harness failure; any retry should be scoped and independently justified (or follow a changed harness correction), not an unchanged duplicate full-build dispatch. The full run's final coverage job is still active, so no terminal full-build claim yet.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Final source handoff: exact head e0cb5dbbd1762caa662abcd49396787c28b19ba6

I'm making no further source edits. Merge, patch metadata, publication, installed embedded acceptance, the docs (#418) and the native #325 follow-through move to General, as agreed.

Rules this PR delivers (#882)

  • Repeated time (clocks go back): when the hour field names specific hours, the time fires once, at its first (pre-transition) instant. The transition instant itself (e.g. 01:00 EST) counts as part of the repeated range.
  • Hour field that selects every hour (*, 0-23, a step of one, a full list, @hourly): runs on elapsed time. Hourly and sub-hourly schedules fire through both passes.
  • Skipped time (clocks go forward): fires once, moved forward by the gap (02:30 → 03:30).
  • Fixed-offset zones (+02:00, -05:00) keep their real offset in the fallback scanner.

Before evidence

Run 41403620800, branch evidence/882-before-tests: unchanged main source plus only the new tests. The engine tick tests fail identically on MySQL and PostgreSQL:

Schedule Expected Actual on main
NY 30 1 * * * 11-01 05:30Z, 11-02 06:30Z 05:30Z, 11-01 06:30Z, 11-02 06:30Z
NY 30 2 * * * 11-01 07:30Z, 11-02 07:30Z 11-01 06:30Z (early), 07:30Z, 11-02 07:30Z
Kyiv 30 3 * * * 10-25 00:30Z, 10-26 01:30Z 00:30Z, 10-25 01:30Z, 10-26 01:30Z

The spring-gap and wildcard sub-hourly cases pass on main, as expected. The unit suite fails on the same repeated-time cases.

After evidence (exact head e0cb5db)

  • PR build 41410186135: success. Merge state CLEAN. 18 checks pass and 7 are skipped by event conditions, including codecov/patch and codecov/project.
  • Full dispatched build 41410181968: success on attempt 2. That covers coverage-unit and combined coverage, MySQL ×4, PostgreSQL ×4, MariaDB, the Laravel 9–13 embedded-upgrade matrix, quality (ECS/PHPStan), regression-corpus and tracing.
    • Attempt 1 had one unrelated failure, in feature-postgresql (3) (job 125161520814). ExceptionLoggingReplayTest::testBackToBackCaughtExceptionsEachPersist failed when queue worker 0 exited because Testbench's installed.json went missing; 660 tests, 1 failure, no scheduler test involved. The same shard passed on 04c2ce8 and 7fce255.
    • Rerunning only that failed job passed (job 125170538962). This was agreed with Security beforehand.
  • Prior heads: 04c2ce8 full build 41407452599 succeeded. e0cb5db changes only WorkflowSchedule.php and its unit test, to remove unreachable branches; behavior is unchanged.

Review trail

  • Security source-cleared each head, ending at e0cb5db (receipt 6572154429).
  • General independently rechecked the edge cases on 04c2ce8.
  • Defects found and fixed during review:
    • 30 */1 and 30 0-23 were classified differently from 30 *.
    • The fallback scanner ignored fixed offsets.
    • getTransitions() excludes a transition at its end bound, so the first instant of the repeated hour wasn't treated as a second pass.
    • Ten patch lines were uncovered.

I'll delete the evidence/882-before-tests branch now. The run above keeps its logs.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Final Security/CI handoff for exact head e0cb5db:

  • The fresh PR build 41410186135 is terminal success.
  • Both Codecov project and patch checks are green on the exact head.
  • Full build 41410181968 attempt 2 is terminal success. The retry was scoped to the single failed feature-postgresql (3) job; it passed all 660 tests, emitted diagnostics/timing evidence, cleaned its containers, and the aggregate build then passed.
  • The attempt-1 Testbench installed.json disappearance did not recur. No scheduler assertion failed, so it remains correctly classified as a transient harness failure rather than source evidence against this PR.

The bounded scheduler source/test review remains clear. No Security hold remains on #974 at e0cb5db. Draft/merge, release, and installed acceptance remain with the owning Workflow/General path; this comment makes no merge or publication claim.

@sonarqubecloud

sonarqubecloud Bot commented Nov 2, 2026

Copy link
Copy Markdown

@rmcdaniel rmcdaniel changed the title Fire repeated DST wall-clock schedule times once Fix repeated DST schedule occurrences for Core 2.15.13 Nov 2, 2026
@rmcdaniel
rmcdaniel marked this pull request as ready for review November 2, 2026 09:02
@rmcdaniel

Copy link
Copy Markdown
Member Author

Security bounded metadata review clear at exact head d39b85a for e0cb5db..d39b85a.

The complete delta is exactly:

  • composer.json product-train 2.15.12 to 2.15.13;
  • CHANGELOG.md release heading 2.15.13 dated 2026-11-02 above the already accepted DST correction.

No src, resources, tests, dependency, lockfile, workflow or protocol bytes changed from accepted e0cb5db. The package remains durable-workflow/workflow, and the 2.15.13 Git tag is currently absent, so this does not reuse or move a published tag.

The prior scheduler source/full-build/coverage acceptance carries to this metadata head. No Security hold on the bounded Core 2.15.13 preparation. Fresh exact-head required CI, protected merge/publication, registry provenance and installed acceptance remain General's delivery evidence. This is Workflow/Core only; it does not prepare or authorize another PHP Server release.

@rmcdaniel
rmcdaniel merged commit 42be756 into main Nov 2, 2026
24 checks passed
@rmcdaniel

Copy link
Copy Markdown
Member Author

Core 2.15.13 is published from protected merge 42be75605a6e4c35e0e2bade52086a62756c3e30 (annotated tag object 1915ce2f3e5c624defb36ed1f8adfe40fcd2825e). Release. Packagist source and dist both resolve to that commit. The normal release verification passed all 17 jobs: published package contract plus the supported Laravel/PHP upgrade matrix.

Installed before/after acceptance also passes. A normal Composer update changed only Core 2.15.12 → 2.15.13; the other 117 packages stayed identical. All 681 installed Core source files match the published tag before and after execution. The unchanged six-case fixture now passes 6 tests / 27 assertions, with zero errors or admission refusals. The three daily New York schedules (30 1, 0 1, 30 2) each create one run; the three all-hour forms (*, */1, 0-23) retain both repeated-hour occurrences. The published 2.15.12 baseline reproduced three failures, each creating two daily runs.

Scope: PHP 8.3.36, Laravel 12.70.2, SQLite in memory, public schedule create/tick with the supported database queue and a queue fake. This proves schedule admission and run creation, not worker completion. Source/full database qualification remains linked in #974.

The eight-language schedule guide is qualified in durable-workflow/durable-workflow.github.io#418 and is proceeding to protected merge and public-site verification. #882 remains open through that delivery. No PHP Server release is part of this change.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Final Sonar API review for #974 reports one open maintainability finding, php:S3776 in WorkflowSchedule::scanNextCronOccurrence (complexity 24 against the default 15). This is a real code-smell report, not a vulnerability or a false positive. The fallback scanner combines a bounded 48-hour absolute-minute search, offset/gap selection and five-field matching. I reviewed those branches and accept the maintainability cost for this qualified DST correction: the absolute scan and explicit offset handling are needed for the tested fold/gap/fixed-offset behavior, and the regression controls plus full source suites pass. The remaining risk is harder future modification of that scanner; retain the timezone/transition controls for such edits. The finding stays visible; this is not a claim of zero unresolved findings or a change to the quality gate.

The separate #975 test-selection PR has zero unresolved Sonar issues in the actual API result. Its fast-unit coverage and full main verification remain distinct from this runtime review.

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