From 34ef709504e2d450dd4e57272ab20c73caebd301 Mon Sep 17 00:00:00 2001 From: xarmian Date: Wed, 28 Oct 2026 13:13:32 +0000 Subject: [PATCH] docs(schedule): state the UNTIL type exception, the density refusal and MaxDuration's real overrun (TASK-4100 3a) Comment-only, per the lead's 3a final-round ruling: - doc.go: ParseRule refuses what RFC 5545 forbids EXCEPT an UNTIL whose value type differs from DTSTART's, which is accepted as Until defines it (C-M1, option B), and the refusal list names the 10,000-candidate density cap (H-L3). - caps.go: MaxDuration is checked between yields, so a call can overrun it by one period expansion or one MaxSteps no-match stretch, and MaxSteps has no ceiling (H-L1). Claude-Session: https://claude.ai/code/session_01Pa22hHpfXoZbQkEFvPtNeS --- internal/schedule/caps.go | 10 +++++----- internal/schedule/doc.go | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/internal/schedule/caps.go b/internal/schedule/caps.go index a4782043e..6c4da365b 100644 --- a/internal/schedule/caps.go +++ b/internal/schedule/caps.go @@ -18,8 +18,9 @@ type Limits struct { // candidates it produces (TASK-4100 H1, r2): the count restarts at every // candidate, so it ends a no-match stretch and nothing else. A stall is a // CapStall only when an answer could lie past the examined part (r3). - // MaxDuration is checked between yields, bounding a call to within one - // period expansion, limited by maxPeriodCandidates (TASK-4100 final round H1). + // MaxDuration is checked between yields, so a call can overrun it by one + // period expansion (limited by maxPeriodCandidates) or one no-match stretch of + // MaxSteps steps; MaxSteps has no ceiling (TASK-4100 final round H1, 3a H-L1). // MaxScan additionally refuses a wall-clock query far from Start, except // for COUNT rules and rules whose UNTIL is already spent (TASK-4100 r4). MaxSteps int64 @@ -106,9 +107,8 @@ func (c *Compiled) checkScan(loc *time.Location, base, q time.Time, lim Limits) // guard runs fn in the calling goroutine, so nothing can be abandoned or leak. // A panic in the library becomes a MalformedError instead of crashing the caller. -// MaxDuration is checked between yields, bounding a call to within one period -// expansion, limited by maxPeriodCandidates (TASK-4100 final round H1). -// MaxSteps ends a no-match stretch inside the vendored generator (r2, r4). +// MaxDuration is checked between yields and MaxSteps ends a no-match stretch inside the +// vendored generator (r2, r4); Limits.MaxSteps states what the two bound together. func guard(fn func() error) (err error) { defer func() { if recover() != nil { diff --git a/internal/schedule/doc.go b/internal/schedule/doc.go index 64e7b5dda..427bd6dfa 100644 --- a/internal/schedule/doc.go +++ b/internal/schedule/doc.go @@ -35,11 +35,12 @@ // EXDATE matched on the wall reading // UNTIL compared on the wall; a Z UNTIL and elapsed rules: on the instant // -// RULES. ParseRule is strict. It refuses what RFC 5545 forbids (COUNT with -// UNTIL, BYWEEKNO off YEARLY, an ordinal BYDAY off MONTHLY and YEARLY, -// BYSETPOS alone, BYMONTHDAY on WEEKLY, BYYEARDAY on DAILY, WEEKLY or MONTHLY), -// rules that can never fire (Feb 30), FREQ=SECONDLY (the tick is 30 s), and -// rules over 512 bytes, INTERVAL over 1,000,000 or years outside 1970..2200. +// RULES. ParseRule refuses what RFC 5545 forbids (COUNT with UNTIL, BYWEEKNO off YEARLY, +// an ordinal BYDAY off MONTHLY and YEARLY, BYSETPOS alone, BYMONTHDAY on WEEKLY, BYYEARDAY +// on DAILY, WEEKLY or MONTHLY), except that an UNTIL whose type differs from DTSTART's is +// accepted as Until defines it (untilbounds_test.go). It also refuses rules that never fire +// (Feb 30), FREQ=SECONDLY (the tick is 30 s), over 10,000 candidates per FREQ period +// (density.go), over 512 bytes, INTERVAL over 1,000,000 or years outside 1970..2200. // A DTSTART the rule does not produce is not emitted (dateutil semantics, ruling // Q1). Every refusal is a *MalformedError carrying a Code and the offending Part; // no third-party error text ever reaches a caller.