Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 1 addition & 28 deletions application/single_app/admin_settings_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,6 @@
CHAT_ORCHESTRATION_MAX_WORKFLOWS_DEFAULT,
CHAT_ORCHESTRATION_MAX_WORKFLOWS_MAX,
CHAT_ORCHESTRATION_MAX_WORKFLOWS_MIN,
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_DEFAULT,
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MAX,
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MIN,
CHAT_ORCHESTRATION_WORKFLOW_RUN_WAIT_SECONDS_DEFAULT,
CHAT_ORCHESTRATION_WORKFLOW_RUN_WAIT_SECONDS_MAX,
CHAT_ORCHESTRATION_WORKFLOW_RUN_WAIT_SECONDS_MIN,
Expand All @@ -226,7 +223,6 @@
WorkflowLoopLimitError,
validate_chat_orchestration_max_workflow_handoffs_per_day,
validate_chat_orchestration_max_workflows_per_user,
validate_chat_orchestration_min_workflow_interval_seconds,
validate_chat_orchestration_workflow_run_wait_max_seconds,
validate_workflow_max_loop_items,
validate_workflow_max_repeat_iterations,
Expand Down Expand Up @@ -4580,23 +4576,6 @@
"depends_on": {"key": "enable_chat_orchestration", "equals": True},
"group": {"id": "limits", "label": "Limits", "variant": "limits"},
},
{
"key": "chat_orchestration_min_workflow_interval_seconds",
"type": "number",
"label": "Minimum Schedule Interval For Workflows Created From Chat (seconds)",
"help": (
"The shortest repeat interval a chat plan may give a workflow it creates. The "
"general Workflow Minimum Schedule Interval still applies when it is longer. "
"Daily, weekly and monthly schedules always pass. Default is 3,600 (hourly); "
"supported range is 60-86,400."
),
"default": CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_DEFAULT,
"min": CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MIN,
"max": CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MAX,
"step": 1,
"depends_on": {"key": "enable_chat_orchestration", "equals": True},
"group": {"id": "limits", "label": "Limits", "variant": "limits"},
},
{
"key": "chat_orchestration_max_workflow_handoffs_per_day",
"type": "number",
Expand Down Expand Up @@ -4893,7 +4872,7 @@
"label": "Workflow Minimum Schedule Interval (seconds)",
"help": (
"Shortest repeat interval, in seconds, allowed when a personal or group "
"workflow interval schedule is created or changed. Workflows already saved "
"workflow interval schedule is created or changed, including workflows from chat. Workflows already saved "
"keep their schedule and keep running when this is raised. Calendar "
"schedules are not affected. Default is 1; supported range is 1-86,400."
),
Expand Down Expand Up @@ -9876,12 +9855,6 @@ def _normalize_field_value(key, value, field):
except WorkflowLoopLimitError as error:
return None, error.public_message, None

if key == "chat_orchestration_min_workflow_interval_seconds":
try:
return validate_chat_orchestration_min_workflow_interval_seconds(value), None, None
except WorkflowLoopLimitError as error:
return None, error.public_message, None

if key == "m365_trusted_download_hosts":
try:
hosts = normalize_m365_transport_settings("auto", value)[key]
Expand Down
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.316"
VERSION = "0.261.317"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
7 changes: 7 additions & 0 deletions application/single_app/functions_orchestration_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,13 @@
workflow_propose step that lists it in "delivers". Nothing is created until the user approves the
proposal card shown after the answer, so never say that a workflow was created or scheduled.

Honor an explicitly requested cadence when it meets limits.min_interval_seconds; never replace
every-minute timing with hourly timing. When recurring timing is missing, use the existing
elicitation shape to ask how often it should run, with relevant allowed choices such as every

Check warning on line 288 in application/single_app/functions_orchestration_planner.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
minute, every hour, daily, or weekly. Do not invent a cadence. If the requested interval is below
the administrator's minimum, explain the minimum and ask for an allowed alternative; do not
silently lengthen it. Do not add policy warnings when the requested schedule is allowed.

Check warning on line 291 in application/single_app/functions_orchestration_planner.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.

The workflow_propose step takes no "depends_on" and no "inputs", and no other step, input binding
or final_response may name it or its output. When the request also wants a result now, answer it
once with the usual steps and select that answer as final_response; otherwise a short compose answer
Expand Down
11 changes: 2 additions & 9 deletions application/single_app/functions_personal_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@
normalize_workflow_definition, workflow_definition_for_editor,
)
from functions_workflow_limits import (
get_orchestration_workflow_min_interval_seconds,
get_workflow_min_schedule_interval_seconds,
)
from functions_workflow_runtime_store import workflow_runtime_store
from functions_workflow_schedules import (
enforce_orchestration_workflow_cadence,
enforce_workflow_schedule_minimum,
is_calendar_workflow_schedule,
next_workflow_schedule_run,
Expand Down Expand Up @@ -167,16 +165,13 @@ def _normalize_personal_workflow_conversation_id(user_id, workflow_data, existin
return conversation_id


def _normalize_schedule(schedule_payload, existing_workflow=None, settings=None, orchestration=False):
def _normalize_schedule(schedule_payload, existing_workflow=None, settings=None):
"""Normalize an interval or calendar schedule for saving.

The administrator's minimum interval governs only a new or changed interval schedule, so a
raised minimum never blocks re-saving a workflow on the interval it already runs on. A workflow
that chat orchestration is creating must also meet the higher minimum for such workflows.
raised minimum never blocks re-saving a workflow on the interval it already runs on.
"""
schedule = normalize_workflow_schedule(schedule_payload)
if orchestration:
enforce_orchestration_workflow_cadence(schedule, get_orchestration_workflow_min_interval_seconds(settings))
if workflow_schedule_minimum_applies(schedule, existing_workflow):
enforce_workflow_schedule_minimum(schedule, get_workflow_min_schedule_interval_seconds(settings))
return schedule
Expand Down Expand Up @@ -1103,8 +1098,6 @@ def build_personal_workflow_document(user_id, workflow_data, actor_user_id=None,
workflow_data.get('schedule'),
existing_workflow=existing_workflow,
settings=settings,
# A saved chat plan keeps the chat-workflow floor whenever its schedule is saved.
**({'orchestration': True} if origin is not None or existing_plan_replay_task is not None else {}),
)

workflow = {
Expand Down
16 changes: 2 additions & 14 deletions application/single_app/functions_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,12 @@
from functions_workflow_limits import (
CHAT_ORCHESTRATION_MAX_HANDOFFS_PER_DAY_DEFAULT,
CHAT_ORCHESTRATION_MAX_WORKFLOWS_DEFAULT,
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_DEFAULT,
CHAT_ORCHESTRATION_WORKFLOW_RUN_WAIT_SECONDS_DEFAULT,
WORKFLOW_LOOP_ITEMS_DEFAULT,
WORKFLOW_MIN_SCHEDULE_INTERVAL_DEFAULT,
WORKFLOW_REPEAT_ITERATIONS_DEFAULT,
validate_chat_orchestration_max_workflow_handoffs_per_day,
validate_chat_orchestration_max_workflows_per_user,
validate_chat_orchestration_min_workflow_interval_seconds,
validate_chat_orchestration_workflow_run_wait_max_seconds,
validate_workflow_max_loop_items,
validate_workflow_max_repeat_iterations,
Expand Down Expand Up @@ -269,6 +267,7 @@ def normalize_public_workspace_display_settings(settings):
# Switches that no longer exist. A stored value is removed on load so it is not echoed to
# the browser, and so the V2 admin page stops drawing a switch that changes nothing.
RETIRED_SETTING_KEYS = (
"chat_orchestration_min_workflow_interval_seconds",
# Gather / Reason / Render became the only chat orchestration plan contract. Earlier
# releases already left it out of a saved run's settings fingerprint, so removing it
# does not change that fingerprint.
Expand Down Expand Up @@ -1660,10 +1659,8 @@ def get_settings(use_cosmos=False, include_source=False):
# as that user on a schedule. Off by default; turning it off makes existing replay
# workflows refuse to run with a fixed reason rather than deleting them.
'enable_workflow_plan_replay': False,
# Workflows that a chat plan creates for a user: how many one user may hold, and the
# shortest interval one may run on (also never shorter than the general workflow minimum).
# How many workflows created from chat one user may hold.
'chat_orchestration_max_workflows_per_user': CHAT_ORCHESTRATION_MAX_WORKFLOWS_DEFAULT,
'chat_orchestration_min_workflow_interval_seconds': CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_DEFAULT,
# How many hand-offs one user may start from chat in a rolling 24 hours.
'chat_orchestration_max_workflow_handoffs_per_day': CHAT_ORCHESTRATION_MAX_HANDOFFS_PER_DAY_DEFAULT,
# Planner model binding. Unset falls back to the deployment's default chat model,
Expand Down Expand Up @@ -2527,15 +2524,6 @@ def update_settings(new_settings, *, expected_etag=None):
new_settings['chat_orchestration_max_workflows_per_user']
),
}
if isinstance(new_settings, dict) and 'chat_orchestration_min_workflow_interval_seconds' in new_settings:
new_settings = {
**new_settings,
'chat_orchestration_min_workflow_interval_seconds': (
validate_chat_orchestration_min_workflow_interval_seconds(
new_settings['chat_orchestration_min_workflow_interval_seconds']
)
),
}
expected_etag = expected_etag or new_settings.get("_etag")
updates = {
key: copy.deepcopy(value)
Expand Down
2 changes: 1 addition & 1 deletion application/single_app/functions_workflow_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def __init__(self, public_message=WORKFLOW_FILE_SYNC_SOURCE_UNAVAILABLE_MESSAGE)


class WorkflowCadenceError(WorkflowPublicValidationError):
"""A workflow created from chat would run more often than its own administrator minimum allows."""
"""A proposed workflow would run more often than the shared administrator minimum allows."""

code = "cadence_below_minimum"

Expand Down
53 changes: 3 additions & 50 deletions application/single_app/functions_workflow_limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@
WORKFLOW_MIN_SCHEDULE_INTERVAL_MIN = 1
WORKFLOW_MIN_SCHEDULE_INTERVAL_MAX = 86400
WORKFLOW_MIN_SCHEDULE_INTERVAL_SETTING = "workflow_min_schedule_interval_seconds"
# Workflows that chat orchestration creates for a user have their own cap and a higher floor, so a
# conversation cannot fill a workspace with workflows or schedule one to run every minute.
# Workflows that chat orchestration creates for a user have their own count cap.
CHAT_ORCHESTRATION_MAX_WORKFLOWS_DEFAULT = 20
CHAT_ORCHESTRATION_MAX_WORKFLOWS_MIN = 1
CHAT_ORCHESTRATION_MAX_WORKFLOWS_MAX = 100
CHAT_ORCHESTRATION_MAX_WORKFLOWS_SETTING = "chat_orchestration_max_workflows_per_user"
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_DEFAULT = 3600
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MIN = 60
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MAX = 86400
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_SETTING = "chat_orchestration_min_workflow_interval_seconds"
# Each accepted hand-off creates a one-time workflow and queues a durable run, so a rolling 24-hour
# limit per user bounds how much background work chat can start, separately from the cap above.
CHAT_ORCHESTRATION_MAX_HANDOFFS_PER_DAY_DEFAULT = 5
Expand Down Expand Up @@ -289,51 +284,9 @@ def get_chat_orchestration_workflow_run_wait_max_seconds(settings=None):
)


def validate_chat_orchestration_min_workflow_interval_seconds(value):
"""Validate the shortest interval a workflow created from chat may run on."""
candidate = _whole_number(value)
if (
candidate is None
or not CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MIN <= candidate <= CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_MAX
):
raise WorkflowLoopLimitError(
"Minimum Schedule Interval For Workflows Created From Chat must be a whole number of "
"seconds from 60 to 86,400.",
code="chat_orchestration_workflow_interval_invalid",
)
return candidate


def get_chat_orchestration_min_workflow_interval_seconds(settings=None):
"""Read the orchestration schedule floor on its own, before the general floor is applied."""
if settings is None:
# Settings initialize application clients; load them only at a request boundary.
from functions_settings import get_settings

settings = get_settings()
if not isinstance(settings, Mapping):
raise WorkflowLoopLimitError(
"The schedule minimum for workflows created from chat is temporarily unavailable.",
code="chat_orchestration_workflow_interval_unavailable",
)
return validate_chat_orchestration_min_workflow_interval_seconds(
settings.get(
CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_SETTING, CHAT_ORCHESTRATION_MIN_WORKFLOW_INTERVAL_DEFAULT,
)
)


def get_orchestration_workflow_min_interval_seconds(settings=None):
"""The shortest interval an orchestration-created workflow may use: the larger of both floors."""
if settings is None:
# Settings initialize application clients; load them only at a request boundary.
from functions_settings import get_settings

settings = get_settings()
return max(
get_chat_orchestration_min_workflow_interval_seconds(settings),
get_workflow_min_schedule_interval_seconds(settings),
)
"""Use the same administrator minimum for chat planning as for every workflow editor."""
return get_workflow_min_schedule_interval_seconds(settings)


def effective_workflow_loop_limit(max_items=None, *, settings=None, policy=None):
Expand Down
7 changes: 3 additions & 4 deletions application/single_app/functions_workflow_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,16 @@


def enforce_orchestration_workflow_cadence(schedule, min_interval_seconds):
"""Refuse an interval that runs a workflow created from chat more often than its own floor.
"""Report a blueprint interval below the shared policy with its repairable cadence code.

Check warning on line 231 in application/single_app/functions_workflow_schedules.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.

Calendar schedules repeat at most daily, so they always pass.
"""
seconds = workflow_schedule_interval_seconds(schedule)
if seconds is not None and seconds < min_interval_seconds:
# The minimum is administrator policy, not caller text, so naming it is safe.
raise WorkflowCadenceError(
'Workflows created from chat cannot run this often. '
f'Choose an interval of at least {format_workflow_schedule_duration(min_interval_seconds)}, '
'or a daily, weekly or monthly schedule.'
'This schedule runs more often than the administrator allows. '
f'Choose an interval of at least {format_workflow_schedule_duration(min_interval_seconds)}.'
)
return schedule

Expand Down
2 changes: 1 addition & 1 deletion application/single_app/route_backend_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -3240,7 +3240,7 @@ def _plan_replay_response(run_id, conversation_id, decide):
body, code = replay.plan_replay_error_response(exc.code, exc.public_message, exc.refusals)
return jsonify(body), code
except replay.WorkflowPublicValidationError as exc:
# A reviewed, data-free settings error, such as a schedule faster than the chat minimum.
# A reviewed, data-free settings error, such as a schedule below the shared minimum.
return jsonify({'error': exc.public_message, 'code': exc.code}), 422
except ValueError as exc:
log_event(
Expand Down
3 changes: 0 additions & 3 deletions application/single_app/route_frontend_admin_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,6 @@ def _clamped(field, default, low, high):
'chat_orchestration_max_workflows_per_user': _clamped(
'chat_orchestration_max_workflows_per_user', 20, 1, 100
),
'chat_orchestration_min_workflow_interval_seconds': _clamped(
'chat_orchestration_min_workflow_interval_seconds', 3600, 60, 86400
),
'chat_orchestration_max_workflow_handoffs_per_day': _clamped(
'chat_orchestration_max_workflow_handoffs_per_day', 5, 1, 100
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,6 @@ <h5>
value="{{ settings.chat_orchestration_max_workflows_per_user if settings.chat_orchestration_max_workflows_per_user is not none else 20 }}">
<div class="form-text">The most workflows a chat plan may create for one user. Workflows built in the workflow editor do not count.</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="chat_orchestration_min_workflow_interval_seconds">Minimum schedule interval for workflows created from chat (seconds)</label>
<input type="number" class="form-control" id="chat_orchestration_min_workflow_interval_seconds" name="chat_orchestration_min_workflow_interval_seconds" min="60" max="86400" step="1"
value="{{ settings.chat_orchestration_min_workflow_interval_seconds if settings.chat_orchestration_min_workflow_interval_seconds is not none else 3600 }}">
<div class="form-text">The shortest repeat interval a chat plan may give a workflow it creates. Daily, weekly and monthly schedules always pass.</div>
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="chat_orchestration_max_workflow_handoffs_per_day">Hand-offs from chat per user per day</label>
<input type="number" class="form-control" id="chat_orchestration_max_workflow_handoffs_per_day" name="chat_orchestration_max_workflow_handoffs_per_day" min="1" max="100" step="1"
Expand Down
3 changes: 2 additions & 1 deletion application/single_app/templates/admin/_panes/workflow.html
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ <h5>
value="{{ settings.workflow_min_schedule_interval_seconds|default(1) }}" />
<div class="form-text" id="workflow-min-schedule-interval-help">
Shortest repeat interval, in seconds, allowed when a personal or group workflow
interval schedule is created or changed. Default is 1; supported range is 1-86,400.
interval schedule is created or changed, including workflows from chat.
Default is 1; supported range is 1-86,400.
Workflows already saved keep their schedule and keep running when this is raised.
Calendar schedules are not affected.
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const REASON_TEXT: Record<string, string> = {
agent_unavailable: 'The agent this workflow uses is no longer available to you.',
file_sync_source_unavailable: 'A File Sync source this workflow uses is no longer available to you.',
reference_unavailable: 'A document this workflow uses is no longer available to you.',
cadence_below_minimum: 'This schedule runs more often than workflows created from chat allow.',
cadence_below_minimum: 'This schedule runs more often than the administrator allows.',
proposal_unavailable: 'This proposal cannot be created as planned.',
content_review: 'This response is in content review, so its proposal cannot be used.',
workflow_proposals_disabled: 'Workflow proposals are turned off.',
Expand Down
Loading
Loading