Skip to content

Ask for M365 Run as approval only when someone else authored the revision - #1630

Merged
Paul Lizer (paullizer) merged 3 commits into
microsoft:paullizer-react-v2-uifrom
paullizer:paullizer-run-as-approval-semantics
Oct 3, 2026
Merged

Paul Lizer (paullizer) merged 3 commits into
microsoft:paullizer-react-v2-uifrom
paullizer:paullizer-run-as-approval-semantics

Conversation

@paullizer

Copy link
Copy Markdown
Contributor

Summary

  • Run as approval only when someone else authored the revision. A Microsoft 365 workflow revision saved by its own Run as user now runs as them without a separate approval, including on its first run. That holds when modified_by equals m365_run_as_user_id, which equals the run's data user, and nobody else changed an agent or action it uses after that save. ensure_workflow_binding then records an approved binding marked self_authored: true for that exact revision, connection id and generation. It writes an append-only self_authored_approved audit event, sends no notification and is never pending. A revision someone else saved, a missing modified_by, or a later edit by someone else still asks. A new connection id or generation also asks again on someone else's revision.
  • Audience changes never ask again. ensure_workflow_binding and validate_workflow_binding stop comparing audience_version, though the binding still records it. Source-sharing approvals are unchanged, and so are the runtime audience checks that guard runs and deliveries already in progress.
  • Explicit decisions still win. A denial or cancellation recorded for a run still blocks that run (m365_workflow_declined). Revoking a binding in Profile revokes every approved copy of that revision. The revision is never self-authored again and falls back to a normal pending approval. A revocation stays in force when it races a run or is interrupted. A revocation made before this release still withdraws only its own binding, so other approvals keep working.
  • Authorship integrity and UI text. Every save path takes modified_by from the authenticated actor. The one gap was raw Data Management Cosmos DB editor edits of workflows, agents and actions, which now record the administrator. The V2 proposal card follows the backend's new approval_state: self_authored. The editor's "Saving requires re-approving Run as" note, the AI assistant's run_as_reapproval warning, the Run as help text and the classic Profile and Approvals labels follow the same rule.

Linked issue

Refs #1621

This is the Run as part of #1621. It is independent of the content-screening PR stack (#1628 and later) and touches no content screening code.

Version note: this PR and #1628 both claim 0.261.229. Whichever merges second renumbers its version: config.py, the release notes header, the test Version: headers and assert_app_version_at_least, and the feature doc.

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

VERSION moves from 0.261.228 to 0.261.229. Nothing under deployers/ changed.

Testing / validation

