Skip to content

Gate personal workflow instruction drafting on the AI workflow assistant setting (N9) - #1596

Merged
Paul Lizer (paullizer) merged 2 commits into
paullizer-react-v2-uifrom
paullizer-n9-draft-instructions-gate
Oct 1, 2026
Merged

Paul Lizer (paullizer) merged 2 commits into
paullizer-react-v2-uifrom
paullizer-n9-draft-instructions-gate

Conversation

@paullizer

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

Copy link
Copy Markdown
Contributor

Summary

  • Server, personal scope. POST /api/workflows/draft-instructions now refuses a personal request with 403 when Enable AI Workflow Assistant isn't available to the signed-in user.
    • The body is the workflow_assistant_required decorator's exact body: {"error": "The AI workflow assistant is not available.", "code": "workflow_assistant_disabled"}.
    • The check runs right after _assert_personal_workflow_draft_access(settings), so personal workflows being off (400) and a missing WorkflowUser role (403) keep today's answers.
    • No model client is created when it refuses.
    • This closes reviewer finding N9 from Phase 3c: Ask AI tab in the V2 workflow editor (0.261.211) #1593: turning the assistant off only hid a V2 button, while the classic page and direct API calls could still draft.
  • Classic UI. The classic personal workspace (workspace.html) no longer renders the Task Brief box or the Draft Workflow Instructions button when the assistant isn't available.
    • route_frontend_workspace.py sets one role-aware boolean, enable_workflow_ai_assistant, on the already-sanitized public_settings, using the same helper (is_workflow_assistant_enabled_for_user).
    • Task Instructions stays, so instructions can still be written by hand.
  • Group scope is unchanged. Group drafting and group_workspaces.html keep following the group workflow settings, because the setting is documented as personal-only.
  • V2 needs no change. I confirmed this on base 70c255508 and again on the merged head 544aed9a3:
    • WorkflowEditorDialog.tsx L261 reads the bootstrap flag features.enable_workflow_ai_assistant === true.
    • L262 sets askAiAvailable = askAiEnabled && scope.type === 'personal' && !readOnly.
    • draftWithAi is passed only when askAiAvailable is true (L587 and L966).
    • WorkflowTaskFields.tsx L1086 renders Draft with AI only when draftWithAi is set.
    • The bootstrap flag is role-aware: route_backend_v2.py L711 on the merged head, L710 on 70c255508.

Linked issue

Refs #1548
Part of #1543

Release Notes & Latest Features

  • New Feature
  • Bug Fix
  • UI Enhancement
  • Breaking Change
  • Internal only

Is this visible to end users?

  • Yes
  • No

When the assistant is off, users no longer see the classic personal Task Brief box or the Draft Workflow Instructions button.

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

  • Yes
  • No

Enable AI Workflow Assistant now governs personal AI drafting on every surface.

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
  • deployers/version.txt bumped, or not needed because deployers/ was not changed
    • Not needed; deployers/ wasn't changed.

Testing / validation

Runner. I used the pinned venv python.exe (Flask 3.1.3) with PYTHONPATH=application/single_app;functional_tests and PYTHONDONTWRITEBYTECODE=1, running one process at a time. The global Python has Flask 2.2.5 with Werkzeug 3.1.4, which can't run test_client().

Post-merge validation (head 544aed9a3)

544aed9a3 merges origin/paullizer-react-v2-ui at 520132dc2 (the merge of #1592, Phase 6a, VERSION 0.261.214) into 2bed2f26c.

  • How. I ran git -c rerere.enabled=false merge, committed with rerere off and pushed as a fast-forward (2bed2f26c..544aed9a3). There was no rebase or force-push, and rerere resolved nothing.
  • Conflicts.
    • config.py: kept VERSION = "0.261.215".
    • docs/explanation/release_notes.md: the v0.261.215 section now sits directly above 6a's v0.261.214. A scripted check confirmed the file equals the V2 tip's file plus this PR's 11-line section, so every existing section, including 6a's, is byte-identical.
    • route_backend_workflows.py and docs/admin/workflow.md merged automatically. Only this PR's hunks differ from V2: the import (now L122), the gate (about L1621–1630) and the assistant paragraph (L103–110).
  • Diff against V2. git diff --shortstat origin/paullizer-react-v2-ui...HEAD gives 11 files changed, 825 insertions(+), 15 deletions(-). Only this PR's files appear.
  • Results. I re-ran every check below on 544aed9a3. Where a table has two columns, it shows the results from both heads.

New test

functional_tests/test_workflow_draft_instructions_assistant_gate.py has 12 tests.

  • The real draft_workflow_instructions and workspace route code is loaded from source with ast, registered on a test Flask app and called through test_client().
  • The real workflow-modal markup from workspace.html and group_workspaces.html is rendered with Jinja.
  • Settings, Cosmos, group resolution and the model client are fakes.
Command 2bed2f26c 544aed9a3 (merged)
python -u functional_tests/test_workflow_draft_instructions_assistant_gate.py 12/12 passed 12/12 passed
python -u -O functional_tests/test_workflow_draft_instructions_assistant_gate.py 12/12 passed 12/12 passed
python -u -m pytest functional_tests/test_workflow_draft_instructions_assistant_gate.py -q -p no:cacheprovider 12 passed 12 passed
python -u -O -m pytest functional_tests/test_workflow_draft_instructions_assistant_gate.py -q -p no:cacheprovider 12 passed, 1 warning (pytest's PytestConfigWarning for -O) 12 passed, 1 warning (same)

Under -O the 49 plain asserts are stripped. The harness's 10 explicit raise AssertionError checks still run, including the refuse() fakes that fail the test if the model client or any other unexpected collaborator is created.

Coverage:

  • personal with the assistant on reaches the model
  • personal with the assistant off gets 403 workflow_assistant_disabled, and the model client is never created or called
  • a WorkflowUser still needs the assistant
  • personal workflows off (400) and a missing role (403 with the role message) are unchanged and still come first
  • group with the assistant off isn't blocked, and group refusals are unchanged
  • an invalid scope still gets 400
  • the route isn't wrapped in workflow_assistant_required
  • the classic page's flag is role-aware
  • the personal modal hides its draft controls when the assistant is off and shows them when it's on
  • the group modal is unaffected
  • the route and template together hide the button

Mutation checks

The session helper run_n9_mutations.py runs each mutation through the test's __main__ runner and restores the files afterwards. Result: 15/15 killed, no survivors, files restored byte-identical, on 2bed2f26c and again on 544aed9a3. The unmutated baseline was 12/12 both times.

Mutation Killed by
Remove the new check assistant-off refusal, WorkflowUser still needs the assistant
Also gate the group branch group doesn't follow the personal setting
Gate every scope after the scope dispatch group doesn't follow the personal setting
Invert the flag assistant-on reaches model, assistant-off refusal, WorkflowUser still needs the assistant
Check before the personal-access assert availability and role answers unchanged and first (the 400 and the role 403)
Check after the empty-brief 400 assistant-off refusal
Read the raw setting with a truthy default assistant-off refusal
Pass no roles to the server helper WorkflowUser still needs the assistant
Wrap the route in workflow_assistant_required 8 tests, including group, invalid-scope and route-not-decorated
Always render the personal brief block personal modal hides controls, route and template together
Also hide Task Instructions personal modal hides controls
Pass the raw setting to the template role-aware classic flag
Leave the sanitized raw value in place role-aware classic flag
Pass no roles to the workspace helper role-aware classic flag, route and template together
Hide the group modal's controls too group modal keeps its draft controls

Route policy

The route already existed, and this PR doesn't change these files.

Command 2bed2f26c 544aed9a3 (merged)
python -u functional_tests/route_tests/test_route_blueprint_policy_inventory.py 10/10 10/10
python -u functional_tests/route_tests/test_route_unauthenticated_policy_contract.py 4/4 5/5 (#1592 added a case)
python -u functional_tests/route_tests/test_route_policy_test_coverage.py 2/2 2/2
python -u -m pytest functional_tests/route_tests/test_workflow_result_context_policy.py -q -p no:cacheprovider n/a (added by #1592) 38 passed

Related suites (all pass on both heads)

  • python -u -m pytest functional_tests/route_tests/test_workflow_assist_policy.py functional_tests/route_tests/test_workflow_flow_inspection_policy.py functional_tests/test_workflow_loop_inputs.py -q -p no:cacheprovider → 117 passed, 6 subtests passed
  • pytest:
    • test_workflow_assist_limits_runtime.py → 93 passed
    • test_group_agent_drafting.py → 8 passed
    • ui_tests/test_workspace_supported_file_types_modal.py (static Jinja parse) → 6 passed
  • Scripts:
    • test_v2_documents_explorer.py 20/20
    • test_v2_workspace_sections.py 8/8
    • test_v2_workflow_ask_ai.py 7/7
    • test_agent_modal_instructions_step_order.py 6/6
    • test_agent_draft_instructions_context.py 8/8
    • test_voice_assisted_authoring.py OK
  • test_v2_documents_explorer.py and test_v2_workflow_ask_ai.py skip their TypeScript type-check step, because this worktree has no node_modules. This PR changes no TypeScript.

Docs

Command 2bed2f26c 544aed9a3 (merged)
python .\scripts\build_docs_inventory.py Rewrote only line endings in docs/_data/app_surface.yml; restored, no content change Same
python -u functional_tests/test_docs_app_surface_coverage.py 7/7 7/7
python -u functional_tests/test_docs_site_quality.py 6/6 6/6
python -u functional_tests/test_docs_link_integrity.py 2/5. Same 11 broken links as base 70c255508 (known base failure) 2/5. Same 11 broken links as the V2 tip 520132dc2; this PR's 2 new links resolve
python -u functional_tests/test_docs_release_notes_integrity.py 0/1, as on base 0/1, as on the V2 tip

test_docs_release_notes_integrity.py is a known base failure. The generated release-notes pages are stale on base and weren't regenerated here, so the stale list now also names v0.261.215 (151 releases missing from the generated pages, against 150 on the V2 tip).

Pre-existing failures

Each one fails identically on temporary detached worktrees at base 70c255508 and at the V2 tip 520132dc2. I removed both worktrees afterwards.

  • functional_tests/test_workflow_instruction_drafting.py (pytest): L49 expects the wording workflow or task instructions.
  • functional_tests/test_workflow_access_controls.py (script): expects 14 @enabled_required('allow_user_workflows') route gates.
  • functional_tests/test_model_endpoint_capacity_save_validation.py (pytest): test_capacity_save_routes_fail_safely_before_persistence[True] and [False] fail.
  • functional_tests/test_governance_route_and_wiring_coverage.py (script): 3/4. It expects a literal workspace_governance = { in route_frontend_workspace.py.
  • functional_tests/test_v2_admin_workflow_parity.py (script): AttributeError: 'list' object has no attribute 'get'. It's on the known base-failure list.

Not run: ui_tests/test_voice_assisted_form_inputs.py (Playwright; needs a live server).

  • It treats #workflow-task-brief as optional, which is consistent with this change.
  • Its L97 expects the label Workflow or Task Instructions, but base renders Task Instructions, so that line is stale on base whenever the brief renders.

Guardrails

On 544aed9a3, I ran run_v2_guardrails.py <worktree> 520132dc2323367d61e9455ecc50e82f0d7f5fa1 HEAD n9. It exited 0:

== n9: 11 changed files; app .py 3, xss surface 4, route .py 3, v2 ts/tsx 0
  [PASS] broken-access-control      (3 files)
  [PASS] xss-sinks                  (4 files)
  [PASS] swagger-routes             (2 changed route files)
  [PASS] python-syntax              (491 top-level single_app .py files compile)
  [PASS] malicious-pr-review        (no blocker findings)
  [PASS] v2-ts-sinks (added lines)  (0 ts/tsx files)

Before the merge, the same command against 70c255508 also exited 0 with every check passing; python-syntax compiled 488 files then.

The malicious-PR report reviewed 520132dc2 to HEAD (11 files). Its verdict is "Needs investigation", with no blockers. It has 181 advisory keyword findings, the same set as before the merge:

  • 176 Important. They flag security-control vocabulary on lines that add the check (roles, workflow_assistant_disabled), plus test and doc text.
  • 5 Moderate. One is the release note's wording. The other four are the test harness's ast.literal_eval, getattr, exec(compile(...)) and re.compile. exec(compile(...)) is the harness pattern that 236 existing functional tests use.

Documentation

  • Release notes updated, or not needed
    • New ### **(v0.261.215)** section under Bug Fixes, directly above Phase 6a's v0.261.214. Existing sections are untouched.
  • Feature documentation updated, or not needed
    • docs/admin/workflow.md: only the paragraph after the assistant description. The settings-table row and the troubleshooting table are untouched.
    • WORKFLOW_AI_ASSISTANT.md, CHAT_ORCHESTRATION_WORKFLOWS_ROADMAP.md, VOICE_ASSISTED_WORKFLOW_ACTION_CREATION.md
  • Fix documentation updated, or not needed
    • New docs/explanation/fixes/WORKFLOW_DRAFT_INSTRUCTIONS_ASSISTANT_GATE_FIX.md

Security checklist

  • New Flask routes include @swagger_route(security=get_auth_security())
    • No new routes; the existing route keeps it.
  • Settings sent to non-admin frontends use sanitize_settings_for_user()
    • The workspace page adds one role-aware boolean to its already-sanitized public_settings.
  • Browser JavaScript is served from local SimpleChat static assets only; no CDN-hosted JS
    • No JavaScript changed.
  • No secrets, keys, connection strings, or local-only artifacts are included

Decisions made autonomously

  1. Hide the whole personal Task Brief block, not just the button.
    • The brief only feeds drafting and isn't saved, so without the button it would be a dead field.
    • Every JS consumer of the brief, button and status element is null-safe (workspace_workflows.js and form-voice-input.js).
  2. Reuse the enable_workflow_ai_assistant key on the classic page.
    • The workspace route overwrites the sanitized value with the role-aware result. It already does the same for allow_user_workflows and enable_url_access, and the V2 bootstrap uses the same key.
    • Nothing else on the classic page reads the key; the only other template use is the admin pane.
  3. The gate runs before the "Provide a task brief…" 400. An empty personal request with the assistant off gets the 403, as it would behind the decorator.
  4. Group scope is unchanged, per the spec. The setting is documented as personal-only, and group drafting follows the group workflow settings. See the follow-up below.
  5. Docs scope.
    • Also marked the Phase 3c: Ask AI tab in the V2 workflow editor (0.261.211) #1593 roadmap follow-up (CHAT_ORCHESTRATION_WORKFLOWS_ROADMAP.md, about L950) as settled for personal scope, and added notes to VOICE_ASSISTED_WORKFLOW_ACTION_CREATION.md.
    • Left WORKFLOW_AI_ASSISTANT.md's title version at v0.261.213.
    • Didn't add a fix-index entry: the index is curated, and fix docs aren't part of the site build.
    • Didn't edit the historical v0.261.213 release note.
  6. Push target. The single git push origin attempt succeeded instead of returning the expected 403. The branch is on microsoft/simplechat, so this is a same-repository PR, and nothing was pushed to the fork.

CodeQL

  • This PR: no alerts.
    • The CodeQL check run on the merged head 544aed9a3 concluded success: "No new alerts in code changed by this pull request", with 0 annotations. Analyze (python), Analyze (javascript-typescript) and Analyze (actions) all passed.
    • The pre-merge head 2bed2f26c had the same result.
    • PR-scoped open alerts (code-scanning/alerts?pr=1596&state=open) returned none, so there was nothing to fix, and nothing was dismissed.
  • Pre-existing alerts on paullizer-react-v2-ui in files this PR touches. I re-checked these against the V2 tip after the merge. All are on lines this PR doesn't change; they aren't fixed here and nothing was dismissed.

Follow-ups

  • Revisit group-scope AI drafting with Phase 7 (Workflow orchestration follow-ons: group workflows, SimpleChat action parity and plan replay #1550).
  • Stale assertions that fail identically on base and aren't touched here:
    • test_workflow_instruction_drafting.py L49 wording
    • test_workflow_access_controls.py 14-gate count
    • test_governance_route_and_wiring_coverage.py workspace_governance = { marker
    • test_model_endpoint_capacity_save_validation.py (2 cases)
    • test_v2_admin_workflow_parity.py
    • the Workflow or Task Instructions label in ui_tests/test_voice_assisted_form_inputs.py L97 (and test_workflow_instruction_drafting.py L60)
  • The expected conflicts with Phase 6a are resolved in 544aed9a3; see Post-merge validation. The roadmap edit merged without a conflict.
  • VERSION 0.261.215 follows Phase 6a's 0.261.214, which has landed. If the number is reassigned, only this PR's references move: config.py, the test header and its version check, the docs' "0.261.215" mentions, the fix doc and the release-notes heading.

Paul Lizer (paullizer) and others added 2 commits October 1, 2026 09:10
…ant setting

POST /api/workflows/draft-instructions now refuses personal-scope requests with 403 workflow_assistant_disabled when the Enable AI Workflow Assistant setting is off for the signed-in user. The check runs after the existing personal workflow availability and WorkflowUser role checks, so those responses are unchanged, and no model client is created when it refuses. Group scope keeps following the group workflow settings.

The classic personal workspace no longer renders the Task Brief box and Draft Workflow Instructions button when the assistant isn't available to the user. The group workspace is unchanged. VERSION 0.261.215.

Refs #1548. Part of #1543.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in paullizer-react-v2-ui at 520132d (Phase 6a, VERSION 0.261.214).

Conflicts resolved by hand, without rerere:
- config.py: keep VERSION 0.261.215, one above 6a's 0.261.214.
- release_notes.md: the v0.261.215 section sits above 6a's v0.261.214
  section, which is unchanged byte for byte.

route_backend_workflows.py and docs/admin/workflow.md merged cleanly; the
diff against V2 is still only this PR's hunks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) marked this pull request as ready for review October 1, 2026 16:59
@paullizer
Paul Lizer (paullizer) merged commit 86f3f36 into paullizer-react-v2-ui Oct 1, 2026
11 checks passed
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Oct 1, 2026
Brings in N9 (the AI workflow assistant gate on personal workflow
instruction drafting) so the harness fixes on this branch are rerun
against the updated workflows route and workspace template.

Refs microsoft#1543

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
Resolves the two conflicts with 0.261.214:
- config.py keeps VERSION 0.261.216.
- release_notes.md puts the v0.261.216 section above the new v0.261.214 section. Every V2 section is unchanged.

route_backend_workflows.py merged without conflicts. Its sanitize_log_message wraps and the extended functions_appinsights import are unchanged.

N9 (microsoft#1596, 0.261.215) has not landed on V2 yet. When it does, its release-notes section will need one more merge.

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
Resolves the two conflicts with 0.261.215:
- config.py keeps VERSION 0.261.216.
- release_notes.md puts the v0.261.216 section above the new v0.261.215 section. Every V2 section is unchanged.

route_backend_workflows.py merged without conflicts. N9's import and draft-instructions gate sit above this branch's hunks, so the seven sanitize_log_message wraps moved down 10 lines unchanged, and the extended functions_appinsights import is unchanged.

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
Docs-only status update to the chat orchestration workflows roadmap at
paullizer-react-v2-ui 0.261.216:

- 6a done in microsoft#1592 (v0.261.214), with where it differs from the plan
- workflow_results capability and 6b (6b-1 server, 6b-2 V2) in progress;
  Retry uses the durable runtime/resume, not /resume-failed; run deep
  link reuses P's query form through one scope-aware helper
- N9 follow-up microsoft#1596 (v0.261.215) and microsoft#1597 log injection fix (v0.261.216)
- new follow-ups: microsoft#1598 in review, non_group_invitee product bug, stale
  base assertions, and the verified cold-import cycle
- section 10: branch from paullizer-react-v2-ui, not only base on it

Refs microsoft#1543 microsoft#1546

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.

1 participant