@@ -8,6 +8,7 @@ import { basename, dirname, join, resolve } from "node:path";
88import { monitorSuccessorIntent , monitorSuccessorRoute } from "../scheduler/monitor_successor.ts" ;
99import { normalizeTodoCapabilities } from "../todos/work_requirements.ts" ;
1010import { parseProjectionDelivery } from "../todos/projection_delivery.ts" ;
11+ import { isMonitorUnavailableWait } from "./blocked_wait.ts" ;
1112import {
1213 QUOTA_SETTLEMENT_READBACK_REQUEST_SCHEMA ,
1314 readQuotaSettlementForAdmittedOwnerFromSnapshot ,
@@ -16,11 +17,12 @@ import {
1617import {
1718 parseQuotaAccountingOwner ,
1819 quotaGoalRef ,
20+ quotaOwnerOwnsProjection ,
1921 withBorrowedQuotaAccountingOwner ,
2022 type QuotaAccountingOwner ,
2123} from "./source_admission.ts" ;
2224
23- import type { JsonObject } from "../effect_program.ts" ;
25+ import type { JsonObject , SettlementIdentity } from "../effect_program.ts" ;
2426import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts" ;
2527import {
2628 appendJsonLine ,
@@ -563,17 +565,21 @@ interface Admission {
563565 external : boolean ;
564566}
565567
566- async function readAuxiliarySettlement ( request : MonitorRequest ) : Promise < JsonObject | null > {
568+ async function readMonitorSettlement ( request : MonitorRequest ) : Promise < JsonObject | null > {
569+ const todoId = request . observation . settlement_todo_id ?? request . observation . todo_id ;
570+ // The Turn/effect and run record belong to the decision Agent; provider actor
571+ // metadata cannot select a different historical settlement to evade its fence.
572+ const agentId = request . decision . agent_id ;
567573 if ( ! request . runtime_root || ! request . turn_instance_id ||
568- ! request . observation . actor_agent_id || ! request . observation . settlement_todo_id ) return null ;
574+ ! agentId || ! todoId ) return null ;
569575 const snapshot = await readQuotaSettlementSnapshot (
570576 request . runtime_root ,
571577 request . goal_id ,
572578 ) ;
573579 return readQuotaSettlementForAdmittedOwnerFromSnapshot ( {
574580 schema_version : QUOTA_SETTLEMENT_READBACK_REQUEST_SCHEMA ,
575581 runtime_root : request . runtime_root , goal_id : request . goal_id ,
576- agent_id : request . observation . actor_agent_id , todo_id : request . observation . settlement_todo_id ,
582+ agent_id : agentId , todo_id : todoId ,
577583 turn_instance_id : request . turn_instance_id , replan_obligation_id : null ,
578584 infer_turn_instance_id : false , allow_unbound_binding : false ,
579585 } , request . owner , snapshot ) ;
@@ -604,7 +610,7 @@ async function auxiliaryMonitorAllowed(
604610 todo . excluded_agents . some ( value => typeof value !== "string" ) ||
605611 todo . excluded_agents . includes ( decision . agent_id ) ) ) ) throw conflict ( ) ;
606612 if ( ! historicalAdmission ) {
607- const settlement = await readAuxiliarySettlement ( request ) ;
613+ const settlement = await readMonitorSettlement ( request ) ;
608614 const progress = jsonObject ( settlement ?. progress ) ;
609615 // The advancement's single debit is not an observation-admission fence.
610616 // A settled exact binding still permits independently admitted due Monitors.
@@ -632,6 +638,22 @@ async function auxiliaryMonitorAllowed(
632638}
633639
634640async function admission ( request : MonitorRequest , historicalAdmission = false ) : Promise < Admission > {
641+ const observation = request . observation ;
642+ const settlementTodo = observation . settlement_todo_id ?? observation . todo_id ;
643+ if ( settlementTodo && ( ! observation . todo_id || settlementTodo === observation . todo_id ) ) {
644+ const settlement = await readMonitorSettlement ( request ) ;
645+ // The readback owner verifies admission, exact identity and durable blocked
646+ // evidence. A late observation cannot replace that frozen no-spend outcome.
647+ // Durable poll replay returns before admission; independent auxiliary
648+ // Monitors retain their advancement-bound admission below.
649+ if ( jsonObject ( settlement ?. progress ) ?. closeout_kind === "typed_blocked_writeback_no_spend" &&
650+ isMonitorUnavailableWait ( jsonObject ( settlement ?. writeback_run ) ?. blocked_retry , settlementTodo ) ) {
651+ throw new EffectRuntimeRequestError (
652+ "monitor-poll conflicts with the committed unavailable attempt; use a fresh Turn for a new observation" ,
653+ "monitor_poll_admission_rejected" ,
654+ ) ;
655+ }
656+ }
635657 const blocked = blockedSuccessorAllowed ( request . decision ) ;
636658 const external = externalMonitorAllowed ( request . decision ) ;
637659 const auxiliary = await auxiliaryMonitorAllowed ( request , historicalAdmission ) ;
@@ -1562,7 +1584,7 @@ function successorReceipts(providerReceipt: JsonObject | null): JsonObject[] {
15621584async function projectAuxiliaryContinuation ( request : MonitorRequest , payload : JsonObject ) : Promise < void > {
15631585 const continuation = jsonObject ( payload . turn_continuation ) ;
15641586 if ( ! request . execute || continuation ?. settlement_binding_matches_observation !== false ) return ;
1565- const settlement = await readAuxiliarySettlement ( request ) ;
1587+ const settlement = await readMonitorSettlement ( request ) ;
15661588 if ( jsonObject ( settlement ?. progress ) ?. state !== "settled" ) return ;
15671589 // This is current readback, not a change to the historical Monitor receipt
15681590 // or permission for another advancement. Replays must not reopen a paid Turn.
@@ -1695,6 +1717,7 @@ function receiptObject(value: unknown): MonitorReceipt {
16951717 "receipt.expected_index_digest" ,
16961718 ) ,
16971719 expected_index_bytes : expectedBytes ,
1720+ ...( receipt . goal_ref === undefined ? { } : { goal_ref : requiredObject ( receipt . goal_ref , "receipt.goal_ref" ) } ) ,
16981721 } as const ;
16991722 const status = requireStringLiteral (
17001723 receipt . status ,
@@ -1745,6 +1768,45 @@ async function readReceipt(path: string): Promise<MonitorReceipt | null> {
17451768 }
17461769}
17471770
1771+ /** A stored observation may already have reached its provider even before the
1772+ * run index is settled. Its existing receipt owner must recover or prove no
1773+ * effect before a competing unavailable closeout can be admitted. */
1774+ export async function hasMonitorPollTransaction (
1775+ runtimeRoot : string , identity : SettlementIdentity , owner : QuotaAccountingOwner ,
1776+ ) : Promise < boolean > {
1777+ if ( ! identity . todo_id ) return false ;
1778+ const runsDir = join ( runtimeRoot , "goals" , identity . goal_id , "runs" ) ;
1779+ const base = `quota-monitor-poll:${ identity . goal_id } :${ identity . agent_id } :${ identity . turn_instance_id } ` ;
1780+ for ( const effectId of [ base , `${ base } :todo:${ identity . todo_id } ` ] ) {
1781+ const receipt = await readReceipt ( transactionPath ( runsDir , effectId ) ) ;
1782+ if ( ! receipt ) continue ;
1783+ validateReceiptScope ( receipt , runsDir , join ( runsDir , "index.jsonl" ) ) ;
1784+ const facts = receipt . status === "provider_pending" ? receipt . provider_plan : receipt . index_record ;
1785+ if ( receipt . effect_id !== effectId || facts . goal_id !== identity . goal_id ||
1786+ facts . todo_id !== identity . todo_id ) continue ;
1787+ if ( receipt . status === "provider_pending" ) {
1788+ // The provider plan names its actor, not the Turn's accounting Agent.
1789+ // Legacy pending receipts retain this exact accounting effect identity.
1790+ if ( receipt . schema_version === MONITOR_PENDING_ADMISSION_SCHEMA &&
1791+ ( receipt . admitted_decision . goal_id !== identity . goal_id ||
1792+ receipt . admitted_decision . agent_id !== identity . agent_id ) ) {
1793+ throw new EffectRuntimeRequestError (
1794+ "stored Monitor admission conflicts with its accounting identity" , "malformed_transaction_receipt" ,
1795+ ) ;
1796+ }
1797+ } else if ( facts . agent_id !== identity . agent_id ) continue ;
1798+ const goalRef = receipt . status === "provider_pending" ? receipt . goal_ref : facts . goal_ref ;
1799+ if ( receipt . status === "provider_pending" && goalRef === undefined && owner . kind !== "alias" ) {
1800+ throw new EffectRuntimeRequestError (
1801+ "historical pending Monitor transaction lacks GoalRef scope; reconcile its original owner before unavailable closeout" ,
1802+ ) ;
1803+ }
1804+ if ( ! quotaOwnerOwnsProjection ( owner , goalRef ) ) continue ;
1805+ return true ;
1806+ }
1807+ return false ;
1808+ }
1809+
17481810function validateReceiptScope (
17491811 receipt : MonitorReceipt ,
17501812 runsDir : string ,
@@ -2311,6 +2373,7 @@ async function evaluateQuotaMonitorPollRequest(
23112373 expected_index_bytes : indexBytes ?. length ?? 0 ,
23122374 provider_plan : plan ,
23132375 admitted_decision : request . decision ,
2376+ ...( quotaGoalRef ( request . owner ) === null ? { } : { goal_ref : quotaGoalRef ( request . owner ) } ) ,
23142377 } satisfies PendingMonitorReceipt ;
23152378 await atomicWriteJson ( receiptPath , pending ) ;
23162379 }
0 commit comments