Keeping time
Every kind of time a Service keeps, indexed with its key, its place in the tick, its money and its page. Each compiles to one timer on the record, fired once on the brand's calendar in a fixed order.
Time answers one question: what happens on its own, and when? It decides when a step runs with no one asking, who hears, when money moves, and when the work can no longer be done. Each kind of time is one key with one page that owns it, and each runs as a timer on the record.
apis.tax's annual return keeps seven kinds of time in one file, and its Tax Plan two more:
subject: {
kind: 'taxpayer', key: 'ein', period: { of: 'tax_year', ends: job => job.input.year_end ?? '12-31' }, // A period
},
deliver: { schedule: {
annual: { order: annualReturn, each: 'tax_year', at: '1mo after end', catch_up: 'open' }, // A schedule
estimate: { order: estimate, each: 'quarter', at: '10d before pay', when: sub => sub.input.entity === 'C corp' },
},
price: { usd: 2_400, every: 'year', includes: { 'annual-return': 1, 'information-returns': 25, estimate: 4, question: 12, 'notice-response': 2 } }, // A cycleA year, and the next runs both on a test clock.
| You need | Write | apis.tax writes |
|---|---|---|
| a deadline on the work | price.within | within: '5d before file', so the deadline follows the filing date |
| a date the world sets, and the step that moves it | clocks.<c>.moves | file: { …, moves: { extend: extendedDue } } |
| a date an extension never moves | clocks.<c>.hard | pay: { …, hard: true } |
| the work every quarter | on.schedule with each | estimate: { each: 'quarter', at: '10d before pay' } |
| the client next year | subject.keeps and on.schedule | keeps: { carryforwards, tax } and annual: { each: 'tax_year', at: '1mo after end' } |
| a notice's reply date | on.event and the minted Service's clock | 'notice.received': noticeResponse, whose reply clock is due on the notice's reply_by |
GET /subjects/:id/calendar lists every date, kept and ahead. Acme's, on 2 February 2028, out to a carryforward that expires in 2046:
$ npx services.dev subjects calendar sbj_7Hq2 --from 2027-02-01 --test-clock tc_7Hq4
sbj_7Hq2 · taxpayer 12-3456789 · Acme Robotics · America/New_York · 21 dates · webcal at /subjects/sbj_7Hq2/calendar.ics
date what kind answers for status
2027-02-01 the 2026 return is minted schedule the brand minted job_4Tx3
2027-04-05 the Q1 estimate is minted schedule the brand minted job_4Tx4
2027-04-15 pay the 2026 tax hard Ada Lovelace kept 2027-04-12, with the extension
Four bands: on the job, dates the world sets, across jobs, after the end. Fires as is the kind's place in the tick.
| Time | You write | Fires as | When it comes, the runtime | The money | Its page |
|---|---|---|---|---|---|
| The job's deadline | price.within: '30d', or '5d before file' | 5 · deadline | At three quarters, at risk; at the end, late. It counts only while the work is the brand's, and written before a clock it follows the clock | price.late comes off the charge, from your line | Deadlines |
| A step's deadline | deliver.<step>.within | 5 · deadline | The same, for one step, and the due date on its Task | none of its own | Deadlines |
| A party's link | deliver.<step>.expires | 3 · ask | A reminder a day before; at expiry it opens once more, and the second expiry ends the job lapsed | the hold returns; paid acts already committed are charged | Asks |
| A grant's term | scope.for | 3 · ask | Once it ends, a step that needs it is refused needs_grant, and the party is asked again | none | Grants and decisions |
| A Task's hold | Pool.hold | 4 · task | Warns the member at three quarters; at its end the Task opens again | none | Tasks |
| A quiet job | signals.<s>.silent | 5 · deadline | Nothing happened for that long: the signal is raised, and its goto moves the job | none | Signals |
Every date falls on one calendar, written on Brand() or on a Service. A due function returns a civil date; the calendar closes it at its minute in its zone, rolls a closed day forward, and records clock.rolled with why.
calendar, on Brand() or on a Service
| Key | Written | Left out | Does |
|---|---|---|---|
zone | string | 'UTC' | An IANA zone: America/New_York. |
weekends | Weekday[] | Saturday and Sunday, once a calendar is written | The days nothing is due on. |
closed | (year) => string[] | none | Each year's closed days, as civil dates: year => ['2028-01-17', …]. |
closes | TimeOfDay | '23:59' | The local minute a due date closes. |
| The question | The calendar's answer |
|---|---|
| when is a date on time | through the last instant of its closes minute: 23:59:59.999 local time |
| whose time decides | the gate's commit on the brand's clock; a submit step's outside receipt time when the system returns one |
| whose zone judges a due | the subject's: a due closes in its calendar's zone, and an e-filed return is on time by its postmark in the filer's own zone |
| which zone a time prints in | traces, timer lists and events print UTC, with Z; calendars and notices print the calendar's zone; every header names its zone |
| a proposal near the close | one already in the gate is judged by when it was proposed; one waiting on a person, by when it commits |
a rings_at daylight saving skips | rings at the next minute that exists; in a repeated hour, the first time |
| a postponement | records clock.moved { by: 'calendar' } on each clock it moves, citing its notice; the one thing that moves a hard date |
apis.tax's 2027 return is due Saturday 15 April 2028, before a holiday Monday, so the federal, California and New York dates and the date to pay roll to the 18th; New Jersey's is its own, 15 May. A rung files the extension for the three dates it rang for: the date to file moves, the date to pay stays:
→ POST /test_clocks/default/advance {"to":"2028-04-16T00:00:00Z"}
2028-04-15 13:00Z clock file · rung run tentative 3d → tentative proposed · committed · US, US-CA, US-NY
2028-04-15 13:00Z extend · US accepted → US-CA accepted, US-NY accepted · US-NJ runs its own run tentative 3d on 2028-05-12, three days before 05-15
2028-04-15 13:00Z clock file · moved · US, US-NY 2028-04-18 → 2028-10-16, rolled from 2028-10-15 · US-CA 2028-04-18 → 2028-11-15 · clock pay unmoved, still 2028-04-18
2028-04-15 13:00Z clock pay · satisfied · pay committedThe brand keeps one clock. At each instant every timer owed by then fires, in its kind's place, so a cycle renews before its schedule mints and money moves last:
| Rule | The runtime |
|---|---|
| place | 1 cycle, 2 schedule, 3 ask, 4 task, 5 deadline, 6 clock, 7 window, 8 money |
| ties | at one instant, by owed time, then place, then when the timer's owner opened, then key, byte by byte |
| armed, not fired | a firing that arms a timer owed later records it armed; the trace prints it indented under the firing that armed it, and counts it apart from what fired |
| chains | a firing at T that arms a timer owed by T fires in the same instant; the tick repeats to a fixpoint |
| runaway | a chain deeper than 32 passes stops, records tick.runaway, and alarms the owner console |
On 1 February 2028 the Tax Plan mints the 2027 return. The mint arms the return's timers in the first pass, and the three notice 90d rungs owed on 19 January fire in place 6 in the second, in the same instant. New Jersey's and Texas's wait for 15 February:
$ npx services.dev tick --to 2028-02-01T20:00Z --test-clock tc_7Hq4 --trace --on sbn_7Hq2,job_4Tx9
tc_7Hq4 · 2028-02-01 14:00Z → 20:00Z · UTC · 4 fired · 52 armed · 1 held · 2 passes
pass owed fired place kind on what
1 2028-02-01 14:00Z 2028-02-01 14:00Z 2 schedule sbn_7Hq2 annual → job_4Tx9 · period 2027 · $0.00 · 1 of 1
armed 1 2029-02-01 14:00Z 2 schedule sbn_7Hq2 annual · period 2028, the next
A timer never writes the record. A rung that runs a step proposes it, and the gate commits or refuses it.
Every kind of time compiles to one timer, tmr_…, under a key no move of a date rewrites, armed, fired, superseded or held, and pinned with the bundle it was armed under. Timers and replay is the runtime contract: the key, the states and how each resumes, what re-arms a timer, a gap, a test clock, redelivery, and a job that outlives a deploy.
Nothing lapses silently. Each lapse is on the record, with who hears it:
| What lapses | When | The runtime | Who hears | Its page |
|---|---|---|---|---|
| a clock | its date comes, unsatisfied | waits on its owner's choice; an option that names a step runs it through the gate | its owner, then the owner console | Clocks |
| a hard date | it passes, unsatisfied | the gate refuses the steps it bars with date_passed | its owner and the owner console | Clocks |
| a job | a party's link expires twice | ends the job lapsed; the hold returns | buyer, owner | Asks |
Three money rules run across the kinds:
| When | The money | Its page |
|---|---|---|
a clock's owner is the brand or of record, and it carries guarantee | a penalty notice for that period is credited from your line, up to the cap; AI and members' pay stay whole | Clocks |
| a job lapses, or is canceled before delivery | the hold returns; paid acts already committed are charged, and their pay is the member's | Release |
| your plan or your file changes | a job, subject or subscription keeps the plan it was ordered under and the file it opened under, so a clock that runs for years runs to its end | Across versions |
| App | Shows | Acts |
|---|---|---|
| Customer portal | Coming up: each subject's calendar, every date ahead across clocks, schedules, links and grants, each with what a miss costs; each subscription's allowances | acknowledge, mark done and choose on their own clocks; keep a date with the steward; cancel at the cycle's end |
| Delivery portal | Your dates: each Task's due date and hold beside the clocks the member answers for, late first; the standby window | extend a hold once; acknowledge, mark done and choose on their own clocks; withdraw from a subject with a successor |
| Owner console | Dates at risk, sorted by what a miss costs, each with why and the control that settles it; timers that fired late; the next 30 days | assign an owner; mark the brand's own dates done; choose on the brand's own |
| Word | Means | Its page |
|---|---|---|
| occurrence | one coming of a date: a clock's next every, one state of an each, one period of a schedule | Clocks |
| owed, fired | when a timer was due, and when it ran; late_by is the gap | Timers and replay |
| rung | a step before a clock's date: notice, escalate, ack or run. Not the Pool's fill ladder, nor a custodian's | Clocks |
Every key runs on every plan in test. Live, time that stays on the job runs on Free; time a Service holds for someone past the job runs on Launch; time that needs a market of people, or that you sell as a guarantee, runs on Team: