Skip to content
Like what we’re building? Star on GitHub

Data model — automation surfaces ​

The trigger spine that turns a board event into a bot launch, the two surfaces that fire a bot on a clock, and five things a multi-replica deployment claims or leases before it acts.

Drawn from the code at commit 61a305262 — main at v3.266.1, after #2391, which carries the launch rung; how this page is kept true: README.

Block A — the trigger spine ​

  • board_config is one collection holding three document shapes. The board schema (_id = the team), the event-seq allocator (seq:<team>) and the trigger cursor (trigger-cursor:<team>). None of the three carries a tenant_id field — the tenant is the _id.
  • A projection row names no subscription. kind: projection is owed to the team's bound external board, so sub_id is empty. The binding is reached by key (forge_board_bindings._id is the team id), which is why that edge is solid, not dotted.
  • A bot id is a slug, resolved at launch, never a foreign key. The walk is: the team's bot_sources row for (tenant, slug), then the platform row (tenant platform:), then the baked catalog on the server's own filesystem — so a slug that resolves to nothing is a launch failure, not a dangling reference. The same holds for issue.bot and for scheduled_bots.bot_id.
  • The board field names are the lowercased Go names. The inner documents are native.Issue and native.Event verbatim with no bson tags, so the stored fields are issue.claimleaseuntil, issue.lastrunid, issue.statereason and event.issueid — while the REST API renders the snake_case JSON names.

Block B — the two scheduled surfaces, side by side ​

  • Both rows carry the same policy; their clocks live separately. A schedule-kind trigger_subscriptions row has no next-fire field — the resident trigger.Scheduler persists its calendar and pending intentions in schedule-intents.json. Cloud slots persist in schedule_launch_intents before scheduled_bots.next_fire_at advances, so a maintenance hold survives a restart.
  • The two surfaces do not overlap — and together they do not cover every row. cloudsched fires scheduled_bots rows only; the resident trigger.Scheduler fires subscriptions of tenant "" only, the local single-host scope. So a schedule- or keepalive-kind trigger_subscriptions row carrying a real team id is fired by neither.
  • The gate persists nothing of its own. pkg/schedgate returns a TickRecord; that record is the tick's trace. In cloud the record lands in audit_events (action schedule.tick.<decision>, tenant_id the schedule's team).

Block C — what is leased, and by whom ​

  • Five mechanisms, five grains. A card is held by a fenced CAS claim (marker plus epoch plus a 15-minute lease); an effect row is held by a claim id with a 2-minute horizon; one team's board binding is held, for the length of a reconciliation pass, by a watermark CAS plus a 5-minute lease; a named periodic net is held by a sticky lease in leases; and a schedule's next fire is held by a CAS on next_fire_at (cloudsched's ClaimTick) — no lease, exactly once per slot. Only the sticky lease is an election.
  • There is no dispatcher-coordinator lease. leases holds two names in this tree, merge-gate-sweeper (the merge-gate sweep) and board-issue-sync (the forge→board issue sync of the repo integrations).
  • pkg/lease is load reduction, not mutual exclusion. A holder that stalls past its TTL can overlap its successor, so the package is for idempotent reconciliation nets whose cost scales with replicas.
  • A lease running out does nothing by itself. The expiry is a predicate the sweeps query, never a timer.

Collections ​

CollectionPackageDocument structTenant field (values)Unique / TTL indexesTwin
trigger_subscriptionspkg/triggerSubscriptiontenant_id — a team; "" is the local scopenone unique, none TTLmemory (local, manifest-seeded each start) / Mongo (cloud)
trigger_effectspkg/trigger (type), pkg/dispatcher/boardmongo (store)EffectRowtenant_id — a teamTTL done_ttl, 7 days, partial on state: "done"file trigger-effects.json beside the native board
board_issuespkg/dispatcher/boardmongoissueDoc wrapping native.Issuetenant_id — a teamnone unique, none TTLfile issues/<id>.json
board_eventspkg/dispatcher/boardmongoeventDoc wrapping native.Eventtenant_id — a teamnone unique, none TTLfile events.jsonl
board_configpkg/dispatcher/boardmongoconfigDoc wrapping native.Boardthe _id is the tenant: <team>, seq:<team>, trigger-cursor:<team>the _id index onlyfile board.json (the seq is recovered from events.jsonl; the cursor has no twin)
scheduled_botspkg/cloudschedScheduledBottenant_id — a teamnone unique, none TTLnone (cloud-only) — the in-process store is test-only; ~/.iterion/schedules.yaml is the host counterpart
schedule_launch_intentspkg/cloudschedLaunchIntentschedule.tenant_id — a teamunique slot _id; no TTLschedule-intents.json beside the native board (resident trigger scheduler)
leasespkg/leasenone — {_id, owner, expires_at, renewed_at}none — deployment-wide namesthe _id index onlymemory-only (which elects every process, so it is for one)
forge_board_bindingspkg/forgeBoardBindingthe _id is the teamnone unique, none TTLnone (cloud-only) — the in-process store answers the OpenAPI spec generator and tests, no runtime mode
bot_sourcespkg/botsourceBotSourcetenant_id — a team, or platform:tenant_slug_uniquememory-only (local mode and tests); the baked catalog is the filesystem tier
"FS: baked catalog"pkg/botregistrynot a collection—not a collectionthe bundles shipped in the image, read from bots/, examples/ and .botz/ under the work dir (or Bots.Paths)
repo_integrationspkg/forgeRepoIntegrationtenant_id — a teamtenant_conn_repo_uniquenone (cloud-only) — same as the binding above: spec generator and tests only
runspkg/store/mongostore.Runtenant_id — a team; absent on a local or resident-surface runnone unique, none TTLfile runs/<id>/run.json
audit_eventspkg/auditaudit.Eventtenant_id — a team on the schedule.* rows drawn here, an org on auditOrg rows, empty or either on scope: platform rows (identity)TTL audit_ttl, 400 daysnone (cloud-only); the tick records go to the local JSONL

The three board collections share one filesystem twin, the native tracker's <store-dir>/dispatcher/ directory (board.json, issues/<id>.json, events.jsonl): the same native.Issue and native.Event values, stored on both sides — under their json tags on disk, the lowercased Go field names in Mongo.