Repository navigation
Phase 5: orchestration runs existing workflows (workflow_run, start-and-link) - #1594
Merged
Paul Lizer (paullizer) merged 19 commits intoOct 1, 2026
Conversation
Record, on the unmodified 0.261.209 base, the workflow planning context, the planner model messages, the repair/degrade result and the capability resolution for proposals-only orchestration. Phase 5 must keep all of them byte-identical while enable_chat_orchestration_workflow_runs is off. The request names the oldest workflow and the newest workflow is not durable, so any catalog ranking for workflow runs that leaked into proposals-only planning would change the fixture. Bump VERSION to 0.261.211. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
enable_chat_orchestration_workflow_runs is off by default and only a real boolean True turns it on: update_settings coerces it, the Classic form reads it like its neighbouring checkboxes, and the V2 schema shows it under Capabilities only while Chat Orchestration and personal workflows are on. It is independent of Propose Workflows From Chat. The workflow context gains workflow_runs_configured, workflow_planning_configured (either setting), a settings-only workflow_run_settings_gate that checks the allowlist for workflow_run itself, and workflow_run_gate, which adds the WorkflowUser rule. Nothing uses them yet, so orchestration is unchanged. Documents the setting on the admin page, claims the key and regenerates the docs inventory. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The planning context now exists when proposals or runs are gated in, so workflow_run (next commit) can read the requester's catalog without the proposal setting. - build_workflow_planning_context checks privacy first, then builds the proposal context verbatim when proposals are gated in. When runs are gated in, it adds a workflows-only catalog if the proposal context is not ready (runs-only, the per-user cap, or a failed proposal read). The per-user cap limits proposals only. - With runs gated in, the catalog ranks workflows the request names first, then durable ones, each newest first. Proposals-only keeps Phase 4's newest-first order; the golden fixture proves it. - workflow_run_ready, workflow_run_unavailable_reason and workflow_run_projection mirror the proposal helpers. - /plan, /run, execution and plan editing read the context when either setting is on (workflow_planning_configured). The time zone and source scopes stay proposal-only. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A plan can name one of the requester's saved workflows to start with a workflow_run step (Gather, low cost, at most three per plan). The step's only argument is a catalog handle from the turn's workflow planning context; it takes no depends_on and no inputs, so no step result can choose which workflow runs, and no step or answer may read it. - Registry: the descriptor, dormant until Run Workflows From Chat is a real True, its request gate and closed unavailable reasons. - Schema: prepare_workflow_run_arguments checks static input, an offered durable handle, duplicates and the limit; consumers are rejected. - Planner: run instructions only while the capability is offered, one fixed-text repair round, then each failing run step is dropped with a closed reason so the rest of the plan runs. Model-written workflow_run_notes are discarded. - Deliverables: the planner is told about starting workflows only while the setting is on. - Admin: the Run workflows capability option on both surfaces. - The adapter is registered but fails closed, starting nothing, until the approval floor and run-start commits land. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A workflow_run step carries approval_floor: 'manual' on its descriptor. After
validation, normalize_plan forces approval.mode to manual and records
approval.floor = {mode: 'manual', reason: 'workflow_run'} whenever an enabled
step has a floor, whatever approval mode the admin or user asked for. Every
path that saves a plan goes through normalize_plan, so the first plan,
regenerate, replan, question answers, plan-editor revisions, plan editing and
recovery retries all keep the floor.
claim_plan_run refuses a floor plan whose saved record or plan approval is not
manual (409 approval_floor_required), so a tampered record can't auto-run.
build_plan_inputs adds inputs.workflows (handle, name, trigger summary, paused)
so the approval card names each workflow. IDs stay out of the plan.
The V2 plan card shows a floor notice, never starts a timed countdown for a
floor plan, and the controller never auto-runs one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The workflow_run step resolves its catalog handle to one of the requester's durable personal workflows and queues exactly one run through queue_durable_workflow_run, with trigger_source chat_orchestration. - Re-checks the setting, the WorkflowUser rule and conversation privacy before queueing, reloads the workflow, requires durable execution and refuses Microsoft 365 waiting states, like the manual Run route. - The request id is derived from the plan's first attempt and the step (attempt_root_run_id, now on RunContext at all three construction sites), so a recovery retry links to the run its first attempt started. - Looks up the deterministic run id before queueing, so a later active run never hides ours, and checks cancellation right before queueing. - Every queue exception maps to a closed reason, a tombstoned request included; storage outages are retryable with effects uncertain. - Records chat_invocation on the run beside mcp_invocation, which is unchanged for MCP. - Keeps workflow and run ids in a server-only step sidecar that public_step_record drops; recovered and reused steps rebuild it. - A started run's result is never offered to later plans as an alias. Refs microsoft#1551 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The coordinator set the landing order to Phase 3c (0.261.211), then Phase 5 (0.261.212), then Phase 6a (0.261.213). This renumbers only Phase 5's own references: config.py, its module and test headers, its version checks and its admin settings row. Refs microsoft#1551 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in microsoft#1585 (React V2 image indexing and upload fixes). The only conflict was VERSION in config.py, kept at Phase 5's 0.261.212. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Recovery and the executor decided which failed steps may have acted outside the chat from a hard-coded set of agent_invoke and action_invoke. Each descriptor now declares external_effects, and both read the set from the registry, so workflow_run joins them: a failed or interrupted run step marks its effects uncertain and a retry asks the user to confirm first. The flag stays server-side; the planner and client projections are allowlists. The V2 retry dialog now says a saved workflow that already started is linked again, never started twice. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A step whose checkpoint was lost after the queue is recovered from its retained result alone, so the retry must rebuild the run link from the plan's first attempt and the step. A run prepared outside a request has no signed-in session and never starts a workflow, while one with a request bridge does. Every RunContext reads its attempt root from the run record and its session from the principal's bridge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A plan that starts a saved workflow now ends its reply with a short, server-written note: which workflows started, which were already started for this request, and why any could not start, in fixed application text. Names are the workflow's own, quoted as inline code so they cannot format the reply. The note names no IDs or handles, says where the results appear (the run page, the workflow's own conversation and its alerts), and never promises to post results back here. The note is added in _finalize for every outcome, so a stopped plan still says the workflow it started keeps running. Only completed workflow_run steps are listed; a stop between queueing and saving the step is left to the cancel text and recovery. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A requester-only read route, GET /api/v2/orchestration/runs/<run_id>/workflow-runs, lists the saved workflows a plan started, with each run's current state and its workflow and run ids. Sidecars are read from the run, the step record or a retry's producing attempt, and each is checked against the plan, the step, the requester and the run id derived from the attempt root; a mismatch is left out and logged. A blocked gate, a shared conversation or an answer in content review shows every link as unavailable, with no names or ids. A deleted workflow or a run missing from history reads as unavailable with its own reason. Non-404 read errors return a sanitized 503. The V2 chat mounts the links under an answer whose plan completed a workflow_run step, only in the requester's own active personal conversation and only when nothing is masked. Each link opens the workflow's run history with that run expanded (workflowRunHref). The status is read once, with no polling. Tests: 62 route, 7 node and 29 Playwright tests. All 35 c9 mutations are killed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The log pipeline keeps text only under allowlisted keys and records every other string as a length. The workflow run step logs used reason_code, workflow_run_status and raw run and step ids, so the codes never reached Application Insights. Log hashed run and step ids through workflow_log_context, and use status, reason and failure_code. The link mismatch log uses reason instead of code. Tests run each log through the real allowlist and check that the codes and hashes are kept and that no raw id is logged. Refs microsoft#1551 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the Chat orchestration workflow runs feature doc and the 0.261.212 release notes, and updates the orchestration and workflow admin pages, the plan review guide, the trigger-a-workflow guide and the chat controls reference for Run Workflows From Chat, the manual approval floor, the started workflows links and the retry behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The storage-failure test now runs the route's error log through the real Application Insights allowlist and checks it keeps the stage, the error type and the hashed run and conversation ids, and never the raw ids. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Ask the executor's own transient-retry check about a failed workflow_run step with each transient code and with workflow_runtime_unavailable, using a document_search step with provider_timeout as the positive control. Results are captured before the assertions so the calls still run under python -O. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The XSS sink check flagged the Started workflows link: <Link to={item.href}>
took its URL from a property the checker can't trace. The parsed link item
now keeps the workflow and run ids the response check accepted, and the
component calls workflowRunHref in the link itself. workflowRunHref only
returns the fixed /workspace/workflows path with the ids encoded as query
values, so it joins the checker's reviewed same-origin URL builders, as
Phase 4's workflowProposalLink did.
A new guardrail test runs the checker over the run link files, pins the
builder call and the approval, and checks that a link from a plain property
is still flagged.
Refs microsoft#1551
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CodeQL reported cyclic imports between the plan schema and the workflow run module, an unused global, and unused imports in three tests. - Move workflow_run_catalog_entry into functions_orchestration_schema. The run module already imports the schema at load time, so the approval card and the run checks share one lookup and the schema no longer imports the run module back for it. The schema still reaches the run module lazily, only to check a run step. - Remove the unused RULE_CONSUMED constant. The schema reports consumed run outputs under its own workflow_run_consumed rule. - Remove unused imports from the capability, answer and effects tests. - Add test_orchestration_workflow_run_imports.py: the run and link modules load in fresh normal and optimized interpreters in the application's import orders with no network, every lazy import into and out of them resolves, and an AST guard pins the schema's single lazy import of the run module. - Update the feature doc's file and test tables. Refs microsoft#1551 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ersation Adds a link-route test that plants a copy of a started run under another conversation's partition and asserts the route returns 404 run_not_found without reading the workflow or its run, while the same record still opens from the conversation it names. This kills the coordinator's L7 mutation, which deletes the route's own conversation_id compare. Tests only; no VERSION change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
marked this pull request as ready for review
October 1, 2026 10:11
Paul Lizer (paullizer)
merged commit Oct 1, 2026
5fc50a5
into
microsoft:paullizer-react-v2-ui
11 checks passed
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 1, 2026
microsoft#1594 (Phase 5) merged into paullizer-react-v2-ui first and took 0.261.212, so 3c moves to 0.261.213. This is a mechanical renumber of this PR's own files: config.py, the release-notes heading, the docs' version lines and mentions, and the functional and UI test headers and version assertions. Every changed line differs only by the version. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 1, 2026
Brings in Phase 5 (microsoft#1594, 0.261.212). Conflicts resolved: - application/single_app/config.py: keep VERSION 0.261.213 (3c). - docs/explanation/release_notes.md: the v0.261.213 section goes on top, then Phase 5's v0.261.212 section byte-identical, then v0.261.210 and earlier unchanged. docs/admin/workflow.md and docs/_data/features.yml merged cleanly; both sides are kept. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
14 of 24 tasks
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 1, 2026
Status update 4 for the chat orchestration workflows roadmap (docs only). - Status table: 3c done (microsoft#1593, v0.261.213), so Phase 3 is complete; Phase 5 done (microsoft#1594, v0.261.212); 6a in review (microsoft#1592). - 3c shipped block and its follow-ups; Phase 5 shipped block and its follow-ups; 6a in-review note; 6b run deep-link guidance now covers Phase 5's workflowRunHref. - Base failures on paullizer-react-v2-ui that the phase PRs report, with verified causes. - Gotchas 62 (result answers outside the message list) and 63 (Retry and Edit of a result-backed turn). Refs microsoft#1543 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
14 of 24 tasks
Paul Lizer (paullizer)
added a commit
to paullizer/simplechat
that referenced
this pull request
Oct 1, 2026
Brings in the workflow_run capability (0.261.212). Conflicts: - config.py keeps VERSION 0.261.213; a separate commit renumbers. - release_notes.md keeps the 0.261.213 section above Phase 5's 0.261.212 section, which is unchanged. - chat-controls.md keeps the higher front-matter version and puts Phase 5's "Workflow runs (V2 interface)" section before "Workflow results in chat (V2 interface)". The settings, admin fields, admin route, features.yml, docs and the blueprint inventory test merged cleanly with both sides kept. app_surface.yml is regenerated, not merged: both sides counted 123 capabilities, and the merged surface has 124. Part of microsoft#1546 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 1, 2026
This was referenced Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 5 of the Chat Orchestration Workflows roadmap: orchestration runs existing workflows (
workflow_run, start-and-link). The coordinator's review has passed; the user merges it.workflow_runstep. The step names one of the requester's own durable personal workflows by a handle from the workflow planning context, and it queues the same durable run that Run in Workflows queues. The plan starts the run and links to it. It never waits for the run or reads its results; that's Phase 6.claim_plan_runrefuses a saved plan that lost it (409approval_floor_required). The V2 approval card lists the workflows the plan would start and shows Paused for a paused one.enable_chat_orchestration_workflow_runs) is off by default and independent of Propose Workflows From Chat. Runs also need personal workflows (allow_user_workflows), theWorkflowUserrole whenrequire_member_of_workflow_useris on, durable execution, andworkflow_runin Capabilities when that list is narrowed. With the setting off, nothing changes: a golden captured on the unmodified base pins planning byte for byte.What's in it:
functions_orchestration_registry.py,functions_orchestration_workflow_runs.py, the planner and schema).workflow_runhas the Gather role, at most 3 steps per plan with distinct handles, a static handle input only, and no consumers of its output. The planner gets one repair round. A step that still fails is dropped with its bindings, and server-writtenworkflow_run_notessay why; model-supplied notes are discarded. With the setting off the capability is dormant: it's skipped before any check and records no reason.functions_orchestration_workflow_context.py). It serves proposals and runs from one bounded read. When runs are gated in, it ranks workflows the user named first, then durable ones (500 scanned, top 20 offered).adapter_workflow_run). It re-checks the settings gate, the private conversation and the workflow, then computes the run id and reads the run. If this plan already started the run, it links it with no queue call and no admission checks. Only a new run needs a signed-in session, the role, durable execution and no Microsoft 365 wait. Unavailable outcomes complete the step with a closed reason, and only runtime unavailability fails it. The step writes a newchat_invocationon the run; MCP'smcp_invocationis untouched.workflow_run_id_for_request(functions_workflow_runtime.py). The queue and the step now use one formula for a durable run's id.external_effect_capability_ids()instead of a hard-coded set, andworkflow_runis one of them. A failed or interrupted run step iseffects_uncertain, and a retry asks for confirmation.GET /api/v2/orchestration/runs/<run_id>/workflow-runs(requester-only, private conversations only, sanitized 503 on storage errors).OrchestrationWorkflowRunNoticeon the approval card,WorkflowRunLinksunder the answer, a Workflow row in the run view, a recovery line, and a client floor guard inorchestrationController.ts.Docs: the new
docs/explanation/features/CHAT_ORCHESTRATION_WORKFLOW_RUNS.md,docs/admin/orchestration.md,docs/admin/workflow.md,docs/guides/trigger-a-workflow.md,docs/guides/review-and-edit-orchestration-plans.md,docs/reference/chat-controls.md, a cross-link inCHAT_ORCHESTRATION_WORKFLOW_PROPOSALS.md,docs/_data/features.yml, the v0.261.212 release notes, and the regenerateddocs/_data/app_surface.yml.Linked issue
Closes #1551
Part of #1543
Release Notes & Latest Features
Is this visible to end users?
Is this admin-facing (Admin Settings, governance, deployment, config)?
Should this become a Latest Feature card?
Screenshot needed for the card?
Version bump
application/single_app/config.pyVERSIONthird segment bumped, or not needed because this is docs-only:0.261.210→0.261.212..211is held by Phase 3c (Add an AI workflow assistant to the V2 workflow editor #1548), and Show what was extracted from V2 image uploads, index text-free images, and default image generation to 2025-04-01-preview #1585 took.210. Three-dot against the V2 tip:config.pychanges only theVERSIONline.deployers/version.txtbumped, or not needed becausedeployers/was not changed:deployers/isn't changed.Testing / validation
Head is 678f56c and base is 4f77838, which is both the merge-base and the V2 tip. Some runs are at an earlier commit:
test_orchestration_workflow_run_link_routes.py, which kills the coordinator's L7 mutant (see Mutation testing).test_orchestration_workflow_run_imports.pyand updates one row each in the feature doc's Files and Test coverage tables. It changes no V2 file.scripts/check_xss_sinks.py, the feature doc and the two link tests.test_orchestration_workflow_run_adapter.py.Everything the XSS fix touches was re-run at 99ca676: the Phase 5 suites, Node, typecheck, the run-links Playwright file, the checker tests, the mutations, the guardrails and the docs tests. These ran again at df8bfd0:
python -Otest_orchestration_plan_revision_routes.pyandui_tests/test_v2_orchestration_plan_editor_backend.py, which reach it through the routesThese ran again at the head: the 12 Phase 5 suites normally and under
python -O, Node, the typecheck, the XSS checker tests, the L7 mutant and its control, the guardrails, the docs tests and the three-dot checks. The head adds one test function to one Python test file, and that can't change the other results: the Playwright files and the 60 schema files don't load that file, and an added test can only kill more mutants, never fewer. So those runs and the CodeQL fix's mutations weren't repeated.Python tests ran in a dedicated venv (Python 3.12.10) that pins the app's Flask 3.1.3 and Werkzeug 3.1.6 from
requirements.txt. It was created with--system-site-packages, so pytest 9.0.3 and Playwright 1.58.0 come from the machine's Python, whose own Flask and Werkzeug pair is mismatched. The environment wasPYTHONPATH=application\single_app;functional_tests;ui_testsandPYTHONDONTWRITEBYTECODE=1. Each file ran in its own pytest process.Phase 5 suites
Command:
python -m pytest <file> -q -p no:cacheprovider -W ignore::DeprecationWarning --tb=line -rfE, then the same command underpython -O. Results are at the head. Every file exited 0 both ways.python -Ofunctional_tests/test_orchestration_workflow_run_adapter.pyfunctional_tests/test_orchestration_workflow_run_answer.pyfunctional_tests/test_orchestration_workflow_run_approval_floor.pyfunctional_tests/test_orchestration_workflow_run_capability.pyfunctional_tests/test_orchestration_workflow_run_effects.pyfunctional_tests/test_orchestration_workflow_run_imports.pyfunctional_tests/test_orchestration_workflow_run_link_routes.pyfunctional_tests/test_orchestration_workflow_run_planning_context.pyfunctional_tests/test_orchestration_workflow_runs_admin.pyfunctional_tests/test_orchestration_workflow_runs_off_golden.pyfunctional_tests/test_orchestration_dependency_allowlist.pyfunctional_tests/route_tests/test_route_blueprint_policy_inventory.pyThe tests make every side-effecting call before asserting on its result. Under
python -O, pytest still rewrites the asserts in the test files themselves, while asserts elsewhere, in the application and in helper modules, are stripped. The only-Owarning is pytest's notice about that.V2
Node: from
application/v2_ui,node --test ..\..\functional_tests\test_v2_orchestration_workflow_run_floor.mjs ..\..\functional_tests\test_v2_orchestration_workflow_run_links.mjs. Result at the head: 18 passed, 0 failed.Full Node run (Node v24.11.0):
node --test ..\..\functional_tests\*.mjs. The head has 101 tests and 100 pass; the base has 83 and 82 pass. The one failure is the same on both:test_v2_workspace_action_authoring_logic.mjs(AssertionErroratm365_calendar_plugin.py).Typecheck:
npm run typecheck(tsc -b --noEmit) exited 0 at the head.New Playwright tests:
python -m pytest ui_tests/test_v2_orchestration_workflow_run_floor.py ui_tests/test_v2_orchestration_workflow_run_links.py ui_tests/test_admin_orchestration_workflow_runs.py -q. Result at df8bfd0: 47 passed.v2_admin_settingsfixture's stale-bundle check, which the admin file uses. Restoring the V2 files after the mutation runs had made them newer thanstatic/v2, though their content was unchanged. Afternpm run build, all 47 passed.ui_tests/artifacts/orchestration-plan-editorbuild, whose CSS and script are SHA-256 identical to the rebuiltstatic/v2bundle.Existing orchestration Playwright files at 18a2288, through pytest: all 17 files passed, 314 tests in total:
The XSS fix changes only how the run links build their
tovalue; the rendered DOM is the same. The CodeQL fix changes no V2 file. recovery_backend and plan_editor_backend drive backend code that uses the schema, so both ran again at df8bfd0: 18 passed and 7 passed. recovery_backend also logs 18RuntimeWarnings ("coroutine 'BaseEventLoop.shutdown_asyncgens' was never awaited") from the unchangedfunctional_tests/test_support/orchestration_recovery.py:216. The base gives the same 18 passed and the same 18 warnings.__main__-only UI files: eight files set a module-level page inmain(), so they ran aspython -u ui_tests/<file>. All ran with 0 skipped:auto_open is 1/5 at base too, with the same failures. Its fixture
_plan()lacks theturn_idandconversation_idthatorchestrationController.onPlanrequires.Regression sweep against the base
The sweep covered 777 functional test files at 18a2288, one pytest process per file:
621 selected files:
test_orchestration_**workflow**chat*test_v2_**conversation*test_admin_**m365**mcp*route_testsThe selection also included the return-False guard, the version guardrails, the settings tests, and the analyze and data-management orchestration tests.
156 more files: every other functional test that references a changed module.
549 files exited 0. The other 228 were re-run at the base and compared by failing test id. No failing id is new.
python -u <file>at head and at base:test_control_center_functionality.pydiffers only in the version it prints.test_workflow_feature.pytimed out at the base under load. Alone at the base it matched the head.test_tabular_row_orchestration_scale.pyran with--deselect ...::test_phase_six_rolling_pool_pauses_dispatch_at_checkpoint_high_water, because that test stalls on both sides. Head and base were both 10 failed and 66 passed, with the same 10 failing tests.test_workflow_flow_inspection.pytimed out in the sweep only because it runs for about 40 minutes. Alone, with a 5400-second limit, it passed 27 of 27 at 18a2288 and 27 of 27 at the base, in 2437 seconds each.Failures that also happen at the base:
test_orchestration_plan_revision_routes.py::...::test_document_removal_is_not_lost_when_editor_openstest_orchestration_turn_recovery_identifiers.py::test_run_record_keeps_the_turn_and_question_idstest_workflow_runtime_integration.py(NameError: WORKFLOW_SCHEDULED_TRIGGER_TYPES)test_orchestration_failure_telemetry.py, 14 parameters. These fail only aftertest_orchestration_elicitation_context.pyin the same process, because that file leavessys.modulesstubbed.ui_tests/test_v2_orchestration_auto_open.pythrough__main__: 1 of 5, with the same failures at the base (see V2).Load flakes. Each of these matched the base when re-run alone:
test_orchestration_external_error_markers.py.test_orchestration_harness_execution.py: its nested pytest collided with another worker's temp directories.test_content_screening_model.py: its fixed-wait timing tests failed in 1 of 3 solo rounds at the head and 1 of 3 at the base.ui_tests/test_v2_orchestration_outputs.pyhung in the two-worker UI batch after 49 passing tests, with Python, Node and Chrome all at 0 CPU. Alone it passed 68 of 68.At df8bfd0. The CodeQL fix changes the schema, so every test file that reaches it ran again, one pytest process per file, and was compared with 18a2288 by failing test id. No failing id is new.
PytestReturnNotNoneWarnings from three files this PR doesn't change:elicitation_schema5,prompt_instruction5 andrun_hydration_routes6.test_orchestration_harness_execution.pypassed 649 with 2 xfailed, where the 18a2288 sweep hit the temp-dir load flake above.ui_tests/test_v2_orchestration_recovery_backend.py, covered under V2 above. The 24 warnings are those 18RuntimeWarnings and 6PytestReturnNotNoneWarnings fromtest_orchestration_agent_selection.py, which the base also shows.test_orchestration_plan_revision_routes.pypassed 28 with 14 subtests, plus the base failure listed above.ui_tests/test_v2_orchestration_plan_editor_backend.pypassed 7.Mutation testing
147 mutants were applied one at a time and run against the Phase 5 suites. All 147 were killed. They include the ten that coordinator note 10 asked for:
workflow_run_noteskeptpublic_step_recordor SSEretry_on_transientset onworkflow_run, andworkflow_runtime_unavailableadded toTRANSIENT_FAILURE_CODESEvery mutant that survived a first run got a new test. Two context roots that never run a step today are pinned by an AST check (decision 17).
The XSS fix added 7 of the 147, and the new guardrail test or the links
.mjskills each one:workflowRunHrefremoved from the approved buildershrefput back on the itemThe CodeQL fix added 8 more, run at df8bfd0. Three re-target earlier mutants whose code moved or changed shape: the catalog lookup ignoring the handle map, which now lives in the schema, and the two run-link mutants that the XSS fix reshaped (the client's unavailable reason made optional, and the component hiding it). Five are new, and the capability test or the new imports test kills each one:
The coordinator's review added the last one, L7, run at the head. It deletes the link route's own conversation check,
record.get('conversation_id') != conversation_idinroute_backend_orchestration.py, so the route would list the links of a run record that names another conversation. The newtest_a_run_whose_record_names_another_conversation_is_not_foundintest_orchestration_workflow_run_link_routes.pykills it: the route must answer 404run_not_foundwithout reading the saved workflow or its workflow run, and the record still opens from the conversation it names. With that test deselected, the mutant survives (62 passed), so the new test is what kills it.The coordinator's own mutation run: 38 of 38 killed after the L7 test (37 of 38 at df8bfd0).
CI fix
The first CI run, on 191cf7f, failed
xss-sink-check:[ts-jsx-url-attribute]atWorkflowRunLinks.tsx:47,<Link to={item.href}>. The checker approves a JSX URL only when it's a call to a reviewed same-origin builder; it can't trace a property. My local guardrail run had missed it because the guardrail script then fed the checker only.js,.htmland.py, 21 files, while CI also checks.ts,.tsx,.jsxand.mjs, 32 files.The fix, in 99ca676, follows Phase 4's
workflowProposalLink(e72baec):{workflowId, runId}.workflowRunHref(...)in theLink.workflowRunHrefjoinsTS_SAME_ORIGIN_URL_BUILDERS. It returns the fixed same-origin path/workspace/workflows?${URLSearchParams}, and a new test pins that.The CI-style checker, run on the head over the 32 changed
application/files, prints "XSS sink validation passed for 32 file(s)". At the head,test_xss_guardrails_checker.pypasses 12 tests, and the newfunctional_tests/test_v2_orchestration_workflow_run_links_xss_guardrail.pypasses 4 of 4 under pytest, underpython -Oand through__main__. It checks four things:Linkcalls the approved builder, and the item has nohref.<Link to={item.href}>is flagged, while the builder call passes.Guardrails
Command:
run_v2_guardrails.py <worktree> 4f778386b 678f56c8e phase5, over the 62 changed files. All six checks passed at the head:.tsand.tsxfiles. This is the same file set CI uses; see CI fix.single_app.py files compile.evil.examplein hostile-name tests, the json-schema.org$schemain the golden, andsimplechat.exampleas a URL-parse base. None of the later fixes or tests adds a URL.Docs
Each test ran as
python functional_tests\<test>.py, at the head and at the base for parity.test_docs_app_surface_coverage.pytest_docs_site_quality.pytest_docs_latest_release_catalog_parity.pytest_docs_web_search_accuracy.pytest_docs_link_integrity.pytest_docs_json_gem_security_fix.pyGemfile.lock)test_docs_release_notes_integrity.pyfeatures.ymllinks (of 68). The head's 12 extra relative links and 3 extra page links all resolve.docs/explanation/release-notes/pages are already stale on the V2 tip, missing 147 versions (decision 22).Three-dot against
origin/paullizer-react-v2-ui(4f77838)git diff origin/paullizer-react-v2-ui...HEAD -- application/single_app/config.py: onlyVERSION = "0.261.210"→"0.261.212".git diff --numstat origin/paullizer-react-v2-ui...HEAD -- docs/explanation/release_notes.md:30 0, so lines are only added.git grep -n "0.261.211"over the 62 changed files: no matches.python .\scripts\build_docs_inventory.pyleavesdocs/_data/app_surface.ymlunchanged.Documentation
### **(v0.261.212)**section on top, and everything below it is byte-identical (git diffshows 30 added lines and none removed).CHAT_ORCHESTRATION_WORKFLOW_RUNS.md(new), plus the admin, guide and reference pages listed under Summary.Security checklist
@swagger_route(security=get_auth_security()): the one new route,GET /api/v2/orchestration/runs/<run_id>/workflow-runs, has it before@login_requiredand@user_required, and it's in the route policy inventory.sanitize_settings_for_user(): no settings are sent to non-admin frontends. The new setting is read on the server and appears only in the admin pages. The link route returns per-run link records only, never settings.static/v2,ui_tests/artifacts) is committed.Decisions made autonomously
From the approved plan
workflow_runcapability. It has the Gather role andmax_per_plan=3, with distinct handles, a static handle input only, and no consumers of the run output (workflow_run_consumed). Phase 6c may lift the no-consumer rule.approval_floor: 'manual'is recorded on the plan asapproval.floor.claim_plan_runbacks it up with a 409approval_floor_required, and V2 has a client guard.chat_invocationfield on the run. MCP'smcp_invocationis untouched.workflow_runtime_unavailable, retryable, effects uncertain).Made while implementing
RunContext.signed_in_sessionrecords whether the identity came from a session, matchingbuild_external_identity_reader(Fix orchestrated web search and deep research in V2 chat #1582). Without a session the step still links a run that already exists. It never queues a new one; it fails withexternal_session_required, whose message now mentions saved workflows.workflow_rundoesn't setretry_on_transient, andworkflow_runtime_unavailableisn't inTRANSIENT_FAILURE_CODES. A behavioral test also asks the executor's own retry check (_should_retry_transient) about a failedworkflow_runstep, with the transient codes (provider_timeout,connection_failed,provider_failed) and withworkflow_runtime_unavailable. Adocument_searchstep withprovider_timeoutis the positive control. Each half has its own mutant.workflow_runtime_unavailable(503 from the route). Theget_personal_workflow*helpers turn every error into None.not_found, read asworkflow_run_not_started. The runtime's code and message never reach the user or the retained value.retry_principal_fix:signed_in_sessionchecksgetattr(principal, "bridge", None) is not Noneat all threeRunContextconstruction sites, because retry validation and some fixtures passprincipal=None.RunContextreads its attempt root from the run record and its session from the principal bridge, so 6a and 6b inherit them.effects_uncertainand no sidecar. The reply gives the generic cancel text ("Already submitted external actions may still finish."), and a retry links the same run. The note and links cover completed steps only.workflowRunHref, notv2WorkflowRunPath. The links use Track P'slib/workflowRunLink.ts.v2WorkflowRunPathinnotificationLinks.tsdeliberately returns null until 6b adds a run page (gotcha 61). When 6b adds one, pointworkflowRunHrefat it too, or route both through one helper.run_id_hash,step_id_hash,status,reason,failure_code,error_type,stage), so their values survivefunctions_appinsights._build_logger_extra. Tests run each log through the real allowlist.workflowRunHrefis an approved same-origin URL builder inscripts/check_xss_sinks.py, as Phase 4 did forworkflowProposalLink(e72baec). It returns the fixed path/workspace/workflows?${URLSearchParams}. The run links call it in<Link to>, and 6b'sWorkflowRunCardcan too. The alternative was a sanitize-named wrapper. That would pass the checker by its name pattern alone, which is a weaker guarantee than a reviewed allowlist entry.workflow_run_catalog_entry(workflow_planning, handle)is a pure function. The schema needs it to list a plan's workflows for the approval card, and the run module uses it for every run check. At 99ca676 the schema imported it lazily from the run module, and CodeQL reported six cyclic-import notes: one on that import and five on imports in the run module. It now lives infunctions_orchestration_schema.py, and the run module imports it at load time, as it already did the rest of the schema. All six notes are gone at the head (see CodeQL). The schema still reaches the run module lazily, but only to check a run step (prepare_workflow_run_arguments). That's the same shape as Phase 4's proposal check.test_orchestration_workflow_run_imports.pypins this with an AST guard, and checks that the run and links modules load in fresh normal and-Ointerpreters, in the application's import orders, with no network call.Coordinator review notes, as folded in
git grep 0.261.211over the changed files is empty.-c rerere.enabled=false(2430df0).deleting; (b) the run id and the run read, linkingalready_startedwith no queue call and no admission checks; (c) only then the session, the role, durable, Microsoft 365, the cancel check and the queue. There are tests for a bound run with the workflow waiting on Microsoft 365, an existing run with durable now off, and an orphan with Microsoft 365 active.workflow_run_id_for_request(workflow, request_id)is used by the queue and the step. A parity test runs the realqueue_durable_workflow_runwith fake services and gets the step's precomputed id.plan_request(..., edit_context=...)) and manual edits (validate_edited_plan→normalize_plan) are in the floor tests. A revision that adds aworkflow_runstep comes back floored and checked against the stored handles, and the edit prompt carriesWORKFLOW_RUN_INSTRUCTIONSwhen the capability is available.Follow-ups
chat_deliveryon the run), a liveWorkflowRunCardwith Cancel and Retry, the chat-list indicator, and pointingworkflowRunHrefat 6b's run page.updated_ator searched by name, to go beyond 500 workflows.functions_workflow_runner.pyL5455, L5484 and L5552). The V2 run history doesn't show which trigger started a run either.functions_orchestration_workflow_proposals.pylogscode=,step_id=andproposal_id=, and the planner's proposal-drop log carriesunavailable_reason. None are allowlisted, so only their lengths are recorded.Shared files (for 6a)
Every existing file this PR changes, apart from
config.pyand the docs:functions_orchestration_registry.py,functions_orchestration_workflow_context.py,functions_orchestration_planner.py,functions_orchestration_schema.py,functions_orchestration_plan_revisions.py,functions_orchestration_plan_editing.py,functions_orchestration_executor.py,functions_orchestration_execution.py,functions_orchestration_recovery.py,functions_orchestration_services.py,functions_orchestration_deliverables.pyandfunctions_orchestration_adapters.py.functions_workflow_runtime.pyandroute_backend_orchestration.py.MessageList.tsx,OrchestrationPlanCard.tsx,OrchestrationRunView.tsx,OrchestrationRecoveryNotice.tsx,lib/orchestration.ts,lib/orchestrationController.ts,lib/orchestrationPlan.tsandlib/workflowRunLink.ts.functions_settings.py,admin_settings_fields.py,route_frontend_admin_settings.pyandtemplates/admin/_panes/chat-orchestration.html.functional_tests/route_tests/test_route_blueprint_policy_inventory.pyandfunctional_tests/test_orchestration_dependency_allowlist.py.scripts/check_xss_sinks.py, one line:workflowRunHrefinTS_SAME_ORIGIN_URL_BUILDERS(decision 24).functions_orchestration_schema.pynow also definesworkflow_run_catalog_entry, the lookup from a run step's handle to its catalog entry (decision 25). Code that needs it should import it from the schema; it isn't in the run module's__all__.New logic is in new modules:
functions_orchestration_workflow_runs.py,functions_orchestration_workflow_run_links.py,OrchestrationWorkflowRunNotice.tsx,WorkflowRunLinks.tsxandlib/orchestrationWorkflowRuns.ts.CodeQL
The code-scanning API returns 403 for this token, so this section comes from the CodeQL check runs and their annotations.
All 11 check runs at the head completed with success.
The ten notes fixed in df8bfd0
None of them was a security alert, and the fix changes no behavior. Line numbers are at 99ca676.
from functions_orchestration_workflow_runs import workflow_run_catalog_entry, and five imports in the run module: L45 (functions_orchestration_registry), L52 (functions_orchestration_result_runtime), L54 (functions_orchestration_schema), L63 (functions_orchestration_workflow_context) and L474 (its lazy import ofworkflow_run_id_for_requestfromfunctions_workflow_runtime).workflow_run_catalog_entrynow lives in the schema (decision 25), so the schema's L1007 import is gone. None of the six is reported at the head.RULE_CONSUMEDin the run module (L87), removed.importlibintest_orchestration_workflow_run_answer.py(L18)NOT_DURABLE_IDand_workflowsintest_orchestration_workflow_run_capability.py(L36, one alert)WORKFLOW_RUNintest_orchestration_workflow_run_effects.py(L34)What ran again after the fix, at df8bfd0 and at the head, is under Testing. Mutation testing lists the 8 mutants the fix added.
Log Injection (3, medium):
log_event's existing sinksAll three runs report Log Injection at the same three lines of
functions_appinsights.py, with the same messages:logger.exception(L515)logger.log(L531)fallback_logger.log(L553–560)This PR doesn't change
functions_appinsights.py:git diff --stat 4f778386b...HEAD -- application/single_app/functions_appinsights.pyis empty.Each message repeats
This log entry depends on a [user-provided value](n).for 74 numbered sources and is cut off at 4,096 characters partway through a 75th. It doesn't say where the sources are, so it can't show whether any of them are new. The head's run summary adds: "Alerts not introduced by this pull request might have been detected because the code changes were too large."What this PR passes to
log_event. The one new route reads one request value:conversation_idfrom the query string (route_backend_orchestration.pyL2907). Its only log call (L2924) recordsconversation_idand therun_idpath value as SHA-256 hashes throughworkflow_log_context. Every log call this PR adds has a constant message. Each records only hashed ids, application codes, numbers and exception class names:route_backend_orchestration.pystage,conversation_id_hash,run_id_hash,error_typefunctions_orchestration_workflow_runs.py(_log, L224)stage,run_id_hash,step_id_hash,statusandreason(both from closed sets),failure_code,error_typefunctions_orchestration_workflow_run_links.py(_log, L89)stage,reason(workflow_run_link_mismatch)functions_orchestration_workflow_context.py(_log_context, L790)stage,reason(workflow_context_unavailable),error_type,workflow_count,duration_msfunctions_orchestration_registry.pyreason(workflow_context_unavailable),error_typefunctions_orchestration_executor.pyrun_id_hash,conversation_id_hash,step_id_hash,capability_id,error_typefunctions_orchestration_planner.pystage,conversation_id_hash,turn_id_hash,reason,attempt,revision,validation_code,validation_rule; 1375 also hasunavailable_reason, and 1384 hasnote_countandremaining_countPlanner L1375 is Phase 4's proposal-drop log. This PR re-indents it and reads the rule from
proposal_errorinstead ofexc; its keys are unchanged.What
log_eventdoes with them (unchanged code):sanitize_log_messagereplaces runs of\r,\nand\twith a space (LOG_CONTROL_CHAR_RE, L73) and redacts secret-like values.The record message is a constant:
[SIMPLE_CHAT_LOG_EVENT], or[SIMPLE_CHAT_LOG_FALLBACK]for the fallback logger._build_logger_extra(L249) keeps a string's text only under allowlisted keys:reason,status,stageanderror_type, keeps sanitized textEvery other string is recorded as its length.
Triage. I made no change for these. The coordinator's review confirmed that the three annotations are these pre-existing sinks, alerts #2551–#2554, which the user is handling. #1580's body records that Phase 3b (#1577) matched the Log Injection alerts at these three lines and L419 to those alerts, which are open on V2 and Development. This token can't read alert state (the code-scanning API returns 403), so I haven't checked them myself. The CodeQL check run succeeded at all three commits with these alerts reported.