Keeping time

The running jobjob_7Hq2 · working · $49.00 held

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:

tax · services/annual-return.ts
  subject: {
    kind: 'taxpayer', key: 'ein', period: { of: 'tax_year', ends: job => job.input.year_end ?? '12-31' }, // A period
  },
  deliver: {
tax · services/tax-plan.ts
    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 cycle

A year, and the next runs both on a test clock.

You needWriteapis.tax writes
a deadline on the workprice.withinwithin: '5d before file', so the deadline follows the filing date
a date the world sets, and the step that moves itclocks.<c>.movesfile: { …, moves: { extend: extendedDue } }
a date an extension never movesclocks.<c>.hardpay: { …, hard: true }
the work every quarteron.schedule with eachestimate: { each: 'quarter', at: '10d before pay' }
the client next yearsubject.keeps and on.schedulekeeps: { carryforwards, tax } and annual: { each: 'tax_year', at: '1mo after end' }
a notice's reply dateon.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:

captured/time-timers.txt · magic values: none
$ 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.

TimeYou writeFires asWhen it comes, the runtimeThe moneyIts page
The job's deadlineprice.within: '30d', or '5d before file'5 · deadlineAt 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 clockprice.late comes off the charge, from your lineDeadlines
A step's deadlinedeliver.<step>.within5 · deadlineThe same, for one step, and the due date on its Tasknone of its ownDeadlines
A party's linkdeliver.<step>.expires3 · askA reminder a day before; at expiry it opens once more, and the second expiry ends the job lapsedthe hold returns; paid acts already committed are chargedAsks
A grant's termscope.for3 · askOnce it ends, a step that needs it is refused needs_grant, and the party is asked againnoneGrants and decisions
A Task's holdPool.hold4 · taskWarns the member at three quarters; at its end the Task opens againnoneTasks
A quiet jobsignals.<s>.silent5 · deadlineNothing happened for that long: the signal is raised, and its goto moves the jobnoneSignals

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

KeyWrittenLeft outDoes
zonestring'UTC'An IANA zone: America/New_York.
weekendsWeekday[]Saturday and Sunday, once a calendar is writtenThe days nothing is due on.
closed(year) => string[]noneEach year's closed days, as civil dates: year => ['2028-01-17', …].
closesTimeOfDay'23:59'The local minute a due date closes.
The questionThe calendar's answer
when is a date on timethrough the last instant of its closes minute: 23:59:59.999 local time
whose time decidesthe gate's commit on the brand's clock; a submit step's outside receipt time when the system returns one
whose zone judges a duethe 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 intraces, 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 closeone 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 skipsrings at the next minute that exists; in a repeated hour, the first time
a postponementrecords 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:

captured/clock-calendar.txt · magic values: owner.email = signs@example.test, metadata.sandbox.asks = {"organizer.books":"after 160d"}
→ 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 committed

The 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:

RuleThe runtime
place1 cycle, 2 schedule, 3 ask, 4 task, 5 deadline, 6 clock, 7 window, 8 money
tiesat one instant, by owed time, then place, then when the timer's owner opened, then key, byte by byte
armed, not fireda 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
chainsa firing at T that arms a timer owed by T fires in the same instant; the tick repeats to a fixpoint
runawaya 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:

captured/time-timers.txt · magic values: none
$ 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 lapsesWhenThe runtimeWho hearsIts page
a clockits date comes, unsatisfiedwaits on its owner's choice; an option that names a step runs it through the gateits owner, then the owner consoleClocks
a hard dateit passes, unsatisfiedthe gate refuses the steps it bars with date_passedits owner and the owner consoleClocks
a joba party's link expires twiceends the job lapsed; the hold returnsbuyer, ownerAsks

Three money rules run across the kinds:

WhenThe moneyIts page
a clock's owner is the brand or of record, and it carries guaranteea penalty notice for that period is credited from your line, up to the cap; AI and members' pay stay wholeClocks
a job lapses, or is canceled before deliverythe hold returns; paid acts already committed are charged, and their pay is the member'sRelease
your plan or your file changesa 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 endAcross versions
AppShowsActs
Customer portalComing up: each subject's calendar, every date ahead across clocks, schedules, links and grants, each with what a miss costs; each subscription's allowancesacknowledge, mark done and choose on their own clocks; keep a date with the steward; cancel at the cycle's end
Delivery portalYour dates: each Task's due date and hold beside the clocks the member answers for, late first; the standby windowextend a hold once; acknowledge, mark done and choose on their own clocks; withdraw from a subject with a successor
Owner consoleDates at risk, sorted by what a miss costs, each with why and the control that settles it; timers that fired late; the next 30 daysassign an owner; mark the brand's own dates done; choose on the brand's own
WordMeansIts page
occurrenceone coming of a date: a clock's next every, one state of an each, one period of a scheduleClocks
owed, firedwhen a timer was due, and when it ran; late_by is the gapTimers and replay
runga step before a clock's date: notice, escalate, ack or run. Not the Pool's fill ladder, nor a custodian'sClocks

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: