Skip to content

Commit 7cb5158

Browse files
nficanocursoragent
andcommitted
fix: enforce expires_at containment in lease subsetting (§9.4) (#155)
LeaseManager::ensureSubset() now rejects a child lease whose expires_at exceeds the parent's with LEASE_SUBSET_VIOLATION, matching §9.4. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 62ca364 commit 7cb5158

2 files changed

Lines changed: 19 additions & 0 deletions

File tree

src/Runtime/LeaseManager.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ public function ensureSubset(LeaseGranted $parent, LeaseGranted $child): void
167167
'cost.budget',
168168
);
169169
}
170+
// §9.4: a delegated lease's expires_at MUST NOT exceed the parent's.
171+
if ($child->expiresAt > $parent->expiresAt) {
172+
throw new LeaseSubsetViolationException(
173+
(string) $parent->leaseId,
174+
(string) $child->leaseId,
175+
'lease_constraints.expires_at',
176+
);
177+
}
170178
}
171179

172180
public function revoke(LeaseId $id, string $reason = ''): LeaseRevoked

tests/Unit/Runtime/LeaseSubsetTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ public function testCostBudgetSubsetEnforced(): void
3434
$manager->ensureSubset($parent, $child);
3535
}
3636

37+
public function testExpiresAtSubsetEnforced(): void
38+
{
39+
$manager = new LeaseManager();
40+
$now = new \DateTimeImmutable('2026-01-01T00:00:00Z');
41+
$parent = new LeaseGranted(new LeaseId('lease_parent'), 'tool.invoke', 'planner', 'run', $now->modify('+1 hour'));
42+
$child = new LeaseGranted(new LeaseId('lease_child'), 'tool.invoke', 'planner', 'run', $now->modify('+2 hours'));
43+
44+
$this->expectException(LeaseSubsetViolationException::class);
45+
$manager->ensureSubset($parent, $child);
46+
}
47+
3748
private function lease(
3849
string $id,
3950
?ModelUse $modelUse = null,

0 commit comments

Comments
 (0)