Skip to content

Plan replay: repeat an approved chat plan as a personal workflow (#1550) - #1724

Merged
Paul Lizer (paullizer) merged 46 commits into
microsoft:paullizer-react-v2-uifrom
paullizer:paullizer-plan-replay-workflow-task
Oct 9, 2026
Merged

Paul Lizer (paullizer) merged 46 commits into
microsoft:paullizer-react-v2-uifrom
paullizer:paullizer-plan-replay-workflow-task

Conversation

@paullizer

@paullizer Paul Lizer (paullizer) commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Repeat a chat plan on a schedule (plan replay, M5C O2). A finished orchestrated answer in V2 chat offers Repeat on a schedule. The card freezes the plan that just ran, discloses every frozen step (title and capability), the request, time handling, cadence floor, step cap and who it runs as, and saves a paused personal workflow with one plan_replay task only after the user clicks Save workflow.

  • Each run replays the frozen plan as its creator (workflow.user_id), in the workflow's own conversation, through the headless HarnessExecution under the durable workflow lease. The model never adds, removes or rewrites steps; the plan is SHA-256 hashed at save and checked again before every run, and the creator, setting, capabilities and every source the plan reads are re-authorized on every run.

  • Deny by default. Only seven role-free, single-pass capabilities can replay (plan-replay-allowlist-v1). Web search, page fetch, deep research, actions, agents, workflow steps, async waits (including Phase 6c's saved_workflow_run), file deliverables and plans that asked the user a question are refused with a fixed reason per step.

  • Admin: enable_workflow_plan_replay (Chat Orchestration pane), default off, requires enable_chat_orchestration and allow_user_workflows. With it off, behaviour is identical to today (off-golden test); an existing replay workflow fails with a fixed reason instead of crashing or skipping.

Linked issue

Part of #1550

Refs #1543

Release Notes & Latest Features

  • New Feature

  • Bug Fix

  • UI Enhancement

  • Breaking Change

  • Internal only

Is this visible to end users?

  • Yes

  • No

Is this admin-facing (Admin Settings, governance, deployment, config)?

  • Yes

  • No

Should this become a Latest Feature card?

  • Yes

  • No

  • Already added

Screenshot needed for the card?

  • Yes

  • No

  • Attached

Version bump

  • application/single_app/config.py VERSION third segment bumped, or not needed because this is docs-only

    • Context: V2 is 0.261.307; replay is 0.261.308 (version commit 9c18dbfed), followed by cleanup commit 8d3955680; Phase 6c remains 0.261.309.
  • deployers/version.txt bumped, or not needed because deployers/ was not changed

Testing / validation

Setup. Every Python run used the pinned venv (...\files\venv\Scripts\python.exe) with PYTHONPATH=application/single_app;functional_tests, PYTHONIOENCODING=utf-8 and PYTHONUTF8=1. pytest also got -p no:langsmith_plugin -p no:cacheprovider. Runs were one process at a time, on the merged tree at head 964c5c5a0 with VERSION 0.261.307.

Validation heads. The full four-mode and mutation evidence below is from 964c5c5a0 (VERSION 0.261.307); the approved light catch-ups are at 3bd32b057 and 9c18dbfed (VERSION 0.261.308). Current PR head 8d3955680 adds the reviewed CodeQL-quality cleanup without changing the version. On that head, execution + freeze + replay-card tests passed 129 normally; execution + freeze passed 122 under optimized Python.

New Python tests, four ways each. Every run passed. Since the first push, Fix A and Fix B took the freeze file from 79 to 87 cases and the routes file from 29 to 31.

| File | Tests | pytest | pytest -O | script | -O script |

| --- | --- | --- | --- | --- | --- |

| functional_tests/test_workflow_plan_replay_freeze.py | 87 | ✅ | ✅ | ✅ | ✅ |

| functional_tests/test_workflow_plan_replay_execution.py | 27 | ✅ | ✅ | ✅ | ✅ |

| functional_tests/test_workflow_plan_replay_routes.py | 31 | ✅ | ✅ | ✅ | ✅ |

| functional_tests/test_workflow_plan_replay_boundaries.py | 15 | ✅ | ✅ | ✅ | ✅ |

| functional_tests/test_workflow_plan_replay_off_golden.py | 9 | ✅ | ✅ | ✅ | ✅ |

Each full file path above was substituted for <file> in all four commands below. All 20 commands exited 0 (169 checks per mode):


python -W ignore::DeprecationWarning -m pytest -p no:langsmith_plugin -p no:cacheprovider -q <file>

python -O -W ignore::DeprecationWarning -m pytest -p no:langsmith_plugin -p no:cacheprovider -q <file>

python -W ignore::DeprecationWarning <file>

python -O -W ignore::DeprecationWarning <file>

V2 merges since the first push touch code that replay runs through:

The latest merge also brings #1728's composition-to-M365-citations import edge and #1730's conversation-deletion recovery. All five rows above, including execution and boundaries, were run on the tree with every merge included.

Route policy (venv pytest). All passed.

  • route_tests/test_route_blueprint_policy_inventory.py: 15

  • route_tests/test_route_unauthenticated_policy_contract.py: 11

  • route_tests/test_route_policy_test_coverage.py: 3

V2

  • node --test functional_tests/test_v2_workflow_plan_replay_client.mjs (Node): 9/9 pass. The new one checks that the card names the same alert rule the server saves.

  • node --test functional_tests/test_v2_workflow_run_action_clients.mjs (Node, changed by Fix HNS result cleanup and workflow deletion recovery #1726): 14/14 pass.

  • npm --prefix .\application\v2_ui run typecheck: pass.

  • npm --prefix .\application\v2_ui run build: pass. The chunk-size warning was already there.

  • Playwright (venv pytest):

    • ui_tests/test_v2_workflow_plan_replay_card.py: 7 passed. It uses hostile step titles and workflow names, and asserts the card's one-line alert disclosure.

    • ui_tests/test_workflow_classic_advanced_guard.py: 22 passed.

Neighbours (venv pytest, one file at a time). 37 of 44 files passed; the seven failing files match base exactly, as detailed below. Each Python file listed here and under Route policy used python -W ignore::DeprecationWarning -m pytest -p no:langsmith_plugin -p no:cacheprovider -q <file>. Failed files were also compared on base and head with --tb=line -rf, including exact test names and error messages.

  • test_orchestration_actions_admin.py: 8, plus 14 subtests

  • Workflow proposals:

    • test_orchestration_workflow_proposal_routes.py: 60

    • test_orchestration_workflow_propose_capability.py: 89

    • test_orchestration_workflow_proposals_admin.py: 12

    • test_orchestration_workflow_proposal_editor_round_trip.py: 5

    • test_orchestration_workflow_proposal_done_when.py: 2

    • test_orchestration_workflow_proposal_imports.py: 6

  • Harness:

    • test_orchestration_harness_execution.py: 649, plus 2 xfailed

    • test_orchestration_harness_scheduler.py: 213

    • test_orchestration_harness_routes.py: 157

    • test_orchestration_harness_chat_checks.py: 6

  • V2 tests added or changed since the first push's base fa3422dee:

  • Latest V2 merge and shared composition/import neighbours:

    • test_m365_source_citations.py: 42

    • test_m365_mail_calendar_search.py: 17

    • test_orchestration_dependency_imports.py: 11

    • test_orchestration_compose_output_retry.py: 23

    • test_conversation_delete_hns_directory_cleanup.py: 42

    • test_conversations_read_ownership_authorization.py: 10

    • test_v2_scope_and_bootstrap_timings.py: 13

  • Workflow runner and validation:

    • test_workflow_task_sequence.py: 10

    • test_workflow_definition_store_integration.py: 7

    • test_workflow_run_time_context.py: 13

    • test_workflow_merge_task.py: 54

    • test_group_workflow_round_trip_preservation.py: 5

  • Settings:

    • test_admin_settings_field_contract.py: 2

    • test_admin_settings_fields_registry_integrity.py: 5

Base failures. Each of the seven files fails identically on base 587094fb6, compared in a temporary detached worktree: the same tests, with the same failure messages. The cancellation and hardening files came from #1726; the manifest-contract suite was added to this round's shared-source neighbours.

| File | Fails on both base and head | Notes |

| --- | --- | --- |

| test_workflow_task_document_actions.py | test_document_run_items_are_task_scoped, test_task_document_action_failures_stay_inside_the_retry_loop | The other 14 pass |

| test_personal_workflows_feature.py | test_personal_workflows_feature_wiring | |

| test_generic_workflow_automation.py | test_generic_workflow_ui_defaults_to_no_document_action, test_generic_workflow_runner_retains_direct_execution_paths | Asserts VERSION = "0.250.065" and a string that's absent from both base and head |

| test_group_workflows_feature.py | test_group_workflow_feature_contracts | 34/41 checks pass on both. Standalone execution also reports 34/41 and exits 1 on both. |

| test_workflow_cancellation.py | test_runner_cancellation_helpers_cover_personal_and_group_items, test_runner_returns_cancelled_terminal_state_and_clears_active_run, test_m365_authorization_denial_finishes_the_run_instead_of_waiting_again | Under pytest, NameError for current_workflow_execution and capture_execution_identity inside the runner on both; the other 5 pass. As a script (python functional_tests/test_workflow_cancellation.py) it exits 0 on both. |

| test_mixed_source_hardening.py | 7 of 21: test_handoff_exposes_the_same_bounded_envelopes_used_for_synthesis, test_compare_failed_source_is_fatal_and_failed_target_does_not_stop_later_targets, test_manifest_and_tabular_cancellation_stop_work_without_failure_downgrade, test_narrative_comparison_and_reduction_cancellation_stop_later_work, test_foundry_context_opt_out_filters_mixed_evidence_for_all_runtime_shapes, test_mixed_analyze_forwards_observed_native_token_usage, test_analyze_all_action_is_analyze_only_and_manifest_is_fresh | The same 7 messages on both, starting with a Cosmos Unrecognized credential type (no Cosmos key in this environment). As a script it stops at the same Cosmos error on both. |

| test_mixed_source_manifest_contracts.py | 9 of 15: test_mixed_classification_order_and_partition, test_xsd_summary_evidence_preserves_schema_source_kind, test_duplicates_and_cross_scope_filename_identity, test_unresolved_and_unsupported_do_not_erase_valid_sources, test_personal_group_public_and_chat_authorization, test_phase_2_evidence_execution_and_partial_coverage, test_restrictive_manifest_scope_fails_closed_and_preserves_storage_locator, test_batch_authorization_snapshot_and_source_limit, test_manifest_diagnostics_are_aggregate_only | The same 9 names and messages on both, including missing Cosmos credentials, held-document errors and manifest assertions; the other 6 pass. |

Docs (venv python, through __main__)

  • python .\scripts\build_docs_inventory.py leaves the committed docs/_data/app_surface.yml unchanged. The file was regenerated during the first merge, not hand-merged, and the subsequent merges didn't touch it. Against 587094fb6 it adds enable_workflow_plan_replay, taking the capabilities from 129 to 130.

  • test_docs_app_surface_coverage.py: 7/7.

  • test_docs_site_quality.py: 6/6.

  • test_docs_link_integrity.py: 2/5, the same as base 587094fb6. Both have the same 11 broken relative links and 23 site links that don't resolve. This PR's 7 new relative links and 2 new site links all resolve. It also declares one screenshot slot, reference/chat-controls-saved-chat-plan-card.png, which shows a placeholder until the image exists.

  • test_docs_release_notes_integrity.py: 0/1, the same as base. The generated release-notes pages are stale on base too (234 releases missing); the only new missing release is this PR's v0.261.308 (autonomous decision 15).

Approved light catch-ups (no four-mode or mutation restart)

| Head | Catch-up | Result |

| --- | --- | --- |

| 3bd32b057 (.307) | #1729; only the route-policy inventory was touched on both sides | Five replay suites plus all three route-policy suites: 198 passed. Docs coverage 7/7, quality 6/6, all six guardrails passed, clean tree. |

| 9c18dbfed (.308) | #1731; MessageList, shared harness fixture, chat controls and config | Five replay suites, three route-policy suites, replay card, M365 pending-action cards and Classic guard: 237 passed. Typecheck and build passed; docs coverage 7/7, quality 6/6; all six guardrails passed; clean tree. |

The second catch-up used the same pytest command and flags above, with these exact targets in one invocation:


functional_tests\test_workflow_plan_replay_freeze.py

functional_tests\test_workflow_plan_replay_execution.py

functional_tests\test_workflow_plan_replay_routes.py

functional_tests\test_workflow_plan_replay_off_golden.py

functional_tests\test_workflow_plan_replay_boundaries.py

functional_tests\route_tests\test_route_blueprint_policy_inventory.py

functional_tests\route_tests\test_route_unauthenticated_policy_contract.py

functional_tests\route_tests\test_route_policy_test_coverage.py

ui_tests\test_v2_workflow_plan_replay_card.py

ui_tests\test_v2_m365_pending_action_cards.py

ui_tests\test_workflow_classic_advanced_guard.py

The UI rows are replay card 7, pending-action cards 10 and Classic guard 22. npm --prefix .\application\v2_ui run typecheck and npm --prefix .\application\v2_ui run build both exited 0 on the final head.

The initial replay-card probe correctly caught V2's new GET /api/msgraph/pending-actions as an unexpected request. Commit 433788790 reused V2's shared empty-list fixture before tracking replay requests; the probe then passed 7/7 and the final combined run passed. Unknown requests remain strict failures, and Repeat on a schedule still performs no replay request until clicked.

The inventory did not need another merge because neither catch-up changed docs/_data; coverage confirmed it is still current. The integrity failures above are the full-run results, not claims that the two integrity scripts were rerun in the light catch-ups. The replay release-notes header was subsequently renumbered to .308.

Mutation testing. 54/54 mutations were killed; every revert left git diff --quiet -- application clean. M1–M15 ran on 026c7f82e, M16 on 1d544fb21, and fixes M17–M18 on the later replay tree.

Mutation kill table

| ID | Mutation | Failing tests (file::test) | Killed | Clean after revert |

| --- | --- | --- | --- | --- |

| M1a | Run as a non-creator: the actor's id drives the replay and the explicit actor check is gone | replay_execution::test_only_the_creator_runs_the_replay | yes | yes |

| M1b | Creator check accepts any recorded creator | replay_freeze::test_only_the_creator_is_ever_the_actor, replay_execution::test_only_the_creator_runs_the_replay | yes | yes |

| M2a | Skip the per-run re-authorization (context built from the task) | replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one, replay_off_golden::test_a_saved_plan_refuses_with_its_fixed_reason_while_off[halt] | yes | yes |

| M2b | Skip the run-time source re-check | replay_freeze::test_every_source_is_rechecked_as_the_creator | yes | yes |

| M2c | Skip the run-time capability re-check | replay_freeze::test_a_capability_the_admin_removed_or_turned_off_is_refused, replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one | yes | yes |

| M2d | Skip re-reading the stored workflow before step 1 | replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one | yes | yes |

| M3a | Accept a hash mismatch at run time | replay_freeze::test_the_stored_task_runs_only_while_its_hash_is_unchanged | yes | yes |

| M3b | Accept a stored plan whose hash changed after admission | replay_execution::test_a_deleted_or_changed_workflow_stops_before_step_one | yes | yes |

| M3c | Accept a save without the previewed hash | replay_routes::test_saving_needs_the_previewed_hash[None] | yes | yes |

| M3d | Let an ordinary save edit the frozen plan | replay_routes::test_the_frozen_plan_stays_read_only_through_the_ordinary_save, replay_freeze::test_any_edit_to_the_frozen_plan_through_a_save_is_refused | yes | yes |

| M4a | Allow deep_research | replay_freeze::test_each_refused_class_has_its_fixed_reason[deep_research-arguments6-capability_not_replayable-can't | yes | yes |

| M4b | Allow the external-effect and workflow capabilities | replay_freeze::test_each_refused_class_has_its_fixed_reason[workflow_run-arguments1-capability_not_replayable-can't | yes | yes |

| M4c | Allow the Phase 6c saved_workflow_run wait | replay_freeze::test_every_refused_wait_kind_is_refused[saved_workflow_run] | yes | yes |

| M4d | Allow the role-gated web_search | replay_freeze::test_each_refused_class_has_its_fixed_reason[web_search-arguments10-role_required-needs | yes | yes |

| M4e | Remove the step budget | replay_freeze::test_the_step_budget_and_an_empty_plan | yes | yes |

| M4f | Remove the run-time budget | replay_execution::test_an_exhausted_budget_cancels_and_fences_the_replay | yes | yes |

| M5a | Allow elicitation at freeze | replay_freeze::test_freeze_refuses_runs_it_must_not_repeat[changes10-elicitation_not_replayable], replay_routes::test_a_run_that_must_not_repeat_cannot_be_saved[changes3-elicitation_not_replayable] | yes | yes |

| M5b | Allow elicitation in a stored plan at run time | replay_freeze::test_a_stored_plan_that_asks_a_question_is_refused_before_step_one | yes | yes |

| M6a | Run in the original chat | replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation | yes | yes |

| M6b | Accept the source chat as the replay conversation | replay_execution::test_the_source_chat_is_never_the_replay_conversation | yes | yes |

| M6c | Accept a conversation that is not a workflow conversation | replay_execution::test_only_the_private_workflow_conversation_is_accepted[changes0] | yes | yes |

| M7a | Widen roles in the harness identity | replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation | yes | yes |

| M7b | Widen roles in the run-time capability check | replay_freeze::test_manual_and_scheduled_runs_are_allowed_the_same_capabilities | yes | yes |

| M8a | Let the model rewrite a step's instruction | replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation | yes | yes |

| M8b | Let the model drop a step | replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation | yes | yes |

| M8c | Resolve the run-time line in UTC instead of the schedule's time zone | replay_execution::test_a_frozen_plan_replays_end_to_end_in_the_workflow_conversation | yes | yes |

| M9a | Skip the setting gate | replay_off_golden::test_a_saved_plan_refuses_with_its_fixed_reason_while_off[halt], replay_freeze::test_every_run_rechecks_the_settings[enable_workflow_plan_replay-replay_disabled] | yes | yes |

| M9b | Off path: every run item gets a plan_replay projection | replay_off_golden::test_workflows_without_a_saved_plan_match_the_base[task_sequence] | yes | yes |

| M9c | Off path: the setting defaults on | replay_off_golden::test_the_setting_is_off_by_default_and_saves_only_a_real_true | yes | yes |

| M9d | Off path: the setting saves any truthy value | replay_off_golden::test_the_setting_is_off_by_default_and_saves_only_a_real_true | yes | yes |

| M9e | Off path: every task takes the replay branch | replay_off_golden::test_workflows_without_a_saved_plan_match_the_base[task_sequence] | yes | yes |

| M10 | Show the typed result when the task output is withheld | replay_execution::test_a_withheld_result_also_withholds_the_typed_projection[False] | yes | yes |

| M11a | Create a group replay workflow | replay_freeze::test_group_workflows_are_refused_at_creation | yes | yes |

| M11b | Run a group replay workflow | replay_freeze::test_group_workflows_are_refused_at_run_time[changes0], replay_execution::test_only_the_creator_runs_the_replay | yes | yes |

| M12a | Skip the cadence floor | replay_routes::test_a_schedule_faster_than_the_chat_minimum_is_refused | yes | yes |

| M12b | Skip the cadence floor when a replay workflow is re-saved | replay_routes::test_the_frozen_plan_stays_read_only_through_the_ordinary_save | yes | yes |

| M13a | The orchestration scheduler's due-run query picks up replays | replay_boundaries::test_the_due_run_query_excludes_workflow_replays | yes | yes |

| M13b | The orchestration scheduler drives a replay it reaches | replay_boundaries::test_a_replay_that_reaches_the_scheduler_is_deferred_untouched | yes | yes |

| M14a | The builder saves a replay workflow that is not durable | replay_routes::test_saving_creates_one_paused_personal_workflow_that_repeats_the_frozen_plan | yes | yes |

| M14b | Skip reconciling earlier attempts | replay_execution::test_a_durable_restart_settles_the_dead_attempt_and_replays_once | yes | yes |

| M14c | Repeat a completed earlier attempt instead of adopting it | replay_execution::test_a_completed_prior_attempt_is_adopted_not_repeated | yes | yes |

| M14d | Adopt a completed earlier attempt of another frozen plan | replay_execution::test_a_completed_prior_attempt_of_another_plan_is_refused | yes | yes |

| M14e | Stop a live earlier attempt without fencing it | replay_execution::test_a_live_prior_attempt_is_stopped_and_fenced_but_never_taken_over | yes | yes |

| M14f | Leave a settled earlier attempt unfenced | replay_execution::test_a_durable_restart_settles_the_dead_attempt_and_replays_once | yes | yes |

| M14g | Treat a dead earlier attempt as settled without settling its run | replay_execution::test_a_durable_restart_settles_the_dead_attempt_and_replays_once | yes | yes |

| M14h | A stopped attempt never settles its own run | replay_execution::test_an_interrupted_attempt_settles_its_own_run | yes | yes |

| M14i | A stopped attempt cannot settle a run its own lease still holds | replay_execution::test_an_interrupted_attempt_settles_its_own_run | yes | yes |

| M15a | The runner's replay unit is not replay-safe | replay_execution::test_the_runner_replays_one_durable_unit_numbered_by_its_own_attempt | yes | yes |

| M15b | The runner numbers a durable replay by its in-process retry index | replay_execution::test_the_runner_replays_one_durable_unit_numbered_by_its_own_attempt | yes | yes |

| M15c | The run item drops the typed replay result | replay_execution::test_the_runner_replays_one_durable_unit_numbered_by_its_own_attempt | yes | yes |

| M16 | Card shows a doubled step prefix (Step 1: Step 1 (...)) | ui_tests/test_v2_workflow_plan_replay_card.py::test_ineligible_preview_shows_refusals_without_save_form | yes | yes |

| M17 | Fix B: drop the default 'Run failed' alert rule | replay_routes::test_a_saved_replay_workflow_starts_with_one_editable_run_failed_alert, replay_routes::test_a_refused_replay_run_reaches_the_bell_with_its_fixed_reason | yes | yes |

| M18a | Fix A: ignore Phase 6c's stored wait marker when freezing | replay_freeze::test_a_waited_workflow_run_step_is_refused_as_a_wait_when_frozen | yes | yes |

| M18b | Fix A: let a wait marker that names no running step through | replay_freeze::test_a_wait_marker_that_matches_no_running_step_refuses_the_whole_plan[marker0] | yes | yes |

Guardrails

I ran the coordinator's guardrails script on the full-validation tree at 964c5c5a0 and again on both light catch-ups. The final run is at 9c18dbfed, with the venv python, against V2 08def99f7:

run_v2_guardrails.py <worktree> 08def99f7a39e20376bee354db8b7fc838a9ae0e HEAD replay

It exits 0. It reviewed 41 changed files: 12 app .py, 21 on the XSS surface, 12 route .py and 7 V2 ts/tsx.

| Check | Result |

| --- | --- |

| broken-access-control | PASS (12 files) |

| xss-sinks | PASS (21 files) |

| swagger-routes | PASS (2 changed route files) |

| python-syntax | PASS (543 files compile) |

| malicious-pr-review | PASS. 1234 marker findings, 0 blockers; verdict "Needs investigation". |

| v2-ts-sinks (added lines) | PASS (no sink patterns in 7 ts/tsx files) |

The malicious-PR review flags changed lines by keyword marker, for example "AI, plugin, agent or workspace boundary" and "security control". Most findings are in the replay module and its tests, which deal with authorization and refusals by design. The full run had 1231 marker findings and zero blockers; the final run adds three markers for the closed-API fixture's empty pending-action list. These are review markers, not 1234 confirmed vulnerabilities.

The earlier 17 "dynamic execution, persistence, or system access" findings were reviewed: lines containing "empty" in prose/tests, plus subprocess or execFileSync in the fresh-process import probe, the off-golden fixture writer (git rev-parse HEAD, only with --write-golden) and the V2 client's AST-only constants/registry read. The final fixture uses only the shared local test helper and never broadens the unknown-request fallback.

Documentation

  • Release notes updated, or not needed

    • Context: new v0.261.308 section; prior V2 release-note sections are preserved.
  • Feature documentation updated, or not needed

    • Context: (docs/explanation/features/WORKFLOW_PLAN_REPLAY.md; O2 status in WORKFLOW_M5C_COMPLETION_AND_NEXT_STEPS.md)
  • Fix documentation updated, or not needed

    • Context: (not needed)

Also: docs/admin/orchestration.md settings row (the toggle lives in the Chat Orchestration pane, so the row sits with the other orchestration limits it depends on), docs/_data/features.yml key claim, regenerated docs/_data/app_surface.yml, docs/guides/create-a-workflow.md ("Repeat a chat plan on a schedule") and docs/reference/chat-controls.md ("Saved chat plans (V2 interface)").

Security checklist

  • New Flask routes include @swagger_route(security=get_auth_security())

    • Context: (GET/POST /api/v2/orchestration/runs/<run_id>/plan-replay, with @login_required, @user_required, @enabled_required('allow_user_workflows') and @workflow_user_required; the replay setting and Chat Orchestration are checked inside, with fixed refusals; route-policy inventory and unauthenticated contract updated)
  • Settings sent to non-admin frontends use sanitize_settings_for_user()

    • Context: (no new settings payloads; the V2 gate reads the existing sanitized bootstrap feature flags)
  • Browser JavaScript is served from local SimpleChat static assets only; no CDN-hosted JS

  • No secrets, keys, connection strings, or local-only artifacts are included

Decisions (coordinator decisions 1–8)

  1. Roles: option (c), a fixed empty-roles identity plus option (a)'s role-free allowlist. Freeze, every run and the harness identity use user_roles=[] and user_enable_agents=False, so a manual run and a scheduled run see the same capabilities: neither consults the trigger's roles. The only role-gated capability on the registry, web_search, is refused at freeze as role_required. Nothing is ever widened, and there's no stale snapshot to expire. A server-side role snapshot (option b) is a follow-up.

  2. Replayable list (plan-replay-allowlist-v1), deny by default.

    | Capability | Why it can repeat |

    | --- | --- |

    | document_search | Searches documents the creator can still open; access is re-checked before every run. |

    | document_analyze | Analyzes documents the creator can still open, with the frozen instructions. |

    | document_compare | Compares documents the creator can still open, with the frozen instructions. |

    | document_merge | Merges documents the creator can still open into a file kept with the run. |

    | tabular_inspect | Reads the shape of tabular files the creator can still open; it finishes in the step. |

    | compose | Writes the answer from earlier step results with the frozen instructions. |

    | generate_image | Creates a new image from the frozen prompt; reference images are refused. |

    Everything else is refused: workflow_* and the external-effect and approval-floor capabilities (capability_not_replayable), web_search (role_required), tabular_analyze, render_file, file deliverables and every async wait kind including saved_workflow_run (replay_wait_unsupported), url_fetch, deep_research, tabular_merge and anything unlisted (capability_not_replayable), elicitation (elicitation_not_replayable), and more than 8 steps (replay_budget_exceeded).

  3. Creation path: (B), a "Repeat on a schedule" action on the completed plan's answer. It's deterministic (no model chooses what is frozen), freezes exactly the run the user saw, and needs no planner change. The card fetches a server-built preview (GET .../plan-replay?conversation_id=) only on click and discloses the request, time handling, cadence floor, step cap, "Runs as", every frozen step (N. title + capability label) and any per-step refusal. Save workflow is the manual approval; the POST re-freezes, re-checks sources and requires the previewed plan_sha256. The workflow is created paused unless the user turns the schedule on.

  4. Execution: reuse HarnessExecution through a thin adapter. Each attempt creates a new orchestration run in the workflow's conversation, with ids derived from (workflow run, task, attempt) and a workflow_replay marker. The durable workflow unit numbers attempts across worker restarts (max 3), so a resumed attempt never reuses a dead worker's identity. The orchestration scheduler excludes workflow_replay runs (due-run query and per-run guard), so only the workflow runner drives them. Before a new attempt, earlier attempts are reconciled: a completed one with the same hash is adopted, a dead one is settled as ownership_lost and fenced, and a still-live one is stopped and fenced or the attempt fails after ~55 s. The workflow lease is checked through the runner's cancel check on every step, and each attempt self-settles through a CAS.

  5. Relative time: frozen as written, plus a server-built run-time line ("current local time") in the schedule's time zone (then the frozen chat time zone, then UTC). The card's Time row discloses it. Waits and budgets: steps that wait asynchronously are refused in this slice; a run is limited to 8 steps and 900 s, with a 30 s stop grace.

  6. Stored shape: task type: plan_replay with plan_replay = {version: 1, allowlist_version, request, frozen_plan, frozen_seeds, plan_sha256, approval: {approved_by, approved_at, plan_sha256}, provenance: {source_run_id, source_conversation_id, created_by, frozen_at, time_handling, time_zone}}. The hash covers {task_version, allowlist_version, request, plan, seeds}. tasks is part of WORKFLOW_DEFINITION_FIELDS, so the definition revision changes with any edit; the ordinary save refuses changes to the frozen plan (plan_replay_read_only) and keeps every non-editable key at its stored value.

  7. Refused at run time: the run fails before step 1 with the fixed reason (see Refusal mapping); it's not retried. The run inspector shows the reason as the task and run error. Alerts follow the workflow's own alert settings, as for any failed run; when an alert fires, its body carries the same fixed reason. Nothing new goes to the bell.

  8. Setting off / capability removed: existing replay workflows stay saved and editable (name, schedule, alerts) but every run fails with replay_disabled, orchestration_disabled, personal_workflows_disabled or capability_unavailable. Turning the setting or capability back on lets the next run proceed; nothing is deleted or rewritten.

Refusal mapping

| Code | Where | Card | Editor | Run inspector / alerts |

| --- | --- | --- | --- | --- |

| replay_disabled | save, run | Button hidden (feature gate); a stale save shows the fixed text inline | Unchanged | Run fails: "Repeating chat plans is turned off. Ask an admin to turn it on, then run the workflow again." |

| orchestration_disabled / personal_workflows_disabled | save, run | Button hidden; inline text on a stale save | Unchanged | Run fails with the fixed text |

| group_not_supported | group save | — | Group save refused with "Only personal workflows can repeat a chat plan." | — |

| creator_mismatch | run | — | — | Run fails: "This saved plan can only run as the person who created it." |

| plan_hash_mismatch | save, run | Inline alert + Reload plan | — | Run fails: "This saved plan changed after it was approved. Create it again from chat." |

| allowlist_version_unsupported | run | — | — | Run fails with the fixed text |

| capability_not_replayable, role_required, replay_wait_unsupported, elicitation_not_replayable, replay_budget_exceeded (step cap) | preview, save, run | Preview lists each as "Step N (label) …" or "Whole plan: …" under "This plan has replay notes:" and shows no save form | — | Run fails with the code's fixed text |

| capability_unavailable | save, run | Inline fixed text | — | Run fails: "A step in this plan is turned off or no longer available." |

| source_unavailable | save, run | Inline fixed text | — | Run fails: "A document, group or public workspace this plan reads is no longer available to you." |

| source_run_not_eligible, shared_conversation_not_allowed, conversation_context_not_replayable | preview, save | Preview note / inline fixed text | — | — |

| replay_budget_exceeded (time) | run | — | — | Run fails: "The repeated plan took longer than its time limit and was stopped." |

| replay_execution_failed | run | — | — | Run fails: "The repeated plan could not finish. Open the run for details." |

| workflow_unavailable | run | — | — | Run fails: "This workflow was deleted or is being deleted, so the saved plan didn't run." |

| plan_replay_read_only | edit | — | Frozen steps are shown read-only; a save that changes the frozen plan, the task or its type is refused with "A saved plan can't be edited. Create it again from chat." Classic shows a toast and routes editing to V2 | — |

| workflow_replay_run_managed | chat run/retry/edit/revisions/cancel | — | — | 409 from the chat routes: "This plan runs as part of a saved workflow. Open the workflow to run or cancel it." |

| quota_exceeded, model_unavailable, workflow_conflict, run_not_found, invalid_request, service_unavailable | save | Inline fixed text | — | — |

The client mirrors the server's REFUSAL_MESSAGES exactly (test_v2_workflow_plan_replay_client.mjs asserts equality), so model prose never reaches these surfaces.

Decisions made autonomously

Each of these is the conservative, reversible option.

  1. Setting name. I kept the suggested enable_workflow_plan_replay.

  2. Roles: option (c). A fixed, empty-roles identity on every path, plus option (a)'s role-free allowlist. A role snapshot (option b) is a follow-up.

  3. Creation path (B). "Repeat on a schedule" sits on a completed plan's answer. It's deterministic, freezes exactly the run the user saw, and needs no planner change.

  4. The admin row is in docs/admin/orchestration.md. The toggle sits in the Chat Orchestration pane, next to the cadence floor and workflow quota it depends on.

  5. Waits are refused in this slice. That covers tabular_analyze, render_file, file deliverables and every async wait kind, including Phase 6c's saved_workflow_run. deep_research, url_fetch and tabular_merge are refused too.

  6. Workflows are kept, not disabled or deleted, when the setting is turned off or a capability is removed. Every run fails with the fixed reason until the setting or capability is back.

  7. The default "Run failed" alert is restored per the approved plan (Fix B, 9f87bf1c4). A new replay workflow starts with one editable high-priority, notify_only rule for failed or completed_with_task_errors, disclosed on the card and in the documentation. The stable rule is created server-side, never re-added when a creator edits/removes it, and the tests and mutation M17 cover its behavior. alert_priority remains none, so this does not create a popup for every run.

  8. Creation is idempotent per (source run, plan hash). Saving the same plan twice returns the existing workflow with a 200 instead of a 201.

  9. Only the workflow manages a replay run.

    • The chat run, retry, edit, revisions and cancel routes return 409 workflow_replay_run_managed for a replay orchestration run.

    • A replay run can't be frozen again; that's refused as source_run_not_eligible.

  10. Budgets. At most 8 steps and 900 s per run, with a 30 s stop grace.

  11. The workflow conversation gets both the orchestration answer and the workflow's normal result message. This changed from the approved plan. The plan made the harness answer the only visible answer, with a pointer message. Collapsing them is a follow-up.

  12. Status codes. replay_disabled and creator_mismatch are 403, plan_hash_mismatch is 409 and quota_exceeded is 429. Every code maps to one of 403, 404, 409, 422, 429 or 503.

  13. The Phase 6c constant is mirrored locally as SAVED_WORKFLOW_RUN_WAIT_KIND = 'saved_workflow_run', not imported, so this PR doesn't depend on 6c landing first. A test refuses it by name.

  14. Card refusal text.

    • Step refusals render as the server writes them, "Step N (label) …". Only whole-plan refusals get a "Whole plan: " prefix.

    • I removed the unused workflow_replay_managed code from the server table and its V2 mirror.

  15. The release-notes pages weren't regenerated. test_docs_release_notes_integrity stays at its base result, 0/1, because regenerating would rewrite 200+ unrelated pages.

CodeQL

CodeQL Advanced completed successfully on 8d3955680: Analyze (actions), Analyze (javascript-typescript), and Analyze (python) all passed. The PR-head code-scanning query (refs/pull/1724/head) returns no alerts. The seven initial quality annotations from 9c18dbfed were addressed in the cleanup commit: redundant inherited public_message assignments were removed, fatal worker signals are re-raised after settlement, and regression tests cover inherited exception contracts and fatal exits. No alerts were dismissed.

Follow-ups

None of these is a new phase. Each one fits under #1550.

Capabilities

  • Bounded in-run waits, so render_file, tabular_analyze and file deliverables can replay.

  • web_search, deep_research and url_fetch, through a server-side role snapshot with a maximum age (option b).

  • Replaying tabular_merge.

  • Microsoft 365 steps, through the workflow's own M365 connection with Run as = self.

Run results and recovery

  • A failed replay records its fixed reason, but no typed result.

  • The workflow conversation gets both the orchestration answer and the workflow result message. Collapse them to one.

  • A durable restart starts a new orchestration attempt instead of resuming the same orchestration run.

  • A worker that doesn't stop within the 30 s grace is fenced and settled, but not finalized any further.

Creation and the card

  • On a save refusal, the card shows only the fixed message. Linking the refused step would help.

  • A chat creation path, option (A), through workflow_propose.

  • Plans that reference earlier conversation messages, prompt or agent seeds, or image proposals.

Other

  • Group workflows.

  • HMAC binding of the frozen plan against direct database writes.

  • The docs inventory doesn't cover V2 chat controls.

  • The docs release-notes pages are stale for 200+ versions (pre-existing).

Paul Lizer (paullizer) and others added 30 commits October 8, 2026 10:31
Default off; depends on chat orchestration and personal workflows.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…light (WIP)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Calls _authorize_execution on the current workflow, refuses a deleted workflow or changed plan, drops the duplicate capability and source checks, and reads task_results as a dict.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The workflow task loop sends plan_replay tasks to execute_plan_replay_task
as the workflow's creator and never through an agent or model runner.
Group workflow saves refuse replay tasks with group_not_supported.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The due-run query excludes runs marked workflow_replay and _process_run
defers any that slip through, so only the owning workflow run drives,
cancels and fences a replay.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ive a replay

- GET/POST /api/v2/orchestration/runs/<run_id>/plan-replay preview and save a completed,
  approved plan as a personal repeating workflow, behind the personal workflow gates.
- Chat-side run, retry, edit, revisions and cancel refuse replay runs with
  workflow_replay_run_managed, so a session's roles can never drive a frozen replay.
- A cancelled workflow run now stops and fences its replay.
- Route policy tests cover the new routes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A browser JSON round trip turns 1.0 into 1, so the save guard compares the
frozen plan number-neutrally. A saved chat plan keeps the chat-workflow
cadence floor whenever its schedule is saved, not only at creation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A schedule faster than the chat minimum, or another reviewed settings error,
now returns its own message and code with 422 instead of the generic
service-unavailable answer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The runner imports the replay module only for a plan replay task, so other
workflows keep their import graph. Saving a replay workflow re-checks every
source the plan reads, and group sources fail closed when the group setting
is missing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Every compiled plan declares the implicit answer deliverable, so refusing any
deliverable refused every plan. Only file deliverables (which need render_file)
and unknown kinds are refused now. The freeze tests cover the allowlist, each
refused class, the hash, edits, per-run re-authorization and source re-checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the end-to-end replay test under the real headless executor with
the model stubbed, plus cancel, budget and lost-lease fencing, the
workflow-conversation binding and creator checks. A re-entered replay
attempt now fails with the fixed replay_execution_failed code.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Covers the preview disclosure, the paused personal create, hash and
eligibility refusals, settings and cadence gates, shared conversations,
lost group access, store failures, delete then re-create, the read-only
save guard, and the chat routes that must never drive a replay run.

Saving a replay workflow now uses the read-only user-settings and
document seams the chat proposal create uses, so a save never repairs
user settings or refreshes a profile image.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cold-import probes across five import orders (normal and optimized), static
checks that the replay module never reaches the web layer and that its
callers import it lazily, and scheduler checks that replays are excluded
from the due query and deferred untouched if one slips through.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The run inspector reads each task's run item, but a workflow result summary
only carries {kind, result_ref} per output, so the typed replay result was
never on the item. The replay task now returns a projection whose answer is
capped at 4000 characters with a truncated flag; the runner stores it on the
replay task's run item only. The stored json result keeps the full value.
Execution ids and artifact fields are coerced to strings, and a withheld
result also withholds the projection.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A completed chat plan gets a Repeat on a schedule card that fetches the
server preview on click, discloses every frozen step, the schedule, the
run time zone and the date handling, and creates a paused workflow only
on an explicit save. The editor shows a saved chat plan read-only with the
name, schedule and alerts editable, the run inspector renders the typed
replay result, and the Classic UI routes saved chat plans to V2.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Workflows without a saved plan match a golden captured on fa3422d: the
task sequence (dispatch shapes and saved run items), the personal save,
the group build and the history redaction. A saved plan refuses with its
fixed reason while the setting is off, under halt and continue.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ay executor

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Correct the builder to durable_execution True per the approved plan. The runner
numbers replay attempts from the durable unit so a restarted worker never reuses
a dead attempt's orchestration identity, and marks the replay unit replay-safe.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A durable restart re-enters the replay task with the next attempt number. Before
starting, the executor now settles every earlier attempt of the same task: a completed
run of the same frozen plan is adopted rather than replayed, a run whose worker died is
marked failed (ownership_lost) by CAS and fenced, and a run that still holds a live
lease is asked to stop and fenced, failing this attempt cleanly after one lease period
rather than taking the lease over.

Each attempt also settles its own orchestration run when its worker has stopped without
completing it (claim failure, execution error, non-completed final status, and the
cancel, budget and lease-loss paths after the stop grace), since the orchestration
scheduler leaves replay runs to the workflow. CAS reads use the raw ETag-bearing record.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Drive the runner's real task sequence through a worker that dies mid-replay
and a second worker that resumes it, so the replay-safe unit and its durable
attempt numbering are both pinned. The test exposed a crash when the answer
message carries no orchestration metadata; the typed result now tolerates it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Step refusals from the server already start with "Step N (label)", so the
save card showed "Step 2: Step 2 (...)". The card now renders a step
refusal's message as-is and prefixes only whole-plan (step 0) refusals.
The browser fixture now uses server-shaped refusals and asserts exact text.

Also remove the never-raised workflow_replay_managed refusal code from
the server table and its V2 mirror.

Refs microsoft#1550

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add the WORKFLOW_PLAN_REPLAY feature doc, the Repeat Chat Plans On A
Schedule admin row in docs/admin/orchestration.md, the user guide
subsection in create-a-workflow.md, the saved chat plans section in
chat-controls.md, the M5C O2 status, and the v0.261.303 release notes.
Claim enable_workflow_plan_replay in features.yml and regenerate the
docs inventory.

Part of microsoft#1550
Refs microsoft#1543

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) and others added 2 commits October 8, 2026 15:36
The approved plan chose option (c): a fixed, empty-roles identity on every
path combined with option (a)'s role-free allowlist. The feature doc now
uses the same label.

Part of microsoft#1550

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Part of microsoft#1550

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) marked this pull request as ready for review October 8, 2026 20:15
Paul Lizer (paullizer) and others added 13 commits October 8, 2026 16:28
…w-task

Bring the branch up to V2 e1657ef (VERSION 0.261.304).

Conflict resolution:
- application/single_app/config.py: take V2's VERSION line. The final
  commit sets this branch's version.
- docs/explanation/release_notes.md: every V2 section is kept
  byte-identical, including V2's own 0.261.303 section. The plan replay
  section sits verbatim on top; the final commit renumbers its header.
- ui_tests/fixtures/orchestration/harness_entry.tsx: keep both sides
  (the plan replay editor and run history fixtures, plus V2's M365Cards).
- docs/_data/app_surface.yml: regenerated with
  scripts/build_docs_inventory.py, not hand-merged. Identical to git's
  auto-merge.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Phase 6c records which workflow_run step waits as
plan['workflow_run_waits'] = {step_id: {'version': 1, 'workflow': handle}}.
Contract normalization drops that key, so freeze_source_run now reads it
from the source run first and passes it to classify_plan_steps.

- Each enabled step the marker names gets one replay_wait_unsupported
  refusal ("Step N (label) waits for a saved workflow run to finish..."),
  in place of capability_not_replayable.
- A marker that names no enabled step, or can't be read, refuses the whole
  plan with replay_wait_unsupported (fail closed).
- The step-level wait-kind check and the mirrored constant stay.

Tests go through freeze_source_run with 6c's stored shape: a matched step,
unmatched and unreadable markers, and absent or empty markers that change
nothing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A saved plan-replay workflow usually runs on a schedule nobody watches, so a
run that is refused (for example after an admin turns the setting off) or that
fails must still reach its creator. This restores the approved plan's default
alert (deviation microsoft#7 reverted).

- create_plan_replay_workflow saves the workflow with alert_mode 'rules' and
  exactly one rule, 'Run failed': severity high, delivery notify_only (bell
  only, never a pop-up), scope final, condition run_status failed or
  completed_with_task_errors. It goes through normal alert normalization and
  stays editable; a re-save returns the existing workflow unchanged, so the
  rule is never duplicated or put back.
- The V2 Save this chat plan card discloses it before saving: "If a run fails,
  you'll get a 'Run failed' notification in the bell. You can change this in
  the workflow's alerts."
- Tests: the exact stored rule and idempotent re-save; a refused scheduled run
  driven through the production runner reaches the bell once, with the fixed
  replay_disabled reason, linking to the workflow's own conversation, with no
  request, plan or model text and without reaching the harness; the client
  notice's parity with the server rule name; Playwright asserts the notice.
- Docs: feature doc (Default alert section, refusals, test table), user guide,
  admin row, chat-controls reference and release notes.

Part of microsoft#1550
Refs microsoft#1543

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The coordinator assigned 0.261.305 after V2 moved to 0.261.304. This sets
config.py to 0.261.305 and renumbers only this branch's own 0.261.303
references: the feature, M5C, admin, guide and chat-controls docs, the top
release-notes section, and the plan-replay test headers and version floors.
V2's own (v0.261.303) release-notes section is unchanged.

Part of microsoft#1550
Refs microsoft#1543

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 moved past e1657ef before the push. Merged with rerere off; no conflicts. config.py carries the same 0.261.305 line on both sides, and functions_saved_analysis.py merges independent hunks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 reached 0.261.305 with microsoft#1726 (817f9dd) before the push, so this branch takes V2 tip+1. Renumbers only this branch's own 0.261.305 references (the same 30 lines in 15 files that 90947b2 set); microsoft#1726's own 0.261.305 references are untouched.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…o the plan-replay branch

Merged with rerere off. The only conflict was docs/explanation/release_notes.md:
V2's file is kept byte-identical and this PR's section sits on top of it as a pure
insertion. chat-controls.md and the two route-policy tests auto-merged with
independent hunks. config.py merged cleanly because both sides are at 0.261.306;
this PR's references move to 0.261.307 in the following commit.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 587094f is at 0.261.306, so this PR takes the next version. Only this PR's own
references move from 0.261.306 to 0.261.307: the same 30 lines in 15 files that the
previous renumber touched. V2's sections and headers are unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Approved light catch-up after the full .307 validation and mutation table. The shared route-policy inventory merged independent catalogue and replay additions. Keep V2's .306 config line in this merge so the separate last commit restores tip+1 (.307); no other replay references change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 11d8478 remains at .306, so tip+1 is still .307. Keep the application version in its own last commit; all plan-replay test and documentation references already use .307.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Approved light catch-up after full replay validation. MessageList, chat-controls and the shared harness fixture merged independent pending-action and plan-replay additions. Both sides retain V2's .307 version; replay takes tip+1 (.308) in a separate last commit. No rebase or force-push.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The V2 pending-action cards now read the conversation's saved-action list. Handle that exact GET through the shared fixture before tracking replay requests, while retaining strict rejection of all unknown browser requests. The replay card suite is again 7/7 after the V2 merge.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
V2 08def99 reached .307 before the push, so replay takes tip+1 (.308). Renumber only replay's own 30 references in 15 files. Keep every V2 version reference and release-notes section intact. This is the separate last commit after the merge and UI-fixture compatibility fix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread application/single_app/functions_workflow_plan_replay.py Fixed
Comment thread application/single_app/functions_workflow_plan_replay.py Fixed
Comment thread application/single_app/functions_workflow_plan_replay.py Fixed
Comment thread functional_tests/test_workflow_plan_replay_execution.py Fixed
Comment thread functional_tests/test_workflow_plan_replay_execution.py Fixed
Comment thread functional_tests/test_workflow_plan_replay_freeze.py Fixed
Comment thread ui_tests/test_v2_workflow_plan_replay_card.py Fixed
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) merged commit db89800 into microsoft:paullizer-react-v2-ui Oct 9, 2026
11 checks passed
Paul Lizer (paullizer) added a commit that referenced this pull request Oct 9, 2026
Plan replay (#1724) took 0.261.306 after V2 moved to 0.261.305.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

3 participants