TASK-LOOP-RRULE-RECURRENCE-CONTRACT-2G-f.md markdown
136 lines 5.1 KB
Raw
sha256:c2dbf04d56308f3bbf2d06e6d2eb022b8948b1e827195fe525a44e5e18d5f9c0 feat(auth): Phase B Connect cloud agent (RFC 8628) + Hermes… Human minor ⚠ breaking 10 days ago

Task Loop RRULE Recurrence — Contract (Phase 2G-f, Step 2G-f-a)

Status: Contract frozen (2G-f-a Thinking, 2026-06-24). Knowtation-canonical extension to knowtation.task_loop/v0.recurrence resolving OD-2. Validator + store read paths in 2G-f-b Auto — inert until Tier 3 bundled 2G live session.

Related:

  • scooling/docs/LOOP-TRIGGER-AND-RECURRENCE-CONTRACT-2G-f.md — Scooling trigger eval + posture.
  • docs/TASK-LOOP-STORE-CONTRACT-2G-c.md — v0 pinned subset (cron / interval / manual / on_wake).
  • docs/TASK-LOOPS-AND-ORCHESTRATOR-PLAN.md — §3.2 recurrence, OD-2 deferral note.

Scope fence (2G-f-a): Schema + validator rules + test matrix only. No RRULE parser impl, no Hub route changes, no live gate flips.


Simple summary

Task loop series can store a standard RRULE recurrence (like calendar apps use) instead of only cron or fixed intervals. Knowtation validates the rule, stores it on the series, and generates stable occurrence keys. Scooling reads the validated series to compute when the orchestrator should wake — it never stores its own copy of the rule.

Technical summary

Additive recurrence.kind: "rrule" on task_loop/v0. Module lib/task/task-loop-rrule.mjs (validates, projects next occurrence, maps to occurrence_key) lands in 2G-f-b. Write proposals (2G-d) reject invalid RRULE at propose time when writes gate on. Read list/get returns recurrence as stored; invalid legacy rows return recurrence_validation_error on detail get only.


Recurrence shape — rrule kind

Field Type Req Description
kind const yes "rrule"
rrule string yes RFC 5545 RRULE value (no RRULE: prefix)
dtstart ISO8601 yes Series anchor instant
anchor_tz IANA string yes Must equal series timezone
exdates ISO8601[] no Skipped occurrence instants
source_ical string no Optional preserved import for audit

Existing kinds (cron, interval, manual, on_wake) unchanged — see 2G-c store contract §1.1.


Validator rules (v0 subset)

Export: validateTaskLoopRrule(recurrence, seriesTimezone): ValidationResult

Rule Behavior
DTSTART Required; must parse as ISO8601
Timezone anchor_tz must match series timezone
FREQ One of DAILY, WEEKLY, MONTHLY, YEARLY
INTERVAL Optional; default 1; must be ≥ 1
BYDAY Allowed with WEEKLY / MONTHLY only
BYMONTHDAY Allowed with MONTHLY / YEARLY only
COUNT Optional; integer ≥ 1
UNTIL Optional on RRULE; must not conflict with series until_at
Rejected tokens BYSETPOS, BYHOUR, BYMINUTE, BYYEARDAY, WKST, EXRULE
exdates Each must parse; must not duplicate

Invalid input → { ok: false, code: "invalid_rrule", detail: "<token>" } — no canonical write.


Occurrence projection

Export: projectNextOccurrence(recurrence, series, afterInstant): OccurrenceProjection | null

Output Description
occurrenceAt ISO8601 next valid instant after afterInstant
occurrenceKey Stable string — e.g. 2026-W25, 2026-06-24, per FREQ policy table
skippedExdates Count of exdates between anchor and result

Lazy materialization (OD-3) uses projection output for write proposals (2G-d).

Occurrence key policy (v0)

FREQ Key format example
WEEKLY 2026-W25 (ISO week in series TZ)
DAILY 2026-06-24
MONTHLY 2026-06 or 2026-06-24 when BYMONTHDAY set
YEARLY 2026 or 2026-06-24 when BYMONTHDAY set

Store module extension (2G-f-b)

lib/task/task-loop-store.mjs — additive:

  • Accept rrule recurrence in fixture seed data (school-trip + loop_weekly_checkin).
  • getTaskLoop returns recurrence verbatim; runs validator on read for mode: "validate".
  • No Hub/CLI/MCP route changes in 2G-f-b (routes land with TASK_LOOP_LIVE_READ Tier 3).

Posture

Constant Location Value
TASK_LOOP_RRULE_ENABLED lib/task/task-loop-rrule.mjs false until Tier 3

When false: validator runs on fixtures/tests only; live store rejects rrule kind on write proposals.


Seven-tier test matrix (Knowtation 2G-f-b)

Tier File Focus
unit test/task-loop-rrule-validator.test.mjs Subset accept/reject matrix
unit test/task-loop-rrule-projection.test.mjs Next occurrence + occurrence_key
integration test/task-loop-rrule-store.test.mjs Fixture series read with rrule kind
data-integrity test/task-loop-rrule-tz.test.mjs DST boundary occurrence keys
security test/task-loop-rrule-security.test.mjs Injection in rrule string; oversized input

Acceptance (2G-f-a)

  • [x] rrule recurrence wire shape frozen.
  • [x] v0 RRULE subset + validator rules documented.
  • [x] Occurrence projection + key policy documented.
  • [x] 2G-f-b implements task-loop-rrule.mjs + store fixture + tests.

Non-goals

  • Full RFC 5545 parser library vendoring decision (2G-f-b chooses impl).
  • iCal file import endpoint.
  • Hosted canister RRULE index.
File History 1 commit
sha256:93bcf8f9bd56d8c5b9339f4ec73b9ebd66571398d56262d38eedc2cfa9db9882 fix(test): align Band B landing assertion with desktop MCP … Human 10 days ago