@@ -50,15 +50,13 @@ import {
5050 taskLeaseDirectory ,
5151 taskLeaseLockPath ,
5252 taskLeasePath ,
53- utcIsoformat ,
5453 type AuthorityFacts ,
5554 type LeaseRecord ,
5655 type TodoFact ,
5756 type TodoFactField ,
5857} from "./task_lease_acquire.ts" ;
5958import {
6059 decideTaskLeaseLifecycle ,
61- materializeTaskLeaseLifecycle ,
6260 releasedTaskLeaseRecord as releasedLease ,
6361 type TaskLeaseLifecycleDecision ,
6462 type TaskLeaseLifecycleDecisionInput ,
@@ -411,10 +409,10 @@ async function persistFenceReceipt(
411409 ) : T | null => value !== undefined ? value : fallback ?? null ;
412410 const verifyResponse = options . verifyResponse !== undefined
413411 ? options . verifyResponse
414- : base ?. verify_response ?? (
415- state === "held"
416- ? options . response
417- : state === "closed" &&
412+ : state === "held"
413+ ? options . response
414+ : base ?. verify_response ?? (
415+ state === "closed" &&
418416 base ?. response !== null &&
419417 base ?. response !== undefined &&
420418 base . response . action !== "fence_close"
@@ -1469,11 +1467,11 @@ async function fenceVerify(
14691467 // safe to retry the verification under a newly acquired lock only
14701468 // when the same verified generation is still present. A different
14711469 // generation must fail closed and force a fresh fence identity.
1472- if ( currentLease !== null &&
1473- currentLease . status === " active" &&
1474- tupleMatches ) {
1475- receipt = null ;
1476- } else {
1470+ // Retain the original receipt as recovery authority; the locked
1471+ // re-read below revalidates this exact active generation.
1472+ if ( currentLease === null ||
1473+ currentLease . status !== "active" ||
1474+ ! tupleMatches ) {
14771475 throw new TaskLeaseLifecycleError (
14781476 "closed task lease fence receipt is stale or has no retryable lease generation" ,
14791477 "fence_state_invalid" ,
@@ -1499,7 +1497,10 @@ async function fenceVerify(
14991497 ) ;
15001498 }
15011499 }
1502- if ( receipt === null ) rejectLegacyCoordinationExecution ( request . goal_id ) ;
1500+ if ( receipt === null ) {
1501+ await requireLegacyCoordinationPrimaryWriteAllowed ( request . runtime_root , request . goal_id ) ;
1502+ rejectLegacyCoordinationExecution ( request . goal_id ) ;
1503+ }
15031504 const adoptedLock = await adoptFenceReceiptLock ( request , receipt ) ;
15041505 const acquiredFreshLock = adoptedLock === null ;
15051506 lock = adoptedLock ?? await acquireFileMutationLock (
@@ -1707,6 +1708,11 @@ async function fenceVerify(
17071708 }
17081709 return replayResponse ;
17091710 }
1711+ throw new TaskLeaseLifecycleError (
1712+ "held task lease fence receipt does not match the persisted lease" ,
1713+ "fence_state_invalid" ,
1714+ { goal_id : request . goal_id , todo_id : request . todo_id , lease_path : leasePath } ,
1715+ ) ;
17101716 }
17111717 if ( request . operation === "holder_verify" ) {
17121718 if ( request . authority . handoff_mode !== "hard_lease" ) {
@@ -1757,60 +1763,14 @@ async function fenceVerify(
17571763
17581764 const explicitFence = request . idempotency_key !== null || request . expected_version !== null ;
17591765 const autoAcquire = allowsUserGateAutoAcquire ( request , todo ) ;
1760- if ( autoAcquire && ! effective && ! active ) {
1761- if ( ! request . owner ) {
1762- throw new TaskLeaseLifecycleError ( "hard_lease handoff mode auto-acquire requires an attributed actor; provide --agent-id" , "handoff_mode_requires_lease" , { goal_id : request . goal_id , todo_id : request . todo_id , reason : "missing_actor" , lease_path : leasePath } ) ;
1763- }
1764- const rejection = ownerRejection ( todo , request . owner , request . authority . registered_agents ) ;
1765- if ( rejection ) throw ownerError ( request , rejection , todo ) ;
1766- const now = utcIsoformat ( at ) ;
1767- lease = {
1768- schema_version : "task_lease_v0" ,
1769- goal_id : request . goal_id ,
1770- todo_id : request . todo_id ,
1771- owner : request . owner ,
1772- idempotency_key : request . idempotency_key ?? `auto-${ request . todo_id } ` ,
1773- write_scopes : [ ] ,
1774- acquire_ttl_seconds : 2700 ,
1775- version : leaseVersion ( lease ) + 1 ,
1776- lease_epoch : leaseEpoch ( lease ) + 1 ,
1777- acquired_at : now ,
1778- updated_at : now ,
1779- expires_at : utcIsoformat ( new Date ( at . valueOf ( ) + 2700 * 1_000 ) ) ,
1780- status : "active" ,
1781- } ;
1782- await dependencies . beforeWrite ?.( lease ) ;
1783- await revalidateAuthoritySources ( request . authority . source_receipts ) ;
1784- const shadowCapture = await captureLeaseWrite (
1785- request , rawLease , lease , "task_lease_auto_acquire" ,
1786- ) ;
1787- await atomicWriteJson ( leasePath , lease ) ;
1788- await shadowCapture ?. commit ( ) ;
1789- const response = fencePayload (
1790- { ...request , owner : request . owner } ,
1791- lease ,
1792- lock . token ,
1793- true ,
1794- ) ;
1795- await persistFenceReceipt ( request , "held" , lock . token , {
1796- operationId,
1797- lease,
1798- response,
1799- ownerPid : request . owner_pid ?? process . pid ,
1800- fenceOwner : request . owner ,
1801- fenceIdempotencyKey : lease . idempotency_key as string ,
1802- fenceExpectedVersion : leaseVersion ( lease ) ,
1803- fenceExpectedLeaseEpoch : leaseEpoch ( lease ) ,
1804- base : receipt ,
1805- } ) ;
1806- keep = true ;
1807- return attachRuntimeShadowCapture ( response , shadowCapture ) ;
1808- }
1766+ // An acquired receipt records intent, not a committed grant. After the
1767+ // cutoff it can adopt an existing effect, but cannot create a missing one.
1768+ if ( autoAcquire && ! effective && ! active ) rejectLegacyCoordinationExecution ( request . goal_id ) ;
18091769 // A response can be lost after an auto-acquire write but before the
18101770 // receipt reaches the caller. The durable fence receipt proves that the
18111771 // same execution instance already minted this lease, so recover it under
18121772 // the adopted lock instead of treating the active lease as a divergence.
1813- const recoveredAutoAcquire = request . operation === "terminal_verify" &&
1773+ const recoveredAutoAcquire = request . operation === "terminal_verify" && effective &&
18141774 receipt ?. state !== "closed" &&
18151775 receipt ?. fence_idempotency_key === `auto-${ request . todo_id } ` &&
18161776 receipt ?. fence_owner !== null &&
0 commit comments