From f86ae01d336f6086bb2f6bf6a78b9e1d0042807e Mon Sep 17 00:00:00 2001 From: aihop <4408344+hhyykk@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:34:29 +0800 Subject: [PATCH 1/2] refactor(quota): own base admission decisions in TypeScript Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com> --- examples/semantic-vocabulary-drift-smoke.py | 8 +- .../control_plane/effect_runtime_handlers.ts | 1 + .../goals/goal_frontier/__init__.py | 22 - loopx/control_plane/quota/decision_summary.py | 259 ++-------- loopx/control_plane/quota/run_decision.ts | 198 ++++++++ loopx/semantics/production.py | 32 ++ loopx/semantics/vocabulary_v0.json | 33 +- scripts/quota_run_decision_witness.mts | 28 ++ .../architecture/test_semantic_production.py | 1 + .../test_semantic_quota_run_witness.py | 63 +++ .../test_semantic_vocabulary_drift.py | 2 +- .../control_plane/test_quota_run_decision.py | 56 +++ .../fixtures/quota_run_decision.json | 473 ++++++++++++++++++ .../quota_run_decision.test.ts | 20 + tsconfig.control-plane.json | 1 + 15 files changed, 937 insertions(+), 260 deletions(-) create mode 100644 loopx/control_plane/quota/run_decision.ts create mode 100644 scripts/quota_run_decision_witness.mts create mode 100644 tests/architecture/test_semantic_quota_run_witness.py create mode 100644 tests/control_plane_ts/fixtures/quota_run_decision.json create mode 100644 tests/control_plane_ts/quota_run_decision.test.ts diff --git a/examples/semantic-vocabulary-drift-smoke.py b/examples/semantic-vocabulary-drift-smoke.py index f24251c8ec..38b06c5358 100755 --- a/examples/semantic-vocabulary-drift-smoke.py +++ b/examples/semantic-vocabulary-drift-smoke.py @@ -202,6 +202,7 @@ LITERAL_SCAN_ROOTS = ["loopx"] # Input producers with an executable witness in loopx.semantics.production. INPUT_PRODUCER_ANCHOR = { + "effective_action": "loopx/control_plane/quota/run_decision.ts::resolveQuotaRunDecision", "turn_result_kind": "loopx/control_plane/turn_driver/transaction.py::_result_kind", "loop_disposition": "loopx/control_plane/turn_driver/loop_controller.py::decide_loop_disposition", # The settlement binding kind is decided inside the TypeScript builder, so @@ -210,14 +211,13 @@ } PRODUCER_VOCABULARY_ANCHOR = { "effective_action", "turn_route", "loop_disposition", "agent_scope_frontier_action", "turn_result_kind", - # First cross-runtime vocabulary carrying executed production evidence. One - # named boundary, not a claim about the rest of the cross-runtime set. + # Executed evidence is bound to named entrypoints, not every cross-runtime producer. "settlement_binding_kind", } RETURN_PRODUCER_ANCHOR = { "turn_route": {"loopx/control_plane/turn_driver/driver.py::_typed_route", "loopx/control_plane/turn_driver/loop_controller.py::_envelope_route", "loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan"}, "loop_disposition": {"loopx/control_plane/turn_driver/turn_contract_generated.py::project_turn_route"}, - "effective_action": {"loopx/control_plane/quota/decision_summary.py::quota_effective_action", "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action"}, + "effective_action": {"loopx/control_plane/quota/run_decision.ts::quotaEffectiveAction", "loopx/control_plane/quota/run_decision.ts::taskOrchestrationEffectiveAction"}, "turn_result_kind": {"loopx/control_plane/turn_driver/executor.py::_task_validation_receipt"}, } # Reviewed output arguments/paths replace the old any-enum-use heuristic. These @@ -231,7 +231,7 @@ }, } RETURN_PATH_ANCHOR = { - "effective_action": {"loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action": [0]}, + "effective_action": {"loopx/control_plane/quota/run_decision.ts::taskOrchestrationEffectiveAction": [0]}, "turn_route": {"loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan": ["route", "kind"]}, "turn_result_kind": {"loopx/control_plane/turn_driver/executor.py::_task_validation_receipt": ["recovery_kind"]}, } diff --git a/loopx/control_plane/effect_runtime_handlers.ts b/loopx/control_plane/effect_runtime_handlers.ts index 7e0b25b5d9..a15096f14d 100644 --- a/loopx/control_plane/effect_runtime_handlers.ts +++ b/loopx/control_plane/effect_runtime_handlers.ts @@ -397,6 +397,7 @@ export function createEffectRuntimeHandlers( lazyHandler(() => import("./quota/unsettled_host_turn_recovery.ts"), ({reduceUnsettledHostTurnRecovery}) => reduceUnsettledHostTurnRecovery), ], ["quota.turn_envelope.evaluate", lazyHandler(() => import("./quota/turn_envelope.ts"), ({evaluateTurnEnvelope}) => evaluateTurnEnvelope)], + ["quota.run_decision", lazyHandler(() => import("./quota/run_decision.ts"), ({resolveQuotaRunDecision}) => resolveQuotaRunDecision)], ["quota.scoped_override.project", lazyHandler(() => import("./quota/scoped_override.ts"), ({projectScopedOverride}) => projectScopedOverride)], ["task_lease.owner_eligibility", lazyHandler(() => import("./work_items/task_lease_eligibility.ts"), ({evaluateTaskLeaseOwnerEligibility}) => evaluateTaskLeaseOwnerEligibility)], ["task_lease.acquire.native", lazyHandler(() => import("./work_items/task_lease_acquire.ts"), ({executeTaskLeaseAcquire}) => executeTaskLeaseAcquire)], diff --git a/loopx/control_plane/goals/goal_frontier/__init__.py b/loopx/control_plane/goals/goal_frontier/__init__.py index ead42f1f24..a355fd7d51 100644 --- a/loopx/control_plane/goals/goal_frontier/__init__.py +++ b/loopx/control_plane/goals/goal_frontier/__init__.py @@ -212,28 +212,6 @@ def align_autonomous_replan_guidance_with_acceptance_policy( return aligned -def autonomous_replan_decision_allowed( - *, - replan_obligation: dict[str, Any] | None, - plan_ok: bool, - workspace_blocked: bool, - automation_prompt_upgrade_required: bool, - agent_id: str | None = None, - registered_agent_ids: list[str] | None = None, -) -> bool: - return bool( - autonomous_replan_is_required(replan_obligation) - and autonomous_replan_scope_decision( - replan_obligation, - agent_id=agent_id, - registered_agent_ids=registered_agent_ids, - ).get("applies") - and plan_ok - and not workspace_blocked - and not automation_prompt_upgrade_required - ) - - def _normalize_replan_agent_id(value: Any) -> str | None: text = str(value or "").strip() return text or None diff --git a/loopx/control_plane/quota/decision_summary.py b/loopx/control_plane/quota/decision_summary.py index 2f81406c6b..19fa5f23b3 100644 --- a/loopx/control_plane/quota/decision_summary.py +++ b/loopx/control_plane/quota/decision_summary.py @@ -1,5 +1,4 @@ from __future__ import annotations -from .effective_action import EffectiveAction from dataclasses import dataclass from typing import Any, TypedDict @@ -7,9 +6,11 @@ from ...state_projection import actions_are_projection_aligned from ..goals.contract_health import project_contract_health_for_goal from ..goals.goal_frontier import ( - autonomous_replan_decision_allowed, + autonomous_replan_is_required, + autonomous_replan_scope_decision, goal_frontier_is_terminal_no_followup, ) +from ..effect_runtime import effect_runtime_result from ..todos.contract import normalize_todo_claimed_by from ..work_items.work_lane import work_lane_contract_is_due_monitor_attempt @@ -196,6 +197,11 @@ def refine_quota_recommended_action( return refined +def _guard_strings(guard: dict[str, Any] | None, fields: tuple[str, ...]) -> dict[str, str] | None: + # Preserve the compatibility caller's Python string coercion, including whitespace. + return {field: str(guard.get(field) or "") for field in fields} if guard else None + + def resolve_quota_run_decision( *, normal_delivery_allowed: bool, @@ -221,219 +227,36 @@ def resolve_quota_run_decision( ) -> QuotaRunDecision: """Apply the ordered final guards for one quota run decision.""" - capability_repair_allowed = False - workspace_repair_allowed = False - inbox_priority_due = inbox_reply_due or inbox_material_review_due - if ( - capability_gate - and capability_gate.get("action") != "run" - and not capability_monitor_fallback - ): - normal_delivery_allowed = False - recovery_delivery_allowed = False - if capability_gate.get("action") == "repair_bridge": - capability_repair_allowed = True - reason = str( - capability_gate.get("reason") or "capability bridge repair required" - ) - else: - reason = str( - capability_gate.get("reason") - or "selected todo capability is unavailable" - ) - if workspace_guard: - normal_delivery_allowed = False - recovery_delivery_allowed = False - self_repair_allowed = False - capability_repair_allowed = False - workspace_repair_allowed = True - reason = str( - workspace_guard.get("reason") or "agent workspace guard blocks delivery" - ) - if automation_prompt_upgrade_required: - normal_delivery_allowed = False - recovery_delivery_allowed = False - self_repair_allowed = False - capability_repair_allowed = False - workspace_repair_allowed = False - reason = str( - (automation_prompt_upgrade or {}).get("reason") - or "identity-aware automation prompt upgrade is required" - ) - - should_run = bool( - normal_delivery_allowed - or recovery_delivery_allowed - or self_repair_allowed - or capability_repair_allowed - or workspace_repair_allowed - ) - effective_action = quota_effective_action( - normal_delivery_allowed=normal_delivery_allowed, - recovery_delivery_allowed=recovery_delivery_allowed, - self_repair_allowed=self_repair_allowed, - capability_repair_allowed=capability_repair_allowed, - workspace_repair_allowed=workspace_repair_allowed, - stall_self_repair=stall_self_repair, - state=state, - quota=quota, - ) - replan_decision_allowed = ( - not inbox_priority_due - and autonomous_replan_decision_allowed( - replan_obligation=replan_obligation, - plan_ok=goal_health_ok, - workspace_blocked=bool(workspace_guard), - automation_prompt_upgrade_required=automation_prompt_upgrade_required, - agent_id=agent_frontier_id, - registered_agent_ids=registered_agent_ids, - ) - ) - if replan_decision_allowed: - normal_delivery_allowed = False - recovery_delivery_allowed = False - should_run = True - effective_action = EffectiveAction.AUTONOMOUS_REPLAN_REQUIRED.value - reason = ( - "autonomous replan obligation is selected before monitor quiet " - "or agent-scope wait classification" - ) - - terminal_no_followup = goal_frontier_is_terminal_no_followup( - projection=goal_frontier_projection - ) - if terminal_no_followup and not inbox_priority_due: - quota = { - **quota, - "state": "terminal_no_followup", - "reason": ( - "derived terminal no-follow-up is confirmed by complete todo " - "sources and an empty frontier" - ), - } - state = "terminal_no_followup" - normal_delivery_allowed = False - recovery_delivery_allowed = False - self_repair_allowed = False - capability_repair_allowed = False - workspace_repair_allowed = False - should_run = False - effective_action = EffectiveAction.TERMINAL_NO_FOLLOWUP.value - reason = ( - "validated closure evidence derives terminal no-follow-up from " - "complete todo sources and an empty frontier; stop recurring " - "automation until an explicit resume" - ) - - if automation_prompt_upgrade_required and not terminal_no_followup: - should_run = False - effective_action = EffectiveAction.AUTOMATION_PROMPT_UPGRADE_REQUIRED.value - elif inbox_priority_due: - should_run = True - normal_delivery_allowed = True - recovery_delivery_allowed = False - self_repair_allowed = False - capability_repair_allowed = False - workspace_repair_allowed = False - if inbox_reply_due: - effective_action = EffectiveAction.LARK_INBOX_REPLY_DUE.value - reason = ( - "a direct Lark question, bot mention, or verified reply to the bot " - "is pending reply" - ) - else: - effective_action = EffectiveAction.OPERATOR_INBOX_MATERIAL_REVIEW_DUE.value - reason = ( - "captured unaddressed operator-inbox material is pending bounded review" - ) - - effective_action, reason = _task_orchestration_effective_action( - task_orchestration_contract, - should_run=should_run, - normal_delivery_allowed=normal_delivery_allowed, - effective_action=effective_action, - reason=reason, - ) - return QuotaRunDecision( - normal_delivery_allowed=normal_delivery_allowed, - recovery_delivery_allowed=recovery_delivery_allowed, - self_repair_allowed=self_repair_allowed, - capability_repair_allowed=capability_repair_allowed, - workspace_repair_allowed=workspace_repair_allowed, - should_run=should_run, - effective_action=effective_action, - reason=reason, - state=state, - quota=quota, - replan_decision_allowed=replan_decision_allowed, - ) - - -def quota_effective_action( - *, - normal_delivery_allowed: bool, - recovery_delivery_allowed: bool, - self_repair_allowed: bool, - capability_repair_allowed: bool, - workspace_repair_allowed: bool, - stall_self_repair: dict[str, Any] | None, - state: str, - quota: dict[str, Any], -) -> str: - if normal_delivery_allowed: - return EffectiveAction.NORMAL_RUN.value - if recovery_delivery_allowed: - return EffectiveAction.OUTCOME_FLOOR_RECOVERY.value - if workspace_repair_allowed: - return EffectiveAction.AGENT_WORKSPACE_REPAIR.value - if self_repair_allowed: - repair_action = ( - stall_self_repair.get("effective_action") - if isinstance(stall_self_repair, dict) - else None - ) - return str(repair_action or EffectiveAction.CONTROL_PLANE_REPAIR.value) - if capability_repair_allowed: - return EffectiveAction.CAPABILITY_BRIDGE_REPAIR.value - if state == "operator_gate": - return EffectiveAction.OPERATOR_GATE_NOTIFY.value - if state == "blocked_health": - return EffectiveAction.BLOCKED_HEALTH.value - if state == "throttled": - return EffectiveAction.THROTTLED_SKIP.value - if state in {"focus_wait", "waiting"} or quota.get("focus_wait"): - return EffectiveAction.BLOCKED_WAIT.value - return EffectiveAction.QUOTA_SKIP.value - - -def _task_orchestration_effective_action( - contract: dict[str, Any] | None, - *, - should_run: bool, - normal_delivery_allowed: bool, - effective_action: str, - reason: str, -) -> tuple[str, str]: - if ( - contract - and str(contract.get("execution_state") or "ready") == "ready" - and should_run - and normal_delivery_allowed - and effective_action == EffectiveAction.NORMAL_RUN.value - ): - if contract.get("mode") == "adaptive": - return ( - EffectiveAction.COORDINATE_TASK_BUNDLE.value, - ( - "the task coordinator may use admitted child lanes before its " - "own worker-lane delivery" - ), - ) - return ( - EffectiveAction.COORDINATE_TASK_BUNDLE.value, - ( - "the explicitly selected task coordinator must activate or resume " - "eligible peer lanes before doing its own worker-lane delivery" - ), - ) - return effective_action, reason + replan_required = autonomous_replan_is_required(replan_obligation) + # Scope evaluation can synthesize a legacy identity; preserve its lazy read boundary. + scope = autonomous_replan_scope_decision( + replan_obligation, agent_id=agent_frontier_id, + registered_agent_ids=registered_agent_ids, + ) if replan_required and not (inbox_reply_due or inbox_material_review_due) else {} + repair_action = (stall_self_repair.get("effective_action") + if isinstance(stall_self_repair, dict) else None) + result = effect_runtime_result("quota.run_decision", { + "normal_delivery_allowed": normal_delivery_allowed, + "recovery_delivery_allowed": recovery_delivery_allowed, + "self_repair_allowed": self_repair_allowed, + "repair_effective_action": str(repair_action or ""), + "state": state, "reason": reason, + "quota_focus_wait": bool(quota.get("focus_wait")), + "capability_gate": _guard_strings(capability_gate, ("action", "reason")), + "capability_monitor_fallback": bool(capability_monitor_fallback), + "workspace_guard": _guard_strings(workspace_guard, ("reason",)), + "automation_prompt_upgrade": _guard_strings(automation_prompt_upgrade, ("reason",)), + "automation_prompt_upgrade_required": automation_prompt_upgrade_required, + "replan_required": replan_required, + "replan_applies": bool(scope.get("applies")), + "goal_health_ok": goal_health_ok, + "inbox_reply_due": inbox_reply_due, + "inbox_material_review_due": inbox_material_review_due, + "terminal_no_followup": goal_frontier_is_terminal_no_followup( + projection=goal_frontier_projection), + "task_orchestration_contract": _guard_strings( + task_orchestration_contract, ("execution_state", "mode")), + }) + patch = result.pop("quota_patch") + # Only typed edits cross the wire; preserve source numbers and unmodified metadata. + return QuotaRunDecision(quota={**quota, **patch} if patch else quota, **result) diff --git a/loopx/control_plane/quota/run_decision.ts b/loopx/control_plane/quota/run_decision.ts new file mode 100644 index 0000000000..a4527f027b --- /dev/null +++ b/loopx/control_plane/quota/run_decision.ts @@ -0,0 +1,198 @@ +import type {JsonObject} from "../effect_program.ts"; +import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts"; +import {requireBoolean, requireJsonObject} from "../runtime_decode.ts"; +import {EffectiveAction} from "./effective_action.generated.ts"; + +interface RunFacts { + normal_delivery_allowed: boolean; + recovery_delivery_allowed: boolean; + self_repair_allowed: boolean; + repair_effective_action: string; + state: string; + reason: string; + quota_focus_wait: boolean; + capability_gate: JsonObject | null; + capability_monitor_fallback: boolean; + workspace_guard: JsonObject | null; + automation_prompt_upgrade: JsonObject | null; + automation_prompt_upgrade_required: boolean; + replan_required: boolean; + replan_applies: boolean; + goal_health_ok: boolean; + inbox_reply_due: boolean; + inbox_material_review_due: boolean; + terminal_no_followup: boolean; + task_orchestration_contract: JsonObject | null; +} + +interface RunDecision extends JsonObject { + normal_delivery_allowed: boolean; + recovery_delivery_allowed: boolean; + self_repair_allowed: boolean; + capability_repair_allowed: boolean; + workspace_repair_allowed: boolean; + should_run: boolean; + effective_action: string; + reason: string; + state: string; + quota_patch: JsonObject; + replan_decision_allowed: boolean; +} + +function text(value: unknown, label: string): string { + if (typeof value !== "string") throw new EffectRuntimeRequestError(`${label} must be a string`); + return value; +} + +function optionalRecord(value: unknown, label: string): JsonObject | null { + if (value === null || value === undefined) return null; + const record = requireJsonObject(value, label); + return Object.keys(record).length ? record : null; +} + +function decodeRunFacts(params: JsonObject): RunFacts { + return { + normal_delivery_allowed: requireBoolean(params.normal_delivery_allowed, "normal_delivery_allowed"), + recovery_delivery_allowed: requireBoolean(params.recovery_delivery_allowed, "recovery_delivery_allowed"), + self_repair_allowed: requireBoolean(params.self_repair_allowed, "self_repair_allowed"), + repair_effective_action: text(params.repair_effective_action, "repair_effective_action"), + state: text(params.state, "state"), reason: text(params.reason, "reason"), + quota_focus_wait: requireBoolean(params.quota_focus_wait, "quota_focus_wait"), + capability_gate: optionalRecord(params.capability_gate, "capability_gate"), + capability_monitor_fallback: requireBoolean(params.capability_monitor_fallback, "capability_monitor_fallback"), + workspace_guard: optionalRecord(params.workspace_guard, "workspace_guard"), + automation_prompt_upgrade: optionalRecord(params.automation_prompt_upgrade, "automation_prompt_upgrade"), + automation_prompt_upgrade_required: requireBoolean(params.automation_prompt_upgrade_required, "automation_prompt_upgrade_required"), + replan_required: requireBoolean(params.replan_required, "replan_required"), + replan_applies: requireBoolean(params.replan_applies, "replan_applies"), + goal_health_ok: requireBoolean(params.goal_health_ok, "goal_health_ok"), + inbox_reply_due: requireBoolean(params.inbox_reply_due, "inbox_reply_due"), + inbox_material_review_due: requireBoolean(params.inbox_material_review_due, "inbox_material_review_due"), + terminal_no_followup: requireBoolean(params.terminal_no_followup, "terminal_no_followup"), + task_orchestration_contract: optionalRecord(params.task_orchestration_contract, "task_orchestration_contract"), + }; +} + +function guardReason(guard: JsonObject | null, fallback: string): string { + return (guard ? text(guard.reason ?? "", "guard.reason") : "") || fallback; +} + +function clearExecutionLanes(decision: RunDecision): void { + decision.normal_delivery_allowed = false; + decision.recovery_delivery_allowed = false; + decision.self_repair_allowed = false; + decision.capability_repair_allowed = false; + decision.workspace_repair_allowed = false; +} + +function applyDeliveryGuards(facts: RunFacts, decision: RunDecision): void { + const gate = facts.capability_gate; + if (gate && gate.action !== "run" && !facts.capability_monitor_fallback) { + decision.normal_delivery_allowed = false; + decision.recovery_delivery_allowed = false; + decision.capability_repair_allowed = gate.action === "repair_bridge"; + decision.reason = guardReason(gate, decision.capability_repair_allowed + ? "capability bridge repair required" : "selected todo capability is unavailable"); + } + if (facts.workspace_guard) { + clearExecutionLanes(decision); + decision.workspace_repair_allowed = true; + decision.reason = guardReason(facts.workspace_guard, "agent workspace guard blocks delivery"); + } + if (facts.automation_prompt_upgrade_required) { + clearExecutionLanes(decision); + decision.reason = guardReason(facts.automation_prompt_upgrade, + "identity-aware automation prompt upgrade is required"); + } + decision.should_run = decision.normal_delivery_allowed || decision.recovery_delivery_allowed + || decision.self_repair_allowed || decision.capability_repair_allowed || decision.workspace_repair_allowed; +} + +function quotaEffectiveAction(facts: RunFacts, decision: RunDecision): string { + if (decision.normal_delivery_allowed) return EffectiveAction.NORMAL_RUN; + if (decision.recovery_delivery_allowed) return EffectiveAction.OUTCOME_FLOOR_RECOVERY; + if (decision.workspace_repair_allowed) return EffectiveAction.AGENT_WORKSPACE_REPAIR; + if (decision.self_repair_allowed) return facts.repair_effective_action || EffectiveAction.CONTROL_PLANE_REPAIR; + if (decision.capability_repair_allowed) return EffectiveAction.CAPABILITY_BRIDGE_REPAIR; + if (facts.state === "operator_gate") return EffectiveAction.OPERATOR_GATE_NOTIFY; + if (facts.state === "blocked_health") return EffectiveAction.BLOCKED_HEALTH; + if (facts.state === "throttled") return EffectiveAction.THROTTLED_SKIP; + if (facts.state === "focus_wait" || facts.state === "waiting" || facts.quota_focus_wait) + return EffectiveAction.BLOCKED_WAIT; + return EffectiveAction.QUOTA_SKIP; +} + +function applyGoalOverrides(facts: RunFacts, decision: RunDecision): void { + const inboxPriority = facts.inbox_reply_due || facts.inbox_material_review_due; + decision.replan_decision_allowed = !inboxPriority && facts.replan_required && facts.replan_applies + && facts.goal_health_ok && !facts.workspace_guard && !facts.automation_prompt_upgrade_required; + if (decision.replan_decision_allowed) { + decision.normal_delivery_allowed = false; + decision.recovery_delivery_allowed = false; + decision.should_run = true; + decision.effective_action = EffectiveAction.AUTONOMOUS_REPLAN_REQUIRED; + decision.reason = "autonomous replan obligation is selected before monitor quiet " + + "or agent-scope wait classification"; + } + if (facts.terminal_no_followup && !inboxPriority) { + clearExecutionLanes(decision); + decision.should_run = false; + decision.state = "terminal_no_followup"; + decision.quota_patch = {state: "terminal_no_followup", + reason: "derived terminal no-follow-up is confirmed by complete todo sources and an empty frontier"}; + decision.effective_action = EffectiveAction.TERMINAL_NO_FOLLOWUP; + decision.reason = "validated closure evidence derives terminal no-follow-up from " + + "complete todo sources and an empty frontier; stop recurring " + + "automation until an explicit resume"; + } +} + +function applyInboxOverrides(facts: RunFacts, decision: RunDecision): void { + // Preserve the terminal-source exception even when inbox work keeps the lane open. + if (facts.automation_prompt_upgrade_required && !facts.terminal_no_followup) { + decision.should_run = false; + decision.effective_action = EffectiveAction.AUTOMATION_PROMPT_UPGRADE_REQUIRED; + } else if (facts.inbox_reply_due || facts.inbox_material_review_due) { + clearExecutionLanes(decision); + decision.should_run = true; + decision.normal_delivery_allowed = true; + if (facts.inbox_reply_due) { + decision.effective_action = EffectiveAction.LARK_INBOX_REPLY_DUE; + decision.reason = "a direct Lark question, bot mention, or verified reply to the bot is pending reply"; + } else { + decision.effective_action = EffectiveAction.OPERATOR_INBOX_MATERIAL_REVIEW_DUE; + decision.reason = "captured unaddressed operator-inbox material is pending bounded review"; + } + } +} + +function taskOrchestrationEffectiveAction(facts: RunFacts, decision: RunDecision): [string, string] { + const contract = facts.task_orchestration_contract; + if (contract && (text(contract.execution_state ?? "", "execution_state") || "ready") === "ready" + && decision.should_run && decision.normal_delivery_allowed + && decision.effective_action === EffectiveAction.NORMAL_RUN) { + return [EffectiveAction.COORDINATE_TASK_BUNDLE, contract.mode === "adaptive" + ? "the task coordinator may use admitted child lanes before its own worker-lane delivery" + : "the explicitly selected task coordinator must activate or resume eligible peer lanes before doing its own worker-lane delivery"]; + } + return [decision.effective_action, decision.reason]; +} + +/** Compose base quota admission after source reads; receipt-bound overlays keep their owners. */ +export function resolveQuotaRunDecision(params: JsonObject): JsonObject { + const facts = decodeRunFacts(params); + const decision: RunDecision = { + normal_delivery_allowed: facts.normal_delivery_allowed, + recovery_delivery_allowed: facts.recovery_delivery_allowed, + self_repair_allowed: facts.self_repair_allowed, + capability_repair_allowed: false, workspace_repair_allowed: false, + should_run: false, effective_action: EffectiveAction.QUOTA_SKIP, + reason: facts.reason, state: facts.state, quota_patch: {}, replan_decision_allowed: false, + }; + applyDeliveryGuards(facts, decision); + decision.effective_action = quotaEffectiveAction(facts, decision); + applyGoalOverrides(facts, decision); + applyInboxOverrides(facts, decision); + [decision.effective_action, decision.reason] = taskOrchestrationEffectiveAction(facts, decision); + return decision; +} diff --git a/loopx/semantics/production.py b/loopx/semantics/production.py index 6f315541d0..77011e92b7 100644 --- a/loopx/semantics/production.py +++ b/loopx/semantics/production.py @@ -318,13 +318,45 @@ def _probe_controller_domain(vocabulary: dict[str, Any]) -> list[Production]: return probe_controller_production() + probe_projection_production() +def probe_quota_run_decision_production(vocabulary: dict[str, Any]) -> list[Production]: + """Adapt the fixed TypeScript classifier proof to the existing CI collector.""" + if vocabulary.get('input_producer') != QUOTA_RUN_DECISION_SITE: + raise ValueError('effective_action: quota witness must name its anchored classifier') + root = Path(__file__).resolve().parents[2] + try: + completed = subprocess.run( + ['node', '--no-warnings', '--experimental-strip-types', + str(root / 'scripts/quota_run_decision_witness.mts')], + input=json.dumps({'values': vocabulary['values']}), capture_output=True, + text=True, encoding='utf-8', check=False, timeout=30, + ) + except (OSError, subprocess.TimeoutExpired) as error: + raise ValueError('effective_action: quota native witness is unavailable') from error + if completed.returncode: + raise ValueError(f'effective_action: quota native witness failed: {completed.stderr[:300]}') + try: + observed = json.loads(completed.stdout) + except json.JSONDecodeError as error: + raise ValueError('effective_action: quota witness returned invalid JSON') from error + if (not isinstance(observed, dict) or observed.get('site') != QUOTA_RUN_DECISION_SITE + or type(observed.get('line')) is not int or observed['line'] < 1 + or not isinstance(observed.get('actions'), list) or not observed['actions'] + or any(not isinstance(action, str) or action not in vocabulary['values'] + for action in observed['actions'])): + raise ValueError('effective_action: quota witness returned invalid production evidence') + return [Production(QUOTA_RUN_DECISION_SITE, observed['line'], 'input_witness', + frozenset({action}), False) for action in observed['actions']] + + # Executable input witnesses are fixed in code and selected only by the # registered ``input_producer`` site; registry data cannot import a callable. # Line of ``settlementIdentity`` in the tracked builder, so the witness row # points at the function it executed. SETTLEMENT_IDENTITY_LINE = 408 +QUOTA_RUN_DECISION_SITE = 'loopx/control_plane/quota/run_decision.ts::resolveQuotaRunDecision' INPUT_WITNESSES: dict[str, Callable[[dict[str, Any]], list[Production]]] = { + QUOTA_RUN_DECISION_SITE: probe_quota_run_decision_production, 'loopx/control_plane/turn_driver/transaction.py::_result_kind': probe_turn_result_input_domain, 'loopx/control_plane/turn_driver/loop_controller.py::decide_loop_disposition': _probe_controller_domain, 'loopx/control_plane/effect_program.ts::settlementIdentity': probe_settlement_binding_production, diff --git a/loopx/semantics/vocabulary_v0.json b/loopx/semantics/vocabulary_v0.json index 443f29d648..0be44348dd 100644 --- a/loopx/semantics/vocabulary_v0.json +++ b/loopx/semantics/vocabulary_v0.json @@ -451,15 +451,15 @@ "value_notes": { "agent_monitor_only": "The lane is monitor-only for this agent: not actionable by the agent, advancement work is the blocked scope, and the scheduler holds it in an agent-monitor wait.", "agent_workspace_repair": "Workspace repair is allowed, and it outranks self-repair: the agent workspace is repaired before any further delivery. It is an explicit typed REPAIR_ACTIONS member, so the route is repair_required.", - "automation_prompt_upgrade_required": "The installed automation prompt identity must be repaired first. It is one of the conditions that withholds autonomous_replan_decision_allowed, and the automation action is repair_automation_prompt_identity.", + "automation_prompt_upgrade_required": "The installed automation prompt identity must be repaired first. It is one of the conditions that withholds autonomous replan admission, and the automation action is repair_automation_prompt_identity.", "autonomous_replan_required": "An autonomous replan is required and its scope applies to this agent. It is one of REPLAN_ACTIONS, so the route is replan_required.", - "blocked_health": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", - "blocked_wait": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "blocked_health": "Existing result of quotaEffectiveAction; previously missed because the literal scan did not inspect declared return functions.", + "blocked_wait": "Existing result of quotaEffectiveAction; previously missed because the literal scan did not inspect declared return functions.", "boundary_projection_repair": "A required write scope is missing from the projected goal boundary; trigger required_write_scope_missing_from_goal_boundary.", "capability_bridge_repair": "Capability repair is allowed once workspace repair and self-repair are not; the capability bridge is repaired before delivery.", "control_plane_health_repair": "Control-plane health repair for an unavailable closeout query or a stall health blocker; recommended_mode repair_control_plane_health. One of the five self-repair spend actions that stand in for the generic control_plane_repair.", "control_plane_projection_repair": "Stall repair for a lane left waiting with no owner projection; trigger waiting_without_owner_projection.", - "control_plane_repair": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "control_plane_repair": "Existing result of quotaEffectiveAction; previously missed because the literal scan did not inspect declared return functions.", "coordinate_task_bundle": "Replaces normal_run when a ready task-orchestration contract makes this lane the coordinator: admitted or explicitly selected peer lanes are activated or resumed before its own worker-lane delivery.", "external_evidence_observe": "An external-evidence monitor requires a read-only observation before any quiet no-op.", "governed_capability_intent": "A governed capability intent is pending. It is the only action that routes to capability_action_required, and only when the intent projection matches the envelope goal and agent and names a command; a mismatch is a contract_error instead.", @@ -468,25 +468,27 @@ "lark_inbox_reply_due": "A direct Lark question, bot mention, or verified reply to the bot is pending a reply, and normal delivery is allowed for that reply.", "monitor_due": "A monitor poll is due; should_run and normal delivery follow the same monitor_due flag. Its not-due counterpart in the same branch is monitor_quiet_skip.", "monitor_quiet_skip": "A monitor poll that is not due. The scheduler maps it to a monitor wait, and it spends no quota.", - "normal_run": "Normal delivery is allowed; the first branch of quota_effective_action. With delivery_allowed and must_attempt set it routes to ready_for_host.", - "operator_gate_notify": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "normal_run": "Normal delivery is allowed; the first branch of quotaEffectiveAction. With delivery_allowed and must_attempt set it routes to ready_for_host.", + "operator_gate_notify": "Existing result of quotaEffectiveAction; previously missed because the literal scan did not inspect declared return functions.", "operator_inbox_material_review_due": "Captured operator-inbox material is pending bounded review, and normal delivery is allowed for that review.", "outcome_floor_recovery": "Normal delivery is refused but recovery delivery is allowed, through the quota safe bypass of kind outcome_floor_recovery.", "peer_coordination_blocked": "Peer coordination blocks this lane. The scheduler keeps a no-spend observer alive with bounded stateful backoff because peer readiness, coordinator configuration, reassignment, or local work can change asynchronously.", - "quota_skip": "The final fallback of quota_effective_action: no delivery is allowed, no repair applies, and no more specific blocked state matched.", + "quota_skip": "The final fallback of quotaEffectiveAction: no delivery is allowed, no repair applies, and no more specific blocked state matched.", "runtime_user_gate_projection_repair": "A runtime capability user-gate projection needs repair; recommended_mode repair_user_gate_projection, blocked scope user_gate_projection.", "scoped_user_gate_fallback": "A scoped user-gate fallback applies while replan decisions are not allowed. It replaces quota_skip, monitor_quiet_skip or an absent action, and obliges one non-gated fallback segment after the user-gate notice.", "state_projection_gap_repair": "A state-projection gap on a candidate that should run or must attempt; trigger state_projection_gap.", "terminal_no_followup": "The Goal is terminal with no follow-up. The controller reads it as the terminal_action partition, so with no prior receipt the disposition is terminal, and the scheduler stops.", - "throttled_skip": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "throttled_skip": "Existing result of quotaEffectiveAction; previously missed because the literal scan did not inspect declared return functions.", "todo_decision_scope_projection_repair": "A Todo user-gate scope is missing from the decision-scope projection; trigger user_gate_scope_projection_drift.", "unsettled_host_turn_recovery": "A host Turn is unsettled and must be recovered before anything else: the selected Todo and action portfolio are dropped, should_run is set, and normal, recovery and self-repair delivery are all refused." }, "producers": [ "loopx/control_plane/work_items/action_selection_contract.py::build_action_selection_recovery_fields", - "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action", - "loopx/control_plane/quota/decision_summary.py::quota_effective_action", - "loopx/control_plane/quota/decision_summary.py::resolve_quota_run_decision", + "loopx/control_plane/quota/run_decision.ts::taskOrchestrationEffectiveAction", + "loopx/control_plane/quota/run_decision.ts::quotaEffectiveAction", + "loopx/control_plane/quota/run_decision.ts::resolveQuotaRunDecision", + "loopx/control_plane/quota/run_decision.ts::applyGoalOverrides", + "loopx/control_plane/quota/run_decision.ts::applyInboxOverrides", "loopx/cli_commands/quota_failure_report.py::quota_failure_payload", "loopx/control_plane/quota/heartbeat_receipt.py::fail_heartbeat_receipt", "loopx/control_plane/quota/live_decision.py::_apply_pending_capability_intent_precedence", @@ -506,14 +508,15 @@ ], "compatibility_only": {}, "return_producers": [ - "loopx/control_plane/quota/decision_summary.py::quota_effective_action", - "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action" + "loopx/control_plane/quota/run_decision.ts::quotaEffectiveAction", + "loopx/control_plane/quota/run_decision.ts::taskOrchestrationEffectiveAction" ], "return_paths": { - "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action": [ + "loopx/control_plane/quota/run_decision.ts::taskOrchestrationEffectiveAction": [ 0 ] - } + }, + "input_producer": "loopx/control_plane/quota/run_decision.ts::resolveQuotaRunDecision" }, "settlement_step_kind": { "meaning": "Effect-program settlement step executed for one Turn.", diff --git a/scripts/quota_run_decision_witness.mts b/scripts/quota_run_decision_witness.mts new file mode 100644 index 0000000000..2790dacffd --- /dev/null +++ b/scripts/quota_run_decision_witness.mts @@ -0,0 +1,28 @@ +// Registry data cannot select another classifier or replace the independent fixture. +import assert from "node:assert/strict"; +import {readFileSync} from "node:fs"; +import {resolveQuotaRunDecision} from "../loopx/control_plane/quota/run_decision.ts"; +import type {JsonObject} from "../loopx/control_plane/effect_program.ts"; + +let input = ""; +for await (const chunk of process.stdin) input += chunk; +const {values} = JSON.parse(input) as {values: string[]}; +const fixture = JSON.parse(readFileSync(new URL( + "../tests/control_plane_ts/fixtures/quota_run_decision.json", import.meta.url), "utf8")) as { + base_facts: JsonObject; base_result: JsonObject; + cases: {facts: JsonObject; expected: JsonObject}[]; + }; +assert(fixture.cases.length > 0, "quota witness needs ordered guard cases"); +const actions = fixture.cases.map(scenario => { + const decision = resolveQuotaRunDecision({...fixture.base_facts, ...scenario.facts}); + assert.deepEqual(decision, {...fixture.base_result, ...scenario.expected}, + "quota output differs from the ordered guard fixture"); + assert(values.includes(decision.effective_action as string), "quota produced an unregistered action"); + return decision.effective_action; +}); +const source = readFileSync(new URL("../loopx/control_plane/quota/run_decision.ts", import.meta.url), "utf8"); +const line = source.split("\n").findIndex(text => text.startsWith("export function resolveQuotaRunDecision(")) + 1; +assert(line > 0, "quota classifier must retain its source anchor"); +process.stdout.write(JSON.stringify({ + site: "loopx/control_plane/quota/run_decision.ts::resolveQuotaRunDecision", line, actions, +})); diff --git a/tests/architecture/test_semantic_production.py b/tests/architecture/test_semantic_production.py index 4a40a2d99c..d489d9e562 100644 --- a/tests/architecture/test_semantic_production.py +++ b/tests/architecture/test_semantic_production.py @@ -249,6 +249,7 @@ def test_real_effect_adapters_are_observed_as_unresolved_passthrough(): from loopx.semantics.inventory import load_sources v = json.loads((ROOT / 'loopx/semantics/vocabulary_v0.json').read_text(encoding='utf-8'))['vocabularies']['effective_action'] + v.pop('input_producer', None) # Isolate static adapter evidence from the executed classifier witness. paths = {'loopx/control_plane/effect_program.py', 'loopx/control_plane/effect_program.ts'} owner = v['owners']['python'].split('::')[0] sources = [source for source in load_sources(ROOT) if source.path in paths | {owner}] diff --git a/tests/architecture/test_semantic_quota_run_witness.py b/tests/architecture/test_semantic_quota_run_witness.py new file mode 100644 index 0000000000..edf0bd5e4f --- /dev/null +++ b/tests/architecture/test_semantic_quota_run_witness.py @@ -0,0 +1,63 @@ +"""Quota liveness must follow executed decisions, not imported enum definitions.""" +from __future__ import annotations + +import json +from pathlib import Path +import shutil + +import pytest + +from loopx.semantics import production as witness + +ROOT = Path(__file__).resolve().parents[2] + + +def vocabulary() -> dict: + return json.loads((ROOT / "loopx/semantics/vocabulary_v0.json").read_text())["vocabularies"]["effective_action"] + + +def test_actual_quota_outputs_establish_the_migrated_actions() -> None: + rows = witness.probe_quota_run_decision_production(vocabulary()) + observed = set().union(*(row.values for row in rows)) + assert {"normal_run", "control_plane_repair", "autonomous_replan_required", + "terminal_no_followup", "coordinate_task_bundle"} <= observed + assert observed <= set(vocabulary()["values"]) + + +@pytest.fixture +def isolated_source(tmp_path, monkeypatch): + for name in ["scripts/quota_run_decision_witness.mts", + "loopx/control_plane/quota/run_decision.ts", + "loopx/control_plane/quota/effective_action.generated.ts", + "loopx/control_plane/runtime_decode.ts", + "loopx/control_plane/effect_runtime_errors.ts", + "tests/control_plane_ts/fixtures/quota_run_decision.json"]: + target = tmp_path / name + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(ROOT / name, target) + monkeypatch.setattr(witness, "__file__", str(tmp_path / "loopx/semantics/production.py")) + return tmp_path + + +def test_a_changed_rule_cannot_keep_its_old_production_credit(isolated_source) -> None: + policy = isolated_source / "loopx/control_plane/quota/run_decision.ts" + original = "if (decision.normal_delivery_allowed) return EffectiveAction.NORMAL_RUN;" + assert original in policy.read_text() + policy.write_text(policy.read_text().replace(original, + "if (decision.normal_delivery_allowed) return EffectiveAction.QUOTA_SKIP;")) + with pytest.raises(ValueError, match="differs from the ordered guard fixture"): + witness.probe_quota_run_decision_production(vocabulary()) + + +def test_enumerating_the_owner_cannot_replace_execution_evidence(isolated_source) -> None: + policy = isolated_source / "loopx/control_plane/quota/run_decision.ts" + original = "export function resolveQuotaRunDecision(params: JsonObject): JsonObject {" + assert original in policy.read_text() + policy.write_text(policy.read_text().replace(original, original + "\n return EffectiveAction;")) + with pytest.raises(ValueError, match="differs from the ordered guard fixture"): + witness.probe_quota_run_decision_production(vocabulary()) + + +def test_registry_data_cannot_retarget_the_executed_classifier() -> None: + with pytest.raises(ValueError, match="anchored classifier"): + witness.probe_quota_run_decision_production({"input_producer": "elsewhere::run"}) diff --git a/tests/architecture/test_semantic_vocabulary_drift.py b/tests/architecture/test_semantic_vocabulary_drift.py index 8dd97081b9..dcfe4e3723 100644 --- a/tests/architecture/test_semantic_vocabulary_drift.py +++ b/tests/architecture/test_semantic_vocabulary_drift.py @@ -187,7 +187,7 @@ def test_registry_cannot_add_unanchored_output_selectors(name, metadata, selecti smoke = runpy.run_path(str(SMOKE)) registry = copy.deepcopy(smoke['load_registry']()) registry['vocabularies'][name].setdefault(metadata, {})[ - 'loopx/control_plane/quota/decision_summary.py::quota_effective_action' + 'loopx/control_plane/quota/run_decision.ts::quotaEffectiveAction' ] = selection with pytest.raises(smoke['Drift'], match='anchored output evidence exactly'): smoke['check_coverage_floor'](registry) diff --git a/tests/control_plane/test_quota_run_decision.py b/tests/control_plane/test_quota_run_decision.py index df6427868c..49e83f930b 100644 --- a/tests/control_plane/test_quota_run_decision.py +++ b/tests/control_plane/test_quota_run_decision.py @@ -1,6 +1,9 @@ from __future__ import annotations from typing import Any +from dataclasses import asdict +import json +from pathlib import Path import pytest @@ -239,3 +242,56 @@ def test_inbox_admission_preserves_guard_and_source_priority( assert decision.replan_decision_allowed is False assert decision.state == "eligible" assert decision.quota == {"state": "eligible"} + + +_RULE_CASES = json.loads((Path(__file__).parents[1] / "control_plane_ts" / + "fixtures/quota_run_decision.json").read_text()) + + +@pytest.mark.parametrize("case", _RULE_CASES["cases"], ids=lambda case: case["name"]) +def test_ordered_guard_contract(case: dict[str, Any]) -> None: + facts = {**_RULE_CASES["base_facts"], **case["facts"]} + quota = {"state": facts["state"], "focus_wait": facts["quota_focus_wait"]} + decision = _resolve( + **{key: facts[key] for key in ( + "normal_delivery_allowed", "recovery_delivery_allowed", "self_repair_allowed", + "state", "reason", "capability_gate", "workspace_guard", + "automation_prompt_upgrade", "automation_prompt_upgrade_required", + "goal_health_ok", "inbox_reply_due", "inbox_material_review_due", + "task_orchestration_contract", + )}, + quota=quota, + stall_self_repair={"effective_action": facts["repair_effective_action"]}, + capability_monitor_fallback={"selected": True} if facts["capability_monitor_fallback"] else None, + replan_obligation={"required": True, + "agent_id": "agent-a" if facts["replan_applies"] else "other-agent"} + if facts["replan_required"] else None, + goal_frontier_projection=_terminal_projection() if facts["terminal_no_followup"] else None, + ) + expected = {**_RULE_CASES["base_result"], **case["expected"]} + expected["quota"] = {**quota, **expected.pop("quota_patch")} + assert asdict(decision) == expected + + +@pytest.mark.parametrize("terminal", [False, True]) +def test_quota_source_numbers_survive_guard_projection(terminal: bool) -> None: + quota = {"compute": 1.0, "spent_slots": 9007199254740993, + "nested": {"amount": 2.0}, "state": "eligible"} + before = json.dumps(quota, sort_keys=True) + decision = _resolve(quota=quota, + goal_frontier_projection=_terminal_projection() if terminal else None) + assert json.dumps(quota, sort_keys=True) == before + assert type(decision.quota["compute"]) is float + assert decision.quota["spent_slots"] == 9007199254740993 + assert decision.quota["nested"] == {"amount": 2.0} + assert decision.quota["state"] == ("terminal_no_followup" if terminal else "eligible") + + +@pytest.mark.parametrize("value,expected", [ + (0, "agent workspace guard blocks delivery"), + ([1], "[1]"), ({"source": True}, "{'source': True}"), (" ", " "), +]) +def test_legacy_guard_reason_codec_preserves_caller_text(value: Any, expected: str) -> None: + decision = _resolve(workspace_guard={"reason": value}) + assert decision.reason == expected + assert decision.workspace_repair_allowed is True diff --git a/tests/control_plane_ts/fixtures/quota_run_decision.json b/tests/control_plane_ts/fixtures/quota_run_decision.json new file mode 100644 index 0000000000..aa240f9039 --- /dev/null +++ b/tests/control_plane_ts/fixtures/quota_run_decision.json @@ -0,0 +1,473 @@ +{ + "base_facts": { + "normal_delivery_allowed": true, + "recovery_delivery_allowed": false, + "self_repair_allowed": false, + "repair_effective_action": "", + "state": "eligible", + "reason": "eligible", + "quota_focus_wait": false, + "capability_gate": null, + "capability_monitor_fallback": false, + "workspace_guard": null, + "automation_prompt_upgrade": null, + "automation_prompt_upgrade_required": false, + "replan_required": false, + "replan_applies": false, + "goal_health_ok": true, + "inbox_reply_due": false, + "inbox_material_review_due": false, + "terminal_no_followup": false, + "task_orchestration_contract": null + }, + "base_result": { + "normal_delivery_allowed": true, + "recovery_delivery_allowed": false, + "self_repair_allowed": false, + "capability_repair_allowed": false, + "workspace_repair_allowed": false, + "should_run": true, + "effective_action": "normal_run", + "reason": "eligible", + "state": "eligible", + "quota_patch": {}, + "replan_decision_allowed": false + }, + "cases": [ + { + "name": "ordinary delivery", + "facts": {}, + "expected": {} + }, + { + "name": "recovery precedes self repair", + "facts": { + "normal_delivery_allowed": false, + "recovery_delivery_allowed": true, + "self_repair_allowed": true + }, + "expected": { + "normal_delivery_allowed": false, + "recovery_delivery_allowed": true, + "self_repair_allowed": true, + "effective_action": "outcome_floor_recovery" + } + }, + { + "name": "self repair keeps its declared action", + "facts": { + "normal_delivery_allowed": false, + "self_repair_allowed": true, + "repair_effective_action": "control_plane_projection_repair" + }, + "expected": { + "normal_delivery_allowed": false, + "self_repair_allowed": true, + "effective_action": "control_plane_projection_repair" + } + }, + { + "name": "self repair retains the generic fallback", + "facts": { + "normal_delivery_allowed": false, + "self_repair_allowed": true + }, + "expected": { + "normal_delivery_allowed": false, + "self_repair_allowed": true, + "effective_action": "control_plane_repair" + } + }, + { + "name": "capability repair", + "facts": { + "capability_gate": { + "action": "repair_bridge", + "reason": "repair capability" + } + }, + "expected": { + "normal_delivery_allowed": false, + "capability_repair_allowed": true, + "effective_action": "capability_bridge_repair", + "reason": "repair capability" + } + }, + { + "name": "owner capability is not agent repair", + "facts": { + "capability_gate": { + "action": "ask_owner", + "reason": "owner needed" + } + }, + "expected": { + "normal_delivery_allowed": false, + "should_run": false, + "effective_action": "quota_skip", + "reason": "owner needed" + } + }, + { + "name": "monitor fallback retains delivery", + "facts": { + "capability_gate": { + "action": "skip", + "reason": "missing capability" + }, + "capability_monitor_fallback": true + }, + "expected": {} + }, + { + "name": "workspace precedes capability repair", + "facts": { + "recovery_delivery_allowed": true, + "self_repair_allowed": true, + "capability_gate": { + "action": "repair_bridge", + "reason": "repair capability" + }, + "workspace_guard": { + "reason": "workspace repair" + } + }, + "expected": { + "normal_delivery_allowed": false, + "workspace_repair_allowed": true, + "effective_action": "agent_workspace_repair", + "reason": "workspace repair" + } + }, + { + "name": "automation closes all repair lanes", + "facts": { + "recovery_delivery_allowed": true, + "self_repair_allowed": true, + "capability_gate": { + "action": "repair_bridge", + "reason": "repair capability" + }, + "workspace_guard": { + "reason": "workspace repair" + }, + "automation_prompt_upgrade_required": true, + "automation_prompt_upgrade": { + "reason": "refresh prompt identity" + } + }, + "expected": { + "normal_delivery_allowed": false, + "should_run": false, + "effective_action": "automation_prompt_upgrade_required", + "reason": "refresh prompt identity" + } + }, + { + "name": "replan precedes delivery", + "facts": { + "replan_required": true, + "replan_applies": true + }, + "expected": { + "normal_delivery_allowed": false, + "replan_decision_allowed": true, + "effective_action": "autonomous_replan_required", + "reason": "autonomous replan obligation is selected before monitor quiet or agent-scope wait classification" + } + }, + { + "name": "another peer does not acquire the replan", + "facts": { + "replan_required": true, + "replan_applies": false + }, + "expected": {} + }, + { + "name": "health withholds replan", + "facts": { + "normal_delivery_allowed": false, + "state": "blocked_health", + "goal_health_ok": false, + "replan_required": true, + "replan_applies": true + }, + "expected": { + "normal_delivery_allowed": false, + "state": "blocked_health", + "should_run": false, + "effective_action": "blocked_health" + } + }, + { + "name": "workspace withholds replan", + "facts": { + "workspace_guard": { + "reason": "workspace repair" + }, + "replan_required": true, + "replan_applies": true + }, + "expected": { + "normal_delivery_allowed": false, + "workspace_repair_allowed": true, + "effective_action": "agent_workspace_repair", + "reason": "workspace repair" + } + }, + { + "name": "terminal closes delivery", + "facts": { + "terminal_no_followup": true + }, + "expected": { + "normal_delivery_allowed": false, + "should_run": false, + "state": "terminal_no_followup", + "effective_action": "terminal_no_followup", + "reason": "validated closure evidence derives terminal no-follow-up from complete todo sources and an empty frontier; stop recurring automation until an explicit resume", + "quota_patch": { + "state": "terminal_no_followup", + "reason": "derived terminal no-follow-up is confirmed by complete todo sources and an empty frontier" + } + } + }, + { + "name": "terminal precedes automation upgrade", + "facts": { + "terminal_no_followup": true, + "automation_prompt_upgrade_required": true, + "automation_prompt_upgrade": { + "reason": "refresh prompt identity" + } + }, + "expected": { + "normal_delivery_allowed": false, + "should_run": false, + "state": "terminal_no_followup", + "effective_action": "terminal_no_followup", + "reason": "validated closure evidence derives terminal no-follow-up from complete todo sources and an empty frontier; stop recurring automation until an explicit resume", + "quota_patch": { + "state": "terminal_no_followup", + "reason": "derived terminal no-follow-up is confirmed by complete todo sources and an empty frontier" + } + } + }, + { + "name": "reply suppresses replan", + "facts": { + "inbox_reply_due": true, + "replan_required": true, + "replan_applies": true + }, + "expected": { + "effective_action": "lark_inbox_reply_due", + "reason": "a direct Lark question, bot mention, or verified reply to the bot is pending reply" + } + }, + { + "name": "reply takes priority over material review", + "facts": { + "inbox_reply_due": true, + "inbox_material_review_due": true + }, + "expected": { + "effective_action": "lark_inbox_reply_due", + "reason": "a direct Lark question, bot mention, or verified reply to the bot is pending reply" + } + }, + { + "name": "nonterminal upgrade precedes reply", + "facts": { + "automation_prompt_upgrade_required": true, + "automation_prompt_upgrade": { + "reason": "refresh prompt identity" + }, + "inbox_reply_due": true + }, + "expected": { + "normal_delivery_allowed": false, + "should_run": false, + "effective_action": "automation_prompt_upgrade_required", + "reason": "refresh prompt identity" + } + }, + { + "name": "terminal inbox exception retains its original source", + "facts": { + "normal_delivery_allowed": false, + "recovery_delivery_allowed": true, + "self_repair_allowed": true, + "workspace_guard": { + "reason": "workspace repair" + }, + "terminal_no_followup": true, + "automation_prompt_upgrade_required": true, + "automation_prompt_upgrade": { + "reason": "refresh prompt identity" + }, + "inbox_material_review_due": true + }, + "expected": { + "effective_action": "operator_inbox_material_review_due", + "reason": "captured unaddressed operator-inbox material is pending bounded review" + } + }, + { + "name": "adaptive coordinator", + "facts": { + "task_orchestration_contract": { + "execution_state": "ready", + "mode": "adaptive" + } + }, + "expected": { + "effective_action": "coordinate_task_bundle", + "reason": "the task coordinator may use admitted child lanes before its own worker-lane delivery" + } + }, + { + "name": "explicit coordinator defaults to ready", + "facts": { + "task_orchestration_contract": { + "execution_state": "", + "mode": "" + } + }, + "expected": { + "effective_action": "coordinate_task_bundle", + "reason": "the explicitly selected task coordinator must activate or resume eligible peer lanes before doing its own worker-lane delivery" + } + }, + { + "name": "waiting coordinator does not intercept work", + "facts": { + "task_orchestration_contract": { + "execution_state": "waiting", + "mode": "adaptive" + } + }, + "expected": {} + }, + { + "name": "coordinator does not capture inbox work", + "facts": { + "task_orchestration_contract": { + "execution_state": "ready", + "mode": "adaptive" + }, + "inbox_material_review_due": true + }, + "expected": { + "effective_action": "operator_inbox_material_review_due", + "reason": "captured unaddressed operator-inbox material is pending bounded review" + } + }, + { + "name": "closed lane operator_gate", + "facts": { + "normal_delivery_allowed": false, + "state": "operator_gate" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "operator_gate", + "should_run": false, + "effective_action": "operator_gate_notify" + } + }, + { + "name": "closed lane blocked_health", + "facts": { + "normal_delivery_allowed": false, + "state": "blocked_health" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "blocked_health", + "should_run": false, + "effective_action": "blocked_health" + } + }, + { + "name": "closed lane throttled", + "facts": { + "normal_delivery_allowed": false, + "state": "throttled" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "throttled", + "should_run": false, + "effective_action": "throttled_skip" + } + }, + { + "name": "closed lane focus_wait", + "facts": { + "normal_delivery_allowed": false, + "state": "focus_wait" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "focus_wait", + "should_run": false, + "effective_action": "blocked_wait" + } + }, + { + "name": "closed lane waiting", + "facts": { + "normal_delivery_allowed": false, + "state": "waiting" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "waiting", + "should_run": false, + "effective_action": "blocked_wait" + } + }, + { + "name": "closed lane paused", + "facts": { + "normal_delivery_allowed": false, + "state": "paused" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "paused", + "should_run": false, + "effective_action": "quota_skip" + } + }, + { + "name": "closed lane unrecognized", + "facts": { + "normal_delivery_allowed": false, + "state": "unrecognized" + }, + "expected": { + "normal_delivery_allowed": false, + "state": "unrecognized", + "should_run": false, + "effective_action": "quota_skip" + } + }, + { + "name": "focus wait fact remains effective for unknown state", + "facts": { + "normal_delivery_allowed": false, + "state": "unrecognized", + "quota_focus_wait": true + }, + "expected": { + "normal_delivery_allowed": false, + "state": "unrecognized", + "should_run": false, + "effective_action": "blocked_wait" + } + } + ] +} diff --git a/tests/control_plane_ts/quota_run_decision.test.ts b/tests/control_plane_ts/quota_run_decision.test.ts new file mode 100644 index 0000000000..eea306ffec --- /dev/null +++ b/tests/control_plane_ts/quota_run_decision.test.ts @@ -0,0 +1,20 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import {readFileSync} from "node:fs"; +import {resolveQuotaRunDecision} from "../../loopx/control_plane/quota/run_decision.ts"; +import type {JsonObject} from "../../loopx/control_plane/effect_program.ts"; + +const fixture = JSON.parse(readFileSync(new URL("./fixtures/quota_run_decision.json", import.meta.url), "utf8")); +for (const scenario of fixture.cases) { + test(scenario.name, () => { + assert.deepEqual(resolveQuotaRunDecision({...fixture.base_facts, ...scenario.facts}), + {...fixture.base_result, ...scenario.expected}); + }); +} + +test("malformed admission facts cannot manufacture a runnable decision", () => { + for (const patch of [{normal_delivery_allowed: "true"}, {replan_applies: null}, + {workspace_guard: "not a guard"}, {quota_focus_wait: 1}, {reason: null}]) { + assert.throws(() => resolveQuotaRunDecision({...fixture.base_facts, ...patch} as JsonObject)); + } +}); diff --git a/tsconfig.control-plane.json b/tsconfig.control-plane.json index a009df79e7..254fba421e 100644 --- a/tsconfig.control-plane.json +++ b/tsconfig.control-plane.json @@ -39,6 +39,7 @@ "tests/control_plane_ts/goal_workspace_digest.test.ts", "tests/control_plane_ts/interaction_contract.test.ts", "tests/control_plane_ts/runtime_decode.test.ts", + "tests/control_plane_ts/quota_run_decision.test.ts", "tests/control_plane_ts/authority_store.test.ts", "tests/control_plane_ts/authority_state_log.test.ts", "tests/control_plane_ts/sqlite_authority_store.test.ts", From d5ae830616cc3b6a1a7f810df88d24193fa77c3e Mon Sep 17 00:00:00 2001 From: aihop <4408344+hhyykk@users.noreply.github.com> Date: Fri, 16 Oct 2026 10:34:29 +0800 Subject: [PATCH 2/2] docs(architecture): record native quota admission cutover and exits Signed-off-by: aihop <4408344+hhyykk@users.noreply.github.com> --- .../typescript-control-plane-migration-v0.md | 20 +++++++++++++++++++ ...script-control-plane-migration-v0.zh-CN.md | 15 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md index c056cf2fc6..a54d8085f8 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md @@ -1536,6 +1536,26 @@ and receipt identity remain intact. This corrects misleading CLI JSON readback for peer-scoped gates; it does not migrate the remaining route or primary-action builders, reserve recommendations, or complete T3/D1–D3. +**Base quota admission composition.** `quota/run_decision.ts` now owns the +ordered capability, workspace, automation, replan, terminal, inbox and coordinator +overlays that produce the base run decision. The Python classifier and its two +action helpers are retired; the existing public adapter makes one coarse native +request. Goal terminal evidence and peer-scope facts retain their existing owners +as a transition. Their exit requires migrating the shared Goal source projection +and all its callers together. The Python public function, dataclass and diagnostic +codec retire when CLI/Host callers use the typed kernel directly. The target is +TypeScript ownership throughout, with no Python rule fallback. +The adapter transports bounded facts and diagnostic strings, then applies the +typed quota patch without round-tripping source numbers or metadata. Receipt-bound +overlays, later route/action builders and effect authority retain their owners. +The existing effective-action vocabulary and generated TS binding are reused; +its canonical Python enum remains until a separate contract-owner cutover. +The TypeScript producer witness executes the real typed classifier against shared guard fixtures and rejects a +changed rule or an enum-only substitute. This delivers the implementation and +semantic parity for one base-admission consumer. Full-CLI performance acceptance +is held for owner review after a p95 regression; whole-Goal migration and +provider/default qualification remain open. + **Long-history transport boundary.** Replan history still has one TS decision owner. Small requests retain the inline codec; larger complete fact snapshots travel through a private, digest-bound local file reference. The same reducer diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md index 9b76cb9208..fadcb64882 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.zh-CN.md @@ -1027,6 +1027,21 @@ T3/D1 reader,未完成全部 Todo writer、retention/compaction 或 promotion **Scoped gate 动作回读。** 最终 quota 包在选择、能力、workspace、回执及通知决策之后,通过 TS quota 规则投影 scoped User gate/action override。可选 `selected_action` 只在 interaction 允许交付时取最终选中 Todo 的文字;待选择、修复和已结算包省略该字段。准入诊断与回执身份保留。这修正 peer gate 场景的 CLI JSON 误导,不代表剩余 route/primary-action builder 已迁移、推荐已成为预留,或 T3/D1–D3 已完成。 +**基础配额准入组合。** `quota/run_decision.ts` 现在统一按顺序组合能力、workspace、 +automation、replan、终态、inbox 和协调者覆盖规则,生成基础运行决定。Python 分类器 +及其两个动作 helper 已退役;原有公共适配器只发一次粗粒度 native 请求。Goal 终态 +证据和 peer 作用域事实暂由既有 owner 提供;共享 Goal 来源投影与所有调用方一起 +迁移后退役。CLI/Host 调用方直接使用类型化内核后,Python 公共函数、dataclass +及诊断 codec 退役。最终实现全部由 TypeScript 持有,不设 Python 规则回退。 +适配器传递有界事实及诊断字符串,再应用 +类型化配额补丁,不让来源数值或元数据经历 JSON 往返转换。回执绑定覆盖、后续 +route/action builder 和 effect 权限保留各自 owner。沿用既有 effective-action +词表及生成的 TS binding;canonical Python enum 等独立契约 owner 切换后退役。 +TS 生产者验证器通过共享 guard fixture 执行真实 TS 分类器,并拒绝规则变更或 +仅枚举 owner 的替代结果。这交付一个基础准入消费者的实现及语义一致性;完整 CLI +出现 p95 回退,性能验收保留 owner 复核门禁。整 Goal 迁移及 provider/默认配置 +资格仍待完成。 + **长历史传输边界。** Replan 历史仍由一个 TS owner 决策。小请求保留 inline codec;较大的完整事实快照通过私有临时文件和摘要绑定的引用传递。同一 reducer 校验全部记录、agent 作用域内的 ACK 及重试身份;RPC 预算和展示窗口都不允许截断