Skip to content

CLI history export loses whole-float memo kinds and fails its own verifier #912

Open

Description

@rmcdaniel

Problem

The published history-export CLI loses a memo's numeric kind and writes a bundle that the same published Core verifier refuses. A workflow with memo whole=1.0 completes normally, but the exported display projection contains JSON integer 1 while its lossless Avro memo authority still decodes to a float. Persisted workflow execution and the Avro authority remain intact; the loss occurs in the exported JSON.

Actual published-artifact evidence

Qualified tuple: Server 2.9.7 / 150f3fbcbf3911a2a20abb53b4a714081ee53dd7, amd64 image sha256:40e969d0b6b240b835dcdb2fc531507266216ec34a83f59f561674f41e35f461; loaded Core 2.7.2 / 64efa176e4c37e5d6ae80e767ba3b41464024e53. All 615 loaded Core source files match the immutable source. Composer's generated path reference is separate metadata, 9477eaaf970e9ce09da337f662e71178a05c6342. No runtime source overlay.

A real SQLite workflow and activity used original 30-second run/execution budgets. Actual workflow:v2:history-export --run --pretty wrote selected-run bundles while waiting (3 events) and after completion (6 events). Both original files are retained byte-for-byte. The released BundleIntegrityVerifier::verifyJson refuses each with workflow.memo_projection_mismatch and integrity.checksum_mismatch. The actual published workflow:v2:replay-verify --skip-replay command, invoked through Artisan, returns 1 for both files. Their raw hashes are unchanged before/after verification.

Actual export Raw-file SHA256 Recorded canonical SHA256 Published verifier recomputation
waiting 2a11c8ce20d7433fe6b15d98f958f31daac34e34f92eaca451868d0fb9794fea 38c1b65717058fbaa588c52919a223941b04d3c7a57620f557356938eea28070 49dff74395d6c75f118377fcadb9515cf414cefa0cb7755c7e6d5af5d55b7c76
completed 71bc8043b9634297abfe26b58bca788f4c245129adc4d16851cab36889c719e7 2b900246391a0293ccb2fde0bf4d1cf1ae5f67c2c1d6d6ddacaccdb55a976761 0dfe2dc726f70c9519b1943b3c2b63cc2235ef3aa78a42d533f70ac4d66c2be6

The original failed exports and provenance are retained as negative controls in SDK PR #208. The corrected SDK reader also rejects these producer-invalid bytes. This is separate from SDK #173, whose valid schema-2/Unicode reader controls are accepted independently. Neither a version rewrite nor a recalculated checksum was used to force acceptance.

Reproduction and source boundary

  1. Start an ordinary workflow with new StartOptions(memo: ['whole' => 1.0]) and export the selected run using the published CLI.
  2. Parse the JSON and compare workflow.memo.whole with MemoPayload::decodeEntries(workflow.memo_payload)['whole']: the former is an integer, the latter a float.
  3. Run published integrity-only verification against the untouched export; it returns failure.

V2HistoryExportCommand::encode serializes without JSON_PRESERVE_ZERO_FRACTION, while HistoryExport canonicalization preserves fractional-zero floats. This explains both type and checksum failures. The HTTP export surface needs its own audit; this report establishes the CLI case, not a tested HTTP failure.

Acceptance

  • Preserve numeric kinds throughout CLI JSON export, including nested memo/history projections and signed zero, while retaining the existing schema and canonicalization contract.
  • Actual waiting and completed exports containing whole-valued floats pass the published verifier with unchanged checksums and correct memo authority/projection.
  • Keep invalid older export bytes rejected and retain these unsuccessful originals. Add positive/negative regression controls and verify actual CLI output, not only an in-memory exporter array.
  • Audit other JSON export transports for the same loss and track any affected transport separately.
  • Publish a qualified compatible correction and verify normally installed/exported behavior. No runtime deadline, protocol-floor or stored-history change is required.

P2 product defect. This record contains reproduction and evidence; no Core/Server implementation change is included.

Current delivery

CLI source correction #913 is merged at ae6bf6c / whole36045935 after seventeen applicable candidate checks pass. Independent main and compatible 2.11.4 publication/normal installation remain pending. Actual published Server HTTP evidence is confirmed separately in #692/#693; the Core CLI correction alone does not fix that transport. Original invalid exports remain rejected.

