Skip to content

Time-Based Policies Documentation: (time field, Timestamp, CronSpan) - #752

Open
omkarshanbhag wants to merge 2 commits into
mainfrom
omkar/time-based-policies
Open

Time-Based Policies Documentation: (time field, Timestamp, CronSpan)#752
omkarshanbhag wants to merge 2 commits into
mainfrom
omkar/time-based-policies

Conversation

@omkarshanbhag

@omkarshanbhag omkarshanbhag commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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:

  • New ### Time keyword subsection documenting time.now (type timestamp, single trusted server-time value per request).
  • New timestamp primitive type row (Timestamp('<rfc3339>'), UTC-only).
  • New comprehensive ## Time section covering:
    • who/what/when framing; time must eval to bool; evaluated against trusted server time; absent/empty = always active, false = policy skipped (not a deny).
    • time.now and Timestamp('<rfc3339>') (UTC only — Z; non-zero offsets rejected).
    • Time-bound (one-shot) policies: time.now >= Timestamp(start) && time.now < Timestamp(end) (start-incl, end-excl).
    • Active time spans (recurring) via CronSpan('<cron>', '<duration>', '<tz>') — fire + duration union model, strict 5-field cron subset (no */n, @macros, names, seconds), Go-style d/h/m duration ≤ 7 days, IANA tz / DST-aware.
    • Correct 9-to-5: CronSpan('0 9 * * 1-5', '8h', 'America/New_York'), with a callout warning against the hour-range (0 9-17 ...) form.
    • Overnight/midnight-crossing windows, composability (&&/||/!), and combining time with consensus + condition.

🤖 Generated with Claude Code

@mintlify

mintlify Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
turnkey-docs 🟢 Ready View Preview Aug 4, 2026, 3:47 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@omkarshanbhag omkarshanbhag changed the title Document time-based policies (time field, Timestamp, CronSpan) Time-Based Policies Documentation: (time field, Timestamp, CronSpan) Aug 4, 2026
@andrewkmin
andrewkmin requested a review from Copilot August 4, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(...), and CronSpan(...) with examples.
  • Extended the policy language reference with timestamp, time.now, and function signatures for Timestamp / 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 timestamp type row points readers to the Time keyword section, but the UTC-only Timestamp(...) 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.

Comment on lines +68 to 72
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
Comment on lines +57 to +59
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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 andrewkmin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants