Skip to content

Embedded v2 history import reads and attaches across namespaces (published 2.11.1) #765

Open

Description

@rmcdaniel

Current PHP delivery state (2026-11-01 01:15 UTC): published Server 2.11.7 / Core 2.15.7 remains affected. Server main pins Core 2.15.10, which predates the namespace/reference fix. Workflow draft #929 is at aabcc1ccb7e9157e9cd685ce108c4862e21329e5, with Core 2.15.11 prepared but unpublished. Full unit passes 5,571 tests / 76,446 assertions / 48 skips / 15 notices, zero failures. Full coverage, feature and expanded database qualification remain in progress. Hosted selected checks and actual head-bound Sonar pass; no complete local cycle or successor Security acceptance is claimed.

A downstream cached-summary gap is independently reproduced even with the Core fix present. General's prepared Server guard at 3c6ccf15f7ee9207628b591ccc13694ceaf8f399 passes 16 / 56 on SQLite and MySQL; the same tests on unchanged Server source produce 13 intended failures / three valid controls / zero errors on each. Four adjacent API files pass 97 / 1,514 on SQLite. The complete local PHP selection also passes 3,467 tests / 71,512 assertions / 182 skips / six deprecations on this exact prepared Server source plus the unreleased aabcc1cc Core archive. Normal published Core adoption and final protected PR checks remain required. This source branch still has the old Composer pin and cannot be integrated until published Core adoption and complete Server qualification. It is not fixed-image delivery.

The PHP signing-key URL slice PR #893 merged through the protected queue as ac65ab661428181bbdf4e133fe89656c9b440f7e. All three reviewed blobs match main. Merge-group checks and normal main PHP, Sonar and Push on main checks pass. Main PHP qualification: 3,457 tests / 74,984 assertions / 51 skips / six deprecations, plus release contracts 50 / 237. Actual main Sonar is bound to that merge, gate OK, no new issues since queue entry and no unreviewed hotspots. The controller's existing early-return maintainability finding was specifically accepted; no security rule or gate was disabled. This is source completion, not a published-image claim.

Next: finish Core qualification and immutable publication, adopt the published Core package in Server, qualify the combined source and verify actual published-image behavior under the existing release gates. Keep this issue open through installed-image acceptance. General owns the PHP follow-through; native and shared/frozen fixtures remain with their owner.

Published Server 2.11.1, with Workflow 2.10.2's EmbeddedV2HistoryImport, lets an embedded v2 history import in one namespace read and change another namespace's workflow state. The route is admin-only, but namespaces are meant to stay isolated.

1. Cross-namespace duplicate. EmbeddedV2HistoryImport::existingRunOutcome looks the bundle's run up in any namespace. A bundle already imported into namespace B, imported again into namespace A, answers 200 with already_imported for A, which holds no such run.

2. Cross-namespace instance attach. EmbeddedV2HistoryImport::writeBundle finds the workflow instance in any namespace and attaches the imported run to it. A run imported into namespace A becomes the current run, and bumps the run count, of an instance that belongs to namespace B.

Evidence. Both behaviors were recorded three times from the published image, with identical results, in tests/Fixtures/ServerParity/workflow-import.json under workflow_import.published_defects (cross_namespace_duplicate, cross_namespace_instance_attach). The fixture arrives with #764.

Native behavior (#764). A run id held in another namespace is refused as run_id_conflict. An instance of another namespace is refused rather than taken over, and the other namespace is left unchanged. The parity fixture keeps the published answers bounded as defects, so native isn't required to reproduce them.

Suggested PHP fix. Scope both lookups by the requested namespace: the existing-run check and the instance resolution in the write. A run id or instance id already present in another namespace should then be a conflict.

Activity

  1. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    General is implementing the PHP root correction in durable-workflow/workflow#928. Current-source counterfactual coverage reproduces both original defects in all twelve named/null namespace and write/preview cases: twelve assertion failures, zero test errors, against the unchanged Workflow 2.13.1 import class.

    The candidate restricts run-idempotency to the target namespace, reports an explicit foreign-instance conflict before preview/write, and scopes the transaction's locked instance lookup so a preflight race cannot attach the run to a foreign instance. The ordinary run-number-conflict control now explicitly selects its existing namespace, preserving the intended SQL uniqueness/rollback check. Full current-candidate qualification is running; no merged or published correction is claimed.

    The reference audit from #764 is retained in Workflow #928, including parent/child links, signal/update command IDs and other copied relational identifiers. These direct target guards do not complete that audit or the installed-image acceptance. Native/shared/frozen work remains with its owner. Server #526's separate query-authority race remains open in draft #758.

  2. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    The direct target guards are now visible in Workflow draft PR #929 at 5c766e3188fb1fb7ac9ebb267773bcc5e37912f6. Full ECS/PHPStan and 44 tests / 607 assertions pass locally; exact counterfactual and current-scope evidence is in durable-workflow/workflow#929 (comment). Workflow #928 retains the full copied-reference graph and compatible quarantined-lineage work. Hosted qualification and independent partial source review are live. This is source-only, with no package or Server image publication; keep #765 open through complete fixed-image acceptance.

  3. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    Workflow draft PR #929 now includes the active signal/update command-reference boundary at f0e6e9be13de3d5d3dcdf5de42af720bc35ad8bd. Each message must bind to a matching command imported from the same bundle and transaction. SQLite, MySQL 8.0.46 and PostgreSQL 17.11 each pass 82 tests / 779 assertions; counterfactual worker completions reproduce foreign-command mutation before the guard. Evidence: durable-workflow/workflow#929 (comment) . Full copied-reference/historical/lineage acceptance remains in Workflow #928, including the new source-only projection audit in comment 6402344424. Keep #765 open through complete source and fixed published-image verification. Native/shared/frozen work remains with its owner; no publication is claimed.

  4. added a commit that references this issue on Oct 25, 2026
  5. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    Native reference isolation for embedded v2 import, from the Security review of #764. General owns the published PHP side in Workflow #928 and Core #929. Published Server 2.11.1, with Workflow 2.10.2 through 2.13.1, copies every reference a bundle carries without checking it. Native refuses such a bundle. This is the audit of every field the importer copies, at #764 head 6f109c2c118bf3c3c309eb606631357d5a29e6a4.

    Published defects beyond the two run/instance lookups already recorded here:

    • Lineage. A link's related run or instance can belong to another namespace. History export then lists that foreign run's metadata, and retention and cancellation traverse the link.
    • Commands. A signal's or update's command_id can name another workflow's command. Later application then updates that command by id alone: applied_at, plus outcome for updates.
    • Task payloads. A task payload's activity_execution_id can name another run's activity. A lost lease then settles that activity, because it is looked up by id alone.
    • Signing key in the URL. signing_key is read from the query string too. Access logs, tracing and client history keep URLs.

    What native does (writes are atomic, and nothing is committed on refusal):

    Copied field Runtime use found Native rule
    commands/signals/updates[].resolved_run_id Retention deletes by it Must be the imported run (references.foreign_run)
    commands/signals/updates[].requested_run_id Retention deletes by it Null, the imported run, or another run of the same workflow in this namespace (write check)
    signals[].command_id Signal application joins and updates the command A signal command of this bundle; the application join and update also require the same instance
    updates[].command_id Update application joins and updates the command An update command of this bundle; same instance on application
    updates[].failure_id Only checked for non-empty A failure of this bundle, or an id no failure holds
    history_events[].workflow_task_id, workflow_command_id Read only with workflow_run_id A task or command of this bundle
    activities[].current_attempt_id Observation join, run-scoped One of that activity's own attempts
    activities[].attempts[].workflow_task_id / task_id Heartbeat and claim joins A task of this bundle. Attempts of an activity the importer skips are refused.
    tasks[].payload.{activity_execution_id, activity_attempt_id, timer_id, workflow_signal_id, workflow_update_id, workflow_command_id} and resume_source_id for those kinds Claims are run-scoped. Lost-lease settlement was not, and now is. A row of that kind in this bundle
    failures[].source_id (kind activity_execution) Display An activity of this bundle. Other kinds are display-only and stay as recorded.
    workflow.archive_command_id Display A command of this bundle
    links.parents/children endpoints Export, retention, cancellation The related run must exist in this namespace and in the instance named. A run or instance in another namespace refuses the import. A run that exists nowhere yet is not written, and its own import writes the link once both runs exist. A self-link is refused. Export also omits any related run of another namespace.
    signals[].signal_wait_id, attempts[].worker_attempt_id Identity within the run, or metadata Inert
    Ids inside history event payloads Readers stay within the run. Child runs are resolved only through workflow_links (fenced above) or a namespace-checked visible-run lookup. History, not references

    The signing key is read only from the body or the configured secret. A query signing_key in any spelling, signing_key[] included, gets 422 signing_key_in_query, and the answer never repeats the value.

    Evidence: rust/tests/execution/workflow_import_fences.rs runs on SQLite, PostgreSQL and MySQL. It covers refusal in both lineage directions, refusal of a foreign instance, a deferred link that a later foreign run never captures, refusal of each foreign row reference, forged signal and update rows refused at application, worker and operator 403s with admin as the positive control, and the URL signing key. Each refusal is checked against byte-for-byte export snapshots of the other namespace. Without the fence and predicates, five of the seven tests fail.

    🤖 Generated with Claude Code

  6. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    PHP source follow-through is now at Workflow PR #929 head c2d32ac2171587e27f76c6b8ad7393f2e1e62456, still draft. Evidence and exact remaining gates cover the new-import lineage slice, same-bundle message command ownership, and direct target guards.

    Both lineage directions now check namespace plus run/instance identity, missing endpoints do not become physical edges, and display/export and ChildRunHistory reader fallback cannot attach a later unrelated run. Mutating consumers were not fenced at this head; Security found those gaps, addressed in the successor below. Qualifying omission exposed a PostgreSQL reciprocal-import race; a real two-process regression now observes a complete serializable retry and exactly one edge. The common 100-case cohort passes on SQLite/MySQL/PostgreSQL, with only the two PostgreSQL-specific controls skipped on the other databases. Retained PHP replay consumers pass 76/3,789; the 17 new lineage controls all fail against the preceding importer/readers with zero test errors.

    The native copied-field audit is received for PHP comparison. Next are task payload/lost-lease references, resolved/requested run IDs, historical task/command IDs and the other copied references. Legacy dangling-edge recovery, continuation/retry compatibility and MySQL/SQLite concurrency remain unqualified. The PHP signing-key URL path also remains required here. Existing source review applies to earlier slices only; no current-head acceptance, package publication, Server consumer update or fixed published image is claimed.

    Successor and correction: durable-workflow/workflow#929 (comment). Exact head 5e01a93 awaits review; this receipt is historical evidence for c2d32ac2 only.

  7. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    Security re-review of Workflow PR #929 exact head c2d32ac2171587e27f76c6b8ad7393f2e1e62456 found two remaining PHP lineage authority blockers: history-only missing endpoints are still consumed by scoped child cancellation and parent-resume mutation paths without an exact materialized edge, and self-lineage is still accepted/resolved. Exact source paths, counterexamples, and required controls are recorded in durable-workflow/workflow#929 (comment).

    The new import/store/view work is not accepted at this head. PostgreSQL serializable retry evidence remains valid; MySQL/SQLite absent-row concurrency, legacy dangling-edge recovery, and the broader copied-reference audit remain open separately.

  8. rmcdaniel commented on Oct 25, 2026

    @rmcdaniel
    MemberAuthor

    PHP import follow-through is at Workflow draft PR #929 head 5e01a938a8766af8f47861aba69d5497bf6731d0. Exact source evidence and open gates are recorded for review. This successor fences imported child cancellation/parent-resume effects with exact directed edges and rejects self-lineage, addressing Security's two c2d32ac2 blockers. It has not yet been accepted.

    The 129-case importer cohort passes on SQLite, MySQL and PostgreSQL; 18 scope-effect cases pass on each database. The 47 current controls against preceding production source produce 42 assertion failures, five valid-pair passes and zero errors. Native scope behavior (63/525), focused child consumers (46/2,076) and retained PHP replay consumers (76/3,789) pass. Hosted quality/corpus are green; unit/feature are pending. A broader local selection hit a tracing subprocess timeout, so full-suite completion is not claimed.

    The prior handoff's fallback claim has been narrowed to the display/export and ChildRunHistory readers it actually covered; mutating paths were unsafe at that old head. Legacy dangling-edge recovery, continuation/retry import compatibility, MySQL/SQLite concurrency, the remaining copied-reference/runtime audit and PHP signing-key URL path remain open. No package, consumer update or fixed published image is claimed. General next addresses the separately owned Workflow #931 service-operation authority boundary while this exact-head review runs; #928/#765 remain General commitments.

  9. rmcdaniel commented on Oct 30, 2026

    @rmcdaniel
    MemberAuthor

    Current release-boundary reconciliation (2026-10-30): this defect remains unfixed in published Server 2.11.7 / Core 2.15.7, and the current Server 2.11.8 draft PR #877 consumes published Core 2.15.8, which also predates the fix.

    Workflow PR #929's exact head a6f9195ba74a7dd601943efb3467c8bf6696427e retains its prior Security source and terminal hosted-run acceptance, but it remains draft, merge-conflicting/behind main, unpublished, and explicitly incomplete for the broader copied-reference/concurrency/full-suite gates in its body. No package or installed Server correction can be claimed from that source state.

    Risk disposition is unchanged: this is an existing admin-only namespace-isolation defect on a released surface. It should remain the dependency-aware public follow-through through rebased source qualification, immutable Core publication, Server consumer update, and installed-image verification. It does not add a hold to PR #877 because withholding that independent patch release would not repair this exposure. General has the current handoff; no operator action is requested.

  10. rmcdaniel commented on Oct 31, 2026

    @rmcdaniel
    MemberAuthor

    The PHP URL-key fix is reviewable in draft #893, head 369f2ba51e8c9e15e366438d828f09a59f6a74b6 / tree 01af5635e496446331085215653c404a88282a83.

    The controller now refuses a query-string signing key before importing, including case variants, arrays, encoded/normalized names and empty/bare fields, and returns 422 signing_key_in_query without repeating the value. Explicit signing keys come only from JSON; configured keys keep working. On the preceding source, all ten URL cases return 201 instead. The fixed selection passes all 14 tests / 72 assertions, including valid signed body/configured imports and existing discovery/import behavior. Pint and its non-fixing check pass; the normal broader PHP selection is running.

    The Core namespace/reference fix is separately at Workflow #929, 6b8e9d5a, with all five extended database cells and hosted CI passing. Its complete local quality cycle remains open, and 2.15.11 is not yet published. Server #893 still uses published Core 2.15.10. No fixed published Server image or new Security acceptance is claimed; #765 stays open through Core adoption and actual image acceptance.

  11. rmcdaniel commented on Oct 31, 2026

    @rmcdaniel
    MemberAuthor

    The normal protected merge queue completed successfully: PR #893 merged as ac65ab661428181bbdf4e133fe89656c9b440f7e at 22:15:39 UTC. All three reviewed source/test/documentation blobs match the merged main exactly. Merge-group PHP qualification and shared fixture selection passed. Normal main PHP and Sonar checks are now running. No image publication or installed-image delivery is claimed; Core adoption and this issue's published-image acceptance remain open under the existing release gates.

  12. rmcdaniel commented on Nov 1, 2026

    @rmcdaniel
    MemberAuthor

    The downstream cached-summary check confirms a separate Server guard is needed in addition to the Core update. Prepared source is backed up on this branch, commit 3c6ccf15f7ee9207628b591ccc13694ceaf8f399.

    NamespaceWorkflowScope now requires the canonical run and instance to belong to the exact requested namespace before a run or cached summary can be selected. A summary must also name its run's actual instance. These predicates apply before search, counting and pagination, so cached business keys cannot leak through query matches. Reads do not rewrite the foreign instance. Workflow and standalone-activity lists are covered, including named, case-variant and embedded-null owners and valid same-namespace controls.

    On both SQLite 3.40.1 and MySQL 8.0.46, the same 16 cases against unchanged Server main produce 13 intended failures, three valid controls and zero errors, even with the proposed Core fix present. The Server guard passes 16 tests / 56 assertions on each database. Four adjacent workflow-control, standalone-activity, visibility-query and message-stream files pass 97 / 1,514 on SQLite. Pint and its non-fixing check pass. The complete normal local PHP selection then passed at 2026-11-01 01:02:20 UTC: 3,467 tests / 71,512 assertions / 182 skips / six PHPUnit deprecations, zero failures. The initial run stopped at an entrypoint test because this QA image lacked the production helper; restoring its exact bytes and executable mode made the original test pass (one / five) before the successful complete rerun. Server source stayed unchanged, and all 2,259 Core files still match aabcc1cc after the full run. This does not replace the required published dependency pin or normal final PR checks.

    This is a combined unreleased source experiment with exact Core aabcc1ccb7e9157e9cd685ce108c4862e21329e5; all 2,259 Core files match that archive. The separate preceding published Core 2.15.10 installation matched all 2,250 tagged files and reproduced the same 13 failures. The branch's Composer files still pin 2.15.10, so it is not ready for integration: the new guard uses the prepared Core namespace helper. Next, finish Core qualification/publication, adopt 2.15.11 through normal Composer resolution, open the consumer PR and complete normal Server qualification and protected integration. Actual installed-package/image acceptance remains open under existing publication gates. No fixed package or image is claimed.

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