Activity

  1. rmcdaniel commented on Oct 23, 2026

    @rmcdaniel
    MemberAuthor

    Separate published Server 2.11.1 / installed Core 2.10.2 qualification confirms this producer failure in both CLI and HTTP exports, while leaving the independent Core 2.11.3 investigation separate.

    • Server source 5336a98dd256e046edfdb67c81764cde4babd360; published image ghcr.io/durable-workflow/server@sha256:bf8bed13f5f809ee3425233180a581353eac4683adbee5c4ecbf63bb18bbd102 (local image ID c53162134a94025a0b87b995e9b14896c1fdfffbeeace3b530d3fbd0f2b23c6c). All 313 installed Server files under app/, config/ and routes/ match that commit. Installed Core reports 2.10.2; all 641 Core source files match its published source aca7928484083635e68a5a384b2dcecef842108e. Composer's generated reference is separately c1e248afe31060f13216704ac6de9a514ba4c733. PHP 8.3.36. No source overlay.
    • Normally installed PyPI durable-workflow 2.9.0, source 04116871c83e6a502c713789cb452e90917a3438, wheel SHA-256 1e7547e895485dc8f670bd1b9cc34184b135f249db8da98b16b250bfc6002f73; all 36 installed Python files match the published tag and wheel RECORD. Its normal Client.export_history(workflow_id, run_id) receives HTTP 200 from GET /api/workflows/{id}/runs/{runId}/history/export. Raw response bytes were captured before parsing, without reserialization.
    • A separately named real workflow used memo {'whole': 1.0, 'signed_zero': -0.0, 'nested': {'whole': 2.0, 'signed_zero': -0.0, 'list': [3.0, -0.0, 1.5]}}, with original 30-second execution and run budgets. It waited with three events, completed a real activity and workflow in 2.065 seconds, then had six events. CLI and HTTP exports were captured in both states.
    • All four untouched files fail the actual installed PHP BundleIntegrityVerifier::verifyJson and workflow:v2:replay-verify --skip-replay (exit 1), each with workflow.memo_projection_mismatch and integrity.checksum_mismatch. Each JSON projection has integer 1, 2, 0; authoritative Avro values remain float 1.0, 2.0, -0.0 (signed-zero float64 bytes 8000000000000000). Thus HTTP failure is now an observation, beyond source suspicion. These are integrity checks; candidate-code replay was not run.
    New raw export SHA-256 of exact file bytes
    CLI waiting 7eaf0877dcbbaa481ddd2f57f69b4c718eb2dc3abd3946b0e723a86515b0ac5a
    HTTP waiting 05240fdac68c2398a17bb5262a4d1e7a76b22018fe4eea3b5cc5444e98687d0c
    CLI completed bf76c9fa4eb5401c75da75bafbaf55a00eb5cbe120431beca234396f595bc90f
    HTTP completed 6e5eb0a98f30fb50f8cf8acec8e8eaf8f7537a373559be16ad3acc1177d59d09

    These fresh files and reports are retained for producer follow-through. They do not replace the historical Core 2.7.2 failed originals or Website #320 evidence. The Python #173 reader continues rejecting the typed disagreement. No Core/Server/native/frozen source was edited.

  2. rmcdaniel commented on Oct 23, 2026

    @rmcdaniel
    MemberAuthor

    Current-source correction is merged as #913: ae6bf6c8a7c4b9597ee4eecb15ecd786e78b0430, exact tested whole tree 3604593569c80ba2319d9a2ee1d39ab33b206357. All seventeen applicable candidate checks pass, with six explicit skips, and the actual PR Sonar issue API reports zero OPEN/CONFIRMED findings. The merged remote branch is independently absent.

    The fresh normal 2.11.3 install matches all 643 immutable source files at 71213ae1, without an overlay. All four actual waiting/completed compact/pretty export regressions fail there and on the unchanged parent. Corrected source passes 4 tests / 56 assertions, including exact nested numeric kinds, signed-zero bit behavior, untouched-file integrity-only replay verification and refusal of lossy representations. Focused ECS and PHPStan pass. The first fixture also compared noncanonical map order; its corrected version still fails all four parent cases specifically on numeric kinds.

    Fresh main build39947240076, code quality39947240082, Sonar39947239603, dependency security39947240010 and boundary39947239994 are independent and still qualifying. Compatible 2.11.4 is not published yet; registry installation and published-profile acceptance remain next.

    The separate HTTP defect is now Server #692 / PR #693. Its source-only correction passes 49 ordinary history/control-plane cases / 700 assertions after both failing-before numeric cases. No published Server image, stored-history rewrite, reader coercion or cross-tree adoption is claimed.

  3. rmcdaniel commented on Oct 23, 2026

    @rmcdaniel
    MemberAuthor

    CLI package correction delivered as Workflow 2.11.4.

    • Immutable annotated tag 23c667746de44cb1fd5cc280df7bd63615f82a2b selects qualified merge/source ae6bf6c8a7c4b9597ee4eecb15ecd786e78b0430, whole tree 3604593569c80ba2319d9a2ee1d39ab33b206357. GitHub release and normal Packagist source/dist agree.
    • All seventeen applicable candidate checks and all five independent main workflows pass. The tag-triggered published release profile39952791550 now passes all seventeen jobs, including all sixteen supported Laravel/PHP published upgrades.
    • A fresh ordinary Composer consumer on PHP8.5.12/Laravel13.38.0 changes only Core in its 119-package locked graph. All 643 installed source files match the immutable release; there is no product-source override.
    • Actual compact/pretty waiting/completed exports pass 4 cases / 56 assertions: whole and nested floats retain their numeric kinds, signed zero retains its sign, the Avro authority agrees, and the actual integrity-only replay CLI verifies the untouched file without replacing its checksum. Deliberately lossy representations still refuse. Source ECS/PHPStan also pass.

    Both original published2.11.3/unchanged-parent four-case failures remain failures; the earlier map-order fixture error remains separate. This release creates valid future exports and does not reinterpret invalid older JSON.

    Affected Server CLI/HTTP image delivery is still pending: Server #692 / #693 has fully qualified main b57e3c97 / 613829c3, but that source still pins Core2.11.2. General will separately qualify the published2.11.4 pin, image and installed transports. This issue remains open for that affected consumer; no native, frozen or Cloud adoption is claimed. The all-locale export guard follows the exact package receipt.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions