Skip to main content

Runtime and State

The runtime is the SQLite-backed RuntimeStore. This section explains how the runtime root is resolved, what data lives there, how gates are persisted, and why direct mutation is forbidden.

Canonical runtime root

The runtime root is the directory containing goal-state.sqlite3 plus the auxiliary directories (next-actions, runs, sessions, logs, locks).

Default location for local dev:

D:\Projects\ASF50-Platform\ASF50\scripts\goal-runner\local\runtime

This is the location used by ASF50_DOCS_PORTAL_001. For production deployments, the runtime root should be moved outside the repository so that runtime state is never Git-tracked.

Path resolution

The orchestrator resolves the runtime root in this order:

  1. The --runtime-root CLI argument.
  2. The ASF50_RUNTIME_ROOT environment variable.
  3. The repository's local/runtime subdirectory.
  4. The scripts/goal-runner/local/runtime subdirectory (legacy fallback).

goal-doctor.ps1 detects split-brain runtime (where two of these disagree) and fails closed.

Repo-local runtime defaults may be non-canonical

scripts/goal-runner/local/runtime is a legacy local-dev location. Production deployments must:

  1. Move the runtime DB to a separate non-canonical directory.
  2. Set ASF50_RUNTIME_ROOT in the operator's environment.
  3. Confirm via goal-doctor.ps1.

If you see the runtime DB inside the repo on a production deployment, that is a configuration bug. Open a repair goal.

What RuntimeStore contains

RuntimeStore persists:

  • goals, phases, tasks — declared state.
  • next_actions — computed dispatch records.
  • owner_gates — gate state with scope/source/approver.
  • evidence_records — Git-tracked evidence pointers with SHA-256.
  • worktrees, pull_requests, agent_sessions, runs — execution bookkeeping.
  • task_dependencies — task graph edges.
  • state_transitions — transition history.
  • repair_attempts, findings — bounded repair and audit findings.
  • ci_runs, commits, processes, quota_events, role_runs, locks — derived bookkeeping.
  • schema_migrations — schema version table.

How gates are persisted

ScopedOwnerGateManager (via owner_gate_manager.py and goal_gate_cli.py) persists gates in the owner_gates table:

gate_id TEXT PRIMARY KEY,
goal_id TEXT NOT NULL,
scope TEXT,
reason TEXT,
status TEXT NOT NULL,
action TEXT,
source TEXT,
payload TEXT,
approver TEXT,
note TEXT,
evidence_path TEXT,
created_at TEXT NOT NULL,
decided_at TEXT,
dispatches_used INTEGER NOT NULL DEFAULT 0,
max_dispatches INTEGER NOT NULL DEFAULT 1,
last_dispatched_run_id TEXT,
reservation_run_id TEXT

Status values: OPEN, APPROVED, REJECTED, CONSUMED.

How runs/tasks/worktrees/CI/evidence relate

Each run is bound to a task and a worktree. Each PR is bound to a goal. Each CI run is bound to a goal. Each evidence record is bound to a goal and a SHA-256 of the evidence file.

Why direct SQLite mutation is forbidden

  1. Auditability — every state change must be recorded by a function with a receipt.
  2. Schema versioning — the schema is migrated by RuntimeStore.migrate(). Direct writes may bypass migration expectations.
  3. Reconciliation — the next materialization overwrites hand-mutated rows back to the canonical value.

If you need to fix a state bug, file a repair goal (goal_type: REPAIR_GOAL).

Backup and recovery

While ASF50 is idle:

sqlite3 goal-state.sqlite3 ".backup backup-$(date +%Y%m%d).sqlite3"

Recovery:

cp backup-20260919.sqlite3 goal-state.sqlite3
# then run reconciliation
python -m goal_registry.reconcile_all

A canonical-state-only recovery is also possible by deleting goal-state.sqlite3 and re-materializing every goal — the registry rebuilds the runtime from the Git-tracked contracts.

Continue with RuntimeStore →.