Skip to main content

v2.10.0

Release notes for orun v2.10.0.

Highlights​

  • Trigger-first, revision-first state model. Every plan compiled by orun plan is now filed under a PlanRevision together with the TriggerOccurrence that produced it. Every orun run is filed under that revision as a deterministic run-NNN. The legacy .orun/plans/<sha>.json and .orun/executions/<id>/ paths are preserved as a compatibility mirror — every existing reader keeps working untouched.
  • Hidden orun state migrate. Idempotently rehomes a pre-v2.10.0 workspace into the new layout. Synthesizes a system.migrated trigger per legacy plan, attaches matching executions, and updates refs / indexes.
  • New describe aliases. orun describe revision, orun describe trigger, and orun describe execution render the three documents that make up the new chain. describe run and describe plan continue to work, now via the new resolver with a legacy fallback.
  • End-to-end + property gates. make test-state-redesign runs four coverage floors (statestore ≥ 95 %, revision ≥ 90 %, executionstate ≥ 90 %, triggerctx ≥ 90 %) under -race. A new cmd/orun/state_e2e_test.go walks plan → run → status → logs → describe revision latest against a temp workspace.

The new on-disk layout​

.orun/
├── version.json
├── revisions/<rev-key>/
│ ├── trigger.json ← TriggerOccurrence (why)
│ ├── plan.json ← compiled plan (what)
│ ├── revision.json ← PlanRevision summary
│ ├── manifest.json ← latest-execution summary
│ └── executions/run-NNN/
│ ├── execution.json
│ ├── snapshot.latest.json
│ └── logs/<job>/<step>.log
├── refs/
│ ├── latest-revision.json
│ ├── latest-execution.json
│ └── triggers/<name>/{latest,<scope>}.json
├── indexes/
│ ├── revisions/<key>.json
│ └── executions/<key>.json
├── plans/ ← legacy mirror
└── executions/ ← legacy mirror

Revision keys have the form rev-<scope>-<shortHeadSha>-p<planHash8>, so rev-pr139-def456a-p8f31c09 reads as the plan compiled for PR 139 at commit def456a whose plan hashes to 8f31c09….

See State model for the full layout, field definitions, and resolution chain.

What changed in the CLI​

CommandWhat's new
orun planAlways resolves a TriggerOccurrence (system-manual for ad-hoc runs), emits a revision-summary block on success, writes .orun/revisions/<key>/{trigger,plan,revision,manifest}.json plus the legacy mirror.
orun runNew --revision <key> flag executes a specific revision, skipping the resolution chain. Bridges runner output through executionstate.Bridge so the canonical record lives under the revision while the legacy .orun/executions/ tree is hardlink-mirrored.
orun statusNew --revision <key> flag lists all executions under a revision. Default lookup uses refs/latest-execution.json.
orun logsNew --revision <key> flag for exact lookup when combined with --exec-id. Legacy .orun/executions/<id>/logs/ remains a fallback.
orun describeNew revision, trigger, and execution aliases. Existing describe run/describe plan continue to work through the new resolver.
orun get plansWhen the new layout has revisions, lists revision keys + trigger + jobs + latest exec; otherwise falls back to a legacy plan-hash table.
orun state migrateHidden command. Walks .orun/plans/ and .orun/executions/, rehomes each into the new layout. --dry-run previews.

The CLI surface is fully backward-compatible. Every previously accepted invocation still works; new flags and aliases are additive. The compatibility mirror (stateCompatibilityWrites) is on by default.

TriggerOccurrence — system.* constructors​

Every plan now resolves one of:

TriggerWhen
system.manualBare orun plan / orun run with no --trigger and no --from-ci.
system.manual-changedManual invocation with --changed.
system.replayA re-plan from an existing revision.
system.apiPlan compiled by the backend / a programmatic caller.
system.migratedSynthesized by orun state migrate for legacy plans.
declaredMatched a triggerBindings: entry in intent.yaml via --trigger NAME or --from-ci.

This closes the largest semantic gap in the old layout: a plan with no declared trigger now still has a trigger.json and a revision key, so every later command (status, logs, describe, the future TUI cockpit pane, the future R2/Cloud driver) can answer "why was this compiled?" without heuristics.

Architecture: StateStore + Bridge​

Phase 1 introduces three new internal packages:

  • internal/triggerctx — the TriggerOccurrence model, ULID generator, system constructors, and ResolveTriggerContext dispatcher.
  • internal/statestore — the StateStore interface (Read, Write, CreateIfAbsent, CompareAndSwap, List, Delete) with a local driver implementing atomic temp+rename writes, orphan cleanup on startup, typed error taxonomy (ErrNotFound, ErrExists, ErrConflict, ErrInvalid), and typed ref/index helpers. The contract is frozen — Phase 2 will add R2/S3 drivers that satisfy the same interface without callers changing.
  • internal/revision + internal/executionstate — the writers, resolvers, and the executionstate.Bridge that mirrors live runner output into the revision-first layout via hardlinks (with copy fallback on cross-device file systems).

The runner did not change. The bridge subscribes to the existing state-update hook so Phase 1 stays a pure additive layer over the runtime.

Compatibility​

All of these continue to work without code changes on your side:

  • orun status / orun logs / orun describe run against a workspace that has no revisions yet (legacy .orun/executions/ is read transparently).
  • --plan <hash|name> resolution.
  • Reading and writing .orun/plans/<sha>.json, latest.json, and .orun/executions/<id>/.
  • orun get plans against legacy state.

To opt out of the legacy mirror after migrating, pass --state-compat-writes=false (or set the equivalent in ~/.orun/config.yaml). Phase 2 will add --prune-legacy to delete the mirror.

Migration​

orun state migrate --dry-run # preview
orun state migrate # apply

The command is idempotent: re-running it after new state has landed fills in the gaps without touching anything already rehomed.

What's next​

Phase 2 will add the remote StateStore drivers (R2/S3, then Orun Cloud) on the same frozen interface, plus --persist-revision, orun ref pin, and --prune-legacy. Phase 3 will wire DO-backed coordination, Supabase indexes, and cross-plan evidence reuse for the backend. The CLI surface and on-disk schemas published in v2.10.0 will not change between phases.

References​