python is a venv overlay on the system site packages with cryptography 46.0.7, Flask 3.1.3, Werkzeug 3.1.6 and Flask-Session 0.8.0. The global environment can't collect these tests. Baselines were recorded on fd842e199 before any edit.

  • python -m pytest functional_tests/test_m365_run_as_self_authored.py -q → 51 passed. python -O -m pytest functional_tests/test_m365_run_as_self_authored.py -q → 51 passed.
  • The new tests fail without the fix:
    • The first 43, with functions_m365_approvals.py, functions_m365_execution.py, functions_m365_runtime.py and functions_data_management.py reverted to base → 15 failed, 28 passed.
    • The 4 revocation race tests, against the first commit's functions_m365_approvals.py → 4 failed.
    • The 5 pre-release revocation tests, against the second commit's → 5 failed.
  • The functional suite is the 62 test files covering the changed areas, plus the new file, test_group_collaboration_source_storage_fix.py and test_mcp_authorization_context.py, run with python -m pytest <files> -q -rfE. No new failures.
    • Baseline: 24 failed, 1797 passed. Final 1284fdfaf: 28 failed, 1899 passed, which is 24 failed test IDs plus 4 SUBFAILED subtests.
    • 23 of the 24 IDs are baseline failures: stale harnesses, the capture_execution_identity NameError, and tests that need Azure configuration.
    • The 24th is a test_m365_runtime_imports.py cold-import probe that hit its 90s subprocess timeout under load. Rerun alone, python -m pytest "functional_tests/test_m365_runtime_imports.py::test_web_scheduler_modules_import_without_reversed_bootstrap_or_network[functions_m365_execution-True]" -q → 1 passed. A baseline timeout in the same family no longer fails.
    • The 4 subtests fail the same way on base fd842e199: python -m pytest functional_tests/test_group_collaboration_source_storage_fix.py functional_tests/test_mcp_authorization_context.py -q in a base worktree → 4 failed, 49 passed.
    • One earlier loaded run hit the 300s and 150s subprocess timeouts in test_orchestration_workflow_proposal_imports.py and test_m365_conversation_lifecycle.py::test_real_conversation_deletion_fork_copy_and_retention_routes. Rerun alone → 7 passed.
  • UI tests ran in local Chromium with PLAYWRIGHT_SERVICE_URL unset: python -m pytest ui_tests/test_m365_lifecycle_and_approvals.py ui_tests/test_v2_orchestration_workflow_proposal_card.py ui_tests/test_v2_workflow_ask_ai.py ui_tests/test_v2_workflow_change_tracking.py ui_tests/test_v2_orchestration_auto_open.py -q.
    • Baseline: 177 passed, 4 errors. After: 186 passed, 4 errors, the same 4 setup errors in test_v2_orchestration_auto_open.py.
    • The lifecycle and change-tracking files, rerun after the last UI edit → 124 passed. The last two commits change no UI.
  • npm run typecheck and npm run build in application/v2_ui → passed.
  • python scripts/build_docs_inventory.py → the inventory content is unchanged; the line-ending-only diff was restored. python -m pytest functional_tests/test_docs_app_surface_coverage.py functional_tests/test_docs_site_quality.py -q → 13 passed.
  • Route policy tests on 1284fdfaf, though no routes changed:
    • python -m pytest functional_tests/route_tests/test_route_blueprint_policy_inventory.py -q → 11 passed
    • python -m pytest functional_tests/route_tests/test_route_unauthenticated_policy_contract.py -q → 6 passed
    • python -m pytest functional_tests/route_tests/test_route_policy_test_coverage.py -q → 2 passed
  • Guardrails over fd842e199..1284fdfaf, 40 changed files:
    • python scripts/check_xss_sinks.py --base-sha fd842e199 --head-sha HEAD <changed files> → passed, 29 files.
    • python scripts/check_broken_access_control.py --base-sha fd842e199 --head-sha HEAD <changed files> → passed, 20 files.
    • python scripts/check_swagger_routes.py → not run, because no route files changed.
    • git diff --check fd842e199 HEAD → clean.
  • An independent code review found two problems, both fixed with regression tests. In 873a191e1, a revocation racing a new self-authored binding could be undone. In 1284fdfaf, that fix had treated revocations made before this release, which withdrew a single binding, as revision-wide. A final review of 1284fdfaf found no remaining bugs.

Documentation

  • Release notes updated, or not needed

  • Feature documentation updated, or not needed

  • Fix documentation updated, or not needed

  • New: docs/explanation/features/M365_RUN_AS_SELF_AUTHORED_APPROVAL.md.

  • Updated: the release notes (v0.261.229, Breaking Changes), docs/guides/microsoft-365-conversation-data.md and docs/guides/create-a-workflow.md. Also the feature docs for Microsoft 365 actions, workflow editor change tracking, the workflow AI assistant, chat workflow proposals, the workflow draft service, workflow calendar schedules and the orchestration roadmap.

  • No fix doc: this is a behavior change, documented as a feature.

  • Known limitation, documented: after Create, the proposal card judges a workflow by its own modified_by. When someone else later edits an agent it uses, the card shows that only once a run waits.

Security checklist

  • New Flask routes include @swagger_route(security=get_auth_security())
  • Settings sent to non-admin frontends use sanitize_settings_for_user()
  • 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

No routes or settings payloads were added or changed.

Paul Lizer (paullizer) and others added 3 commits October 3, 2026 00:48
…sion

A workflow revision saved by its own Run as user now runs as them without a
pending approval. The approval service records an approved, audited,
self-authored binding for that exact revision, connection id and generation,
and sends no notification. A revision someone else saved, a missing
modified_by, an agent or action someone else changed after the save, or a
new connection still asks. Audience changes no longer ask again; source
sharing and the runtime checks that guard work in progress are unchanged.

Explicit decisions still win: a denial or cancellation stops its run, and
revoking a binding revokes every approved copy of that revision and keeps it
from being self-authored again. Raw Data Management edits of workflows,
agents and actions now record the administrator as their last author.

The V2 proposal card, the editor's Run as note, the AI assist warning, the
Run as help text and the classic Profile and Approvals labels follow the new
rule. Version 0.261.229.

Refs microsoft#1621

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A revocation only stopped a new self-authored binding from being created. A run
that read its records before the revocation could still write a self-authored
binding under a new key, for example after a reconnect, and every later run
reused it. Copies an interrupted fan-out missed were also reused.

ensure_workflow_binding now reuses an approved binding of a revoked revision
only when it is an ordinary approval granted after the latest revocation, and
revokes any other approved copy it finds, so _create_request can't return it as
an existing approval either. _create_self_authored_binding looks for a
revocation after writing its record and withdraws the record if it finds one,
so either side sees the other. Revoking a copy that changed meanwhile is
retried.

Refs microsoft#1621

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Before 0.261.229 revoking a Run as binding withdrew only that binding, which
covered one audience or connection generation. Treating every stored revocation
as revision-wide cancelled approvals still in use on the first run after the
upgrade.

revoke_workflow_binding now marks each binding it revokes, the chosen one and
its fan-out copies, revision_wide. Only marked revocations set the time before
which no other approval of the revision stands. A copy a run withdraws later is
left unmarked, so it can't cancel an approval the user granted in between. Any
revocation still blocks a self-authored binding for its revision.

Refs microsoft#1621

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) merged commit ca95275 into microsoft:paullizer-react-v2-ui Oct 3, 2026
11 checks passed
Paul Lizer (paullizer) added a commit that referenced this pull request Oct 3, 2026
Brings in #1630 (Run as approval only when someone else authored the
revision), which the base branch released as 0.261.229.

Layer 1 also used 0.261.229, so its release notes move into the
v0.261.230 section alongside layer 2 (#1629, already merged into this
branch). Merging this pull request takes the base from 0.261.229 to
0.261.230 and delivers both layers. VERSION stays 0.261.230.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) added a commit that referenced this pull request Oct 3, 2026
The base branch released #1630 (Run as) as 0.261.229, the number layer 1
had used. Layer 1 now ships with layer 2 in 0.261.230, so its fix doc,
user guide note and test headers say 0.261.230. FIX_VERSION only feeds
assert_app_version_at_least, which VERSION 0.261.230 satisfies.

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 3, 2026
Bring in the base branch update that released Run as (microsoft#1630) as 0.261.229 and
relabeled layer 1 as 0.261.230. The merge was clean: VERSION stays 0.261.232
and the release notes run 0.261.232, 0.261.231, 0.261.230, 0.261.229.

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 3, 2026
Run as (microsoft#1630) shipped as 0.261.229, so layers 1 and 2 ship together as
0.261.230. Link the merged 3a and 3b pull requests in the delivery table.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) deleted the paullizer-run-as-approval-semantics branch October 3, 2026 18:51
Paul Lizer (paullizer) added a commit to paullizer/simplechat that referenced this pull request Oct 3, 2026
… Run as

The microsoft#1621 screening changes (microsoft#1628, microsoft#1631, microsoft#1632) and microsoft#1630 have landed on
paullizer-react-v2-ui, so the roadmap no longer describes V2 where it says a
result is withheld when a source document becomes unreadable, or that every
material edit needs Run as approval again.

- §7: reads and deliveries re-check the chat, workflow and run that hold a
  result; since 0.261.231 the documents a run's tasks read are provenance only.
- Gotcha 36: delivery no longer withholds a result over an unreadable source.
- 6a: "lost access" in its Done when now means access to the workflow or run.
- Gotchas 4 and 15: since 0.261.229 a revision the Run as user saved, assistant
  edits included, runs as them without a separate approval; changes saved by
  someone else still ask again.
- The intro names both changes.

Docs only; the original plan text in §5 and the shipped Phase 3 notes are left
as history.

Refs microsoft#1543 microsoft#1546 microsoft#1621

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 6, 2026
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 6, 2026
Captured with --write-golden on unmodified ca95275 (0.261.229, microsoft#1630) in a fresh detached worktree. The only difference from the fd842e1 capture is the captured_from label; microsoft#1630 changed no captured byte.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer) added a commit that referenced this pull request Oct 6, 2026
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