Pulse Schema Reference

Normative DB schema for the Pulse durable runtime: enums, task definitions, runs, checkpoints, stage log, stage attempt log.


On this page
  1. 1. Enums
  2. 2. Task definitions
  3. 3. Runs
  4. 4. Checkpoints
  5. 5. Stage log
  6. 6. Stage attempt log
  7. 7. Run events
  8. 8. Graph state
  9. 9. Signals
  10. Appendix A — Ownership
  11. Related

Pulse Schema Reference

Pulse owns its own schema (pulse) and DB role. Host tables are never read or written by Pulse; operator surfaces read Pulse state through the Pulse service API, not by querying these tables directly.

All columns below are in the pulse schema.

1. Enums

pulse.run_status      -- pending | running | waiting | completed | failed
                      -- cancelled | timeout | skipped
pulse.trigger_source  -- schedule | manual | retry
pulse.stage_status    -- started | completed | failed | skipped

waiting is the run-level state used while at least one node is suspended on a signal and no other node is runnable.

Status and source columns use PostgreSQL enums for type safety.

2. Task definitions

pulse.task_definitions
  task_id            uuid primary key
  task_type          text not null
  task_name          text not null unique
  config             jsonb not null              -- CortexTaskEnvelope
  cron_expression    text not null               -- empty for one-off
  enabled            boolean not null default true
  scheduler_claimed  boolean not null default false
  next_run_at        timestamptz
  last_run_at        timestamptz
  last_run_status    pulse.run_status
  timeout_seconds    integer not null default 3600
  created_at         timestamptz not null
  updated_at         timestamptz not null

scheduler_claimed is the atomic claim flag. The scheduler sets it true under CAS when it creates a run, and resets it to false during schedule finalization (on completion, failure, cancellation, or lease-expiry recovery). The enabled-tasks index filters on WHERE enabled = true AND scheduler_claimed = false.

config carries a CortexTaskEnvelope (types reference) that the launch path decodes eagerly at creation and at execution time.

3. Runs

pulse.runs
  run_id               uuid primary key
  task_id              uuid not null references pulse.task_definitions(task_id)
  status               pulse.run_status not null
  trigger_source       pulse.trigger_source not null
  started_at           timestamptz           -- null while pending
  completed_at         timestamptz
  duration             interval
  lease_owner          text                  -- hostname/pid for crash detection
  lease_expires_at     timestamptz
  cancel_requested_at  timestamptz           -- polled by executor
  cancel_reason        text
  error_type           text
  error_message        text
  error_retryable      boolean
  skip_reason          text
  parent_run_id        uuid references pulse.runs(run_id)
  created_at           timestamptz not null

parent_run_id links a retry to its origin. Retry creates a fresh run and does not inherit the parent’s checkpoint lineage — see run identity for the full verb table.

4. Checkpoints

pulse.checkpoints
  run_id            uuid primary key references pulse.runs(run_id)
  task_type         text not null
  checkpoint_name   text not null
  state             jsonb not null             -- CheckpointEnvelope
  summary           jsonb
  updated_at        timestamptz not null

Mutable latest-checkpoint per run. The state payload is a versioned CheckpointEnvelope; mismatched envelopes fail the run non-retryably on resume rather than silently resuming against changed code.

5. Stage log

pulse.stage_log
  id              bigserial primary key
  run_id          uuid not null references pulse.runs(run_id)
  stage_name      text not null
  status          pulse.stage_status not null
  state_summary   jsonb
  started_at      timestamptz not null
  completed_at    timestamptz

Append-only audit trail. Does not depend on observability log retention.

6. Stage attempt log

pulse.stage_attempt_log
  attempt_id       bigserial primary key
  stage_log_id     bigint not null references pulse.stage_log(id) on delete cascade
  run_id           uuid not null references pulse.runs(run_id) on delete cascade
  attempt_number   integer not null   -- unique per (stage_log_id, attempt_number)
  status           pulse.stage_status not null
  summary          jsonb              -- error_type, error message on failure
  started_at       timestamptz not null
  completed_at     timestamptz

One row per stage attempt. Stages with retry policies produce a row per retry, preserving the failure/retry timeline for operator inspection.

7. Run events

pulse.run_events
  event_id     bigserial primary key
  run_id       uuid not null references pulse.runs(run_id) on delete cascade
  event_type   text not null
  severity     text not null               -- info | warn | error
  message      text not null
  details      jsonb
  created_at   timestamptz not null default now()

Append-only per-run event log. The normative event catalog, severity, and field contract live in events.md; persistence is best-effort — event writes never block or fail a run.

8. Graph state

pulse.graph_state
  run_id                    uuid primary key references pulse.runs(run_id) on delete cascade
  node_statuses             jsonb not null
  node_outputs              jsonb not null
  remaining_rewrite_budget  jsonb
  runtime_version           integer
  applied_rewrite_id        bigint references pulse.graph_rewrites(rewrite_id)
  node_provenance           jsonb
  topology_hash             text
  updated_at                timestamptz not null

Mutable latest graph snapshot for durable resume. node_statuses and node_outputs carry the runtime graph-state maps; rewrite fields bind the snapshot to the materialized rewrite lineage.

updated_at is also the optimistic compare-and-swap revision token. The first graph-state write is insert-only. Later writes update the row only when the caller’s expected revision matches the current updated_at; stale owners stop without overwriting newer graph state and emit run.graph_state_stale_write.

9. Signals

pulse.signals
  signal_id     bigserial primary key
  run_id        uuid not null references pulse.runs(run_id) on delete cascade
  signal_name   text not null
  node_id       text                        -- nullable: which node registered the wait
  status        text not null default 'pending'   -- pending | delivered | expired
  payload       jsonb                       -- null until delivered
  created_at    timestamptz not null
  delivered_at  timestamptz
  expires_at    timestamptz

  -- At most one pending signal per (run, name); the same name may be reused
  -- across stages within a run once a prior wait has been delivered or expired.
  unique index (run_id, signal_name) where status = 'pending'

Durable external-event storage. The signal protocol, delivery semantics, and StageSuspend interaction are in signals.md.

Appendix A — Ownership

The pulse DB role has full ownership of every table in this schema and no access to host tables. Cross-boundary reads are mediated by Pulse’s HTTP API. Running Pulse and a host application in the same PostgreSQL cluster is an operational shortcut; the architectural boundary is service-API-first so migrating to a separate database is cheap.


End of Pulse Schema Reference.