Time-Based Policies Documentation: (time field, Timestamp, CronSpan) - #752
Time-Based Policies Documentation: (time field, Timestamp, CronSpan)#752omkarshanbhag wants to merge 2 commits into
time field, Timestamp, CronSpan)#752Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
8e24f2b to
e8fe605
Compare
time field, Timestamp, CronSpan)
There was a problem hiding this comment.
Pull request overview
Adds documentation for the new top-level policy time field and its related language constructs so authors can gate policy evaluation by trusted server time, including one-shot timestamp windows and recurring cron-based spans.
Changes:
- Added a new “Time-based policies” authoring guide covering
time.now,Timestamp(...), andCronSpan(...)with examples. - Extended the policy language reference with
timestamp,time.now, and function signatures forTimestamp/CronSpan. - Added a new time-based section to the access-control examples and linked the new page into the docs sidebar.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| features/policies/time-based-policies.mdx | New authoring guide for the time policy field with one-shot and recurring window examples. |
| features/policies/language.mdx | Adds time keywords/types/functions to the policy language reference and links to time docs. |
| features/policies/examples/access-control.mdx | Adds practical time-based policy examples to the access-control examples page. |
| docs.json | Registers the new time-based policies page in the documentation sidebar/navigation. |
Suppressed comments (1)
features/policies/language.mdx:86
- The
timestamptype row points readers to theTimekeyword section, but the UTC-onlyTimestamp(...)construction details live under the time function reference. After adding## Time expressions, link there to avoid sending readers to an unrelated section.
| **timestamp** | Timestamp('2025-01-01T00:00:00Z') | a point in time; construct with `Timestamp('<rfc3339>')`, UTC only (see [Time](#time)) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| The optional top-level `time` field gates when a policy is active. See [Time-based | ||
| policies](/features/policies/time-based-policies) for the authoring guide, and [Time | ||
| expressions](#time-expressions) below for the `Timestamp` and `CronSpan` reference. | ||
|
|
||
| ## Types |
| NOTE: Given that this pattern involves comparing each activity's timestamp (denoted by `time.now`) to specific timestamps, | ||
| you can define a policy's active time in ways beyond a single start time and a single end time (like allow after timestamp, allow before timestamp, multiple specific time windows etc) | ||
|
|
| } | ||
| ``` | ||
|
|
||
| #### Time-based policies |
There was a problem hiding this comment.
Thoughts on breaking out into it's own example page?
|
|
||
| The optional top-level `time` field gates when a policy is active. See [Time-based | ||
| policies](/features/policies/time-based-policies) for the authoring guide, and [Time | ||
| expressions](#time-expressions) below for the `Timestamp` and `CronSpan` reference. |
There was a problem hiding this comment.
did time-expressions ever get added?
| | function | x.count() | "\[1,2,3].count()" | (list\<T>) -> int | | ||
| | function | x.filter(item, \<predicate>) | "\[1,2,3].filter(x, x == 1)" | (list\<T>) -> (list\<T>) | | ||
| | function | Timestamp(\<rfc3339>) | "Timestamp('2025-01-01T00:00:00Z')" | (string) -> timestamp | | ||
| | function | CronSpan(\<cron>, \<duration>, \<tz>) | "CronSpan('0 9 \* \* 1-5', '8h', 'America/New_York')" | (string, string, string) -> bool | |
There was a problem hiding this comment.
Where can I find a list of supported timezones?
| The optional top-level `time` field gates **when** a policy is active. It is authored in the policy | ||
| language, must evaluate to a `bool`, and is evaluated against trusted server time. When `time` is | ||
| absent or empty the policy is always active; when it evaluates to `false` the policy is skipped for | ||
| that request. See [Time](/features/policies/language#time) in the language reference for full |
There was a problem hiding this comment.
how about this for this block:
"The optional top-level time field controls when a policy is active. Like consensus and condition, it is written in the policy language and must evaluate to a bool. When time is not specified, the policy is always active. When the current time falls within the defined window, the expression evaluates to true and the policy participates in evaluation as usual. When it falls outside the window, the expression evaluates to false and the policy is skipped for that request. See Time in the language reference for full details on time.now, Timestamp(...), and CronSpan(...).
|
|
||
| ## Gating when a policy is active | ||
|
|
||
| Turnkey policies support a top-level `time` field alongside `consensus` and `condition` which answers the question **When** is this policy active. |
There was a problem hiding this comment.
"Turnkey policies support a top-level time field alongside consensus and condition. While consensus defines who and condition defines what, the time field defines when a policy is active."
| Like the `consensus` and `condition` fields, the `time` field must evaluate to a `bool`. It is evaluated by comparing the particular policy's `time` field against | ||
| **trusted server time** to see whether the trusted server timestamp falls within the window considered active (an evaluation of true) or not. | ||
|
|
||
| NOTE: the **trusted server time** is NOT a client-supplied timestamp and cannot be spoofed by the caller. |
There was a problem hiding this comment.
could we format this like the mintlify note?
| This policy becomes active at `2025-01-01T00:00:00Z`, becomes inactive at `2025-02-01T00:00:00Z`, | ||
| and is skipped before and after. | ||
|
|
||
| NOTE: Given that this pattern involves comparing each activity's timestamp (denoted by `time.now`) to specific timestamps, |
There was a problem hiding this comment.
can this be the mintlify note style?
| `90m`, or `1d12h`. A seconds component is not allowed, and the total duration must be **7 days or | ||
| less**. | ||
|
|
||
| The `<tz>` argument determines when fires occur and makes windows daylight-saving aware, so a |
There was a problem hiding this comment.
can we list all of the timezone names somewhere?
|
|
||
| <Note> | ||
| Author business hours as a **single fire plus a duration**, not as an hour range. | ||
| `CronSpan('0 9 * * 1-5', '8h', 'America/New_York')` opens one 8-hour window per weekday. Do **not** |
There was a problem hiding this comment.
"CronSpan('0 9 * * 1-5', '8h', 'America/New_York') fires once at 9:00 AM and holds the window open for 8 hours, producing a single continuous 9-to-5 span. An expression like 0 9-17 * * 1-5 fires separately at 9:00, 10:00, 11:00, and so on, each opening its own 8-hour window, which is not the same thing."
andrewkmin
left a comment
There was a problem hiding this comment.
Verified these points against tkhq/mono release v2026.8.0 and reproduced the boundary behavior with the real TimeEvaluator.
| } | ||
| ``` | ||
|
|
||
| This opens a window every night at 10:00 PM Eastern that stays active until 6:00 AM the next morning. |
There was a problem hiding this comment.
CronSpan adds the duration as elapsed time after the zoned fire instant, so this is not 10:00 PM–6:00 AM on both DST-transition nights. With this exact expression in America/New_York, the window ends at 7:00 AM after spring-forward and 5:00 AM after fall-back. Please describe the duration as elapsed time and add that caveat here and in the duplicated access-control example. I verified these boundaries with the released v2026.8.0 TimeEvaluator. Implementation
|
|
||
| **Cron expressions** use a strict 5-field subset. Numeric fields, ranges (`1-5`), lists (`1,3,5`), | ||
| and `*` are supported. The following are **not** supported: step values (`*/n`), macros (`@daily`, | ||
| `@hourly`), month and day names (`JAN`, `MON`), and a seconds field. |
There was a problem hiding this comment.
Please document the day-of-month/day-of-week interaction. The pinned croner behavior is OR, so 0 9 1 * 1 fires on every first of the month and every Monday—not only when the first is Monday. Because that can broaden an authorization window, the reference should state the OR rule, the numeric day-of-week range (0-6, where 0 is Sunday), and that this subset does not support the AND modifier.
| single value is used for the entire request, so every comparison within a policy sees a consistent | ||
| instant. | ||
| - **`Timestamp('<rfc3339>')`** — constructs a `timestamp` from an RFC 3339 string. Timestamps must be | ||
| **UTC**: the string must end in `Z`. Non-zero offsets (e.g. `-05:00`) are rejected. |
There was a problem hiding this comment.
The released evaluator accepts both Z and +00:00 as UTC; only non-zero offsets are rejected. Please change “must end in Z” to “must use UTC (Z or +00:00)” so the documented validation contract matches mono. Test
| | Argument | Type | Description | | ||
| | ------------ | ------ | -------------------------------------------------------------------------------------------------------- | | ||
| | `<cron>` | string | A 5-field cron expression (`minute hour day-of-month month day-of-week`) marking when each window opens. | | ||
| | `<duration>` | string | How long each window stays open, as a Go-style duration. | |
There was a problem hiding this comment.
This is not Go's duration grammar: Go supports fractional values and units down to nanoseconds but does not support d. Please call this a custom whole-number d/h/m format and state that seconds and fractions are rejected.
Summary
This PR contains documentation for time based policies (introduced via a new top level field on policies called
time)Overall Summary of new functionality added:
### Timekeyword subsection documentingtime.now(typetimestamp, single trusted server-time value per request).timestampprimitive type row (Timestamp('<rfc3339>'), UTC-only).## Timesection covering:timemust eval tobool; evaluated against trusted server time; absent/empty = always active,false= policy skipped (not a deny).time.nowandTimestamp('<rfc3339>')(UTC only —Z; non-zero offsets rejected).time.now >= Timestamp(start) && time.now < Timestamp(end)(start-incl, end-excl).CronSpan('<cron>', '<duration>', '<tz>')— fire + duration union model, strict 5-field cron subset (no*/n,@macros, names, seconds), Go-styled/h/mduration ≤ 7 days, IANA tz / DST-aware.CronSpan('0 9 * * 1-5', '8h', 'America/New_York'), with a callout warning against the hour-range (0 9-17 ...) form.&&/||/!), and combiningtimewithconsensus+condition.🤖 Generated with Claude Code