Plain-language summary
Asking ActiveGraph to open a run that does not exist quietly creates an empty run with that name instead of saying "not found." A typo can therefore manufacture a record that looks legitimate, hide the original mistake, and later be inspected or operated on as if it had always existed.
Reproduction
import tempfile
from pathlib import Path
from activegraph import Runtime, SQLiteEventStore
with tempfile.TemporaryDirectory() as directory:
database = str(Path(directory) / "runs.sqlite")
missing_run_id = "run_missing"
before = [r.run_id for r in SQLiteEventStore.list_runs(database)]
loaded = Runtime.load(database, run_id=missing_run_id)
after = [r.run_id for r in SQLiteEventStore.list_runs(database)]
print("before:", before)
print("loaded:", loaded.run_id)
print("after:", after)
Observed output:
before: []
loaded: run_missing
after: ['run_missing']
The same result reproduces at the released v1.10.0 commit 148e12c2969f18fa12a1a3c2e75f3affd9aa0616 and PR #78's current head 221d9ecd8dcba510c4707c219b463a9a1404efc2 (activegraph 1.11.0).
Expected behavior
Runtime.load(..., run_id=<unknown>) should fail with the run catalog unchanged and no event accepted. A load/resume operation should not create the thing it was asked to retrieve.
Actual behavior
Runtime.load() replays an empty event list, returns a usable empty runtime, and inserts the requested ID into the runs table through its unconditional store.upsert_run(...) call.
The CLI already works around this in promote: it checks the run catalog before calling Runtime.load() because a mistyped ID would otherwise create a phantom run.
Concise technical explanation
When an explicit run_id is supplied, Runtime.load() skips the most_recent_run_id existence check, opens a store scoped to that arbitrary ID, reads zero events, and then calls upsert_run(created_at=...). Store construction plus an empty event iterator does not distinguish an existing empty run from a missing run.
Proposed solution
Make load/resume strictly non-creating:
- Check canonical run metadata before replay when a
run_id is explicit.
- Raise a typed not-found error without mutating the store when neither metadata nor events exist.
- Treat events without canonical run metadata as corruption or route legacy repair through an explicit migration path.
- Remove the unconditional metadata repair from the normal load path.
The exact error type and any legacy migration behavior are design choices; the required invariant is that a failed lookup cannot create a run.
Repeatable acceptance tests
- On SQLite and PostgreSQL, loading an unknown explicit run raises, leaves
list_runs() unchanged, and accepts no event.
- An explicitly created, valid empty run still loads successfully.
- Loading from a store with no runs and no explicit ID fails without creating metadata.
- A store containing events but no canonical run row fails closed or is handled only by an explicit migration test.
- CLI and library entry points produce the same not-found semantics.
These tests should run against the common store/runtime lifecycle contract rather than only one CLI call site.
Invariant at risk
- Owning layer: runtime lifecycle and run identity.
- Invariant: load/resume observes an existing canonical run; it does not mint identity or silently repair authority metadata.
- What would disprove the fix: any load path that changes
list_runs() after an unknown-run lookup, or different behavior across SQLite and PostgreSQL.
- Out of scope: backend discovery, SurrealDB support, graph projection rebuilding, and broader run-catalog API design.
Failure-path evidence
Before the call, the run catalog is empty. No exception is raised. After the call, run_missing exists in canonical run metadata even though no creation API was invoked and no event was accepted.
Framework version
activegraph 1.10.0 at tag v1.10.0 / 148e12c2969f18fa12a1a3c2e75f3affd9aa0616
activegraph 1.11.0 at PR #78 head 221d9ecd8dcba510c4707c219b463a9a1404efc2
Python version
OS
Anything else worth knowing
The reproduction uses only the standard-library SQLite backend and no custom store, pack, provider, network call, or private payload.
Plain-language summary
Asking ActiveGraph to open a run that does not exist quietly creates an empty run with that name instead of saying "not found." A typo can therefore manufacture a record that looks legitimate, hide the original mistake, and later be inspected or operated on as if it had always existed.
Reproduction
Observed output:
The same result reproduces at the released
v1.10.0commit148e12c2969f18fa12a1a3c2e75f3affd9aa0616and PR #78's current head221d9ecd8dcba510c4707c219b463a9a1404efc2(activegraph 1.11.0).Expected behavior
Runtime.load(..., run_id=<unknown>)should fail with the run catalog unchanged and no event accepted. A load/resume operation should not create the thing it was asked to retrieve.Actual behavior
Runtime.load()replays an empty event list, returns a usable empty runtime, and inserts the requested ID into therunstable through its unconditionalstore.upsert_run(...)call.The CLI already works around this in
promote: it checks the run catalog before callingRuntime.load()because a mistyped ID would otherwise create a phantom run.Concise technical explanation
When an explicit
run_idis supplied,Runtime.load()skips themost_recent_run_idexistence check, opens a store scoped to that arbitrary ID, reads zero events, and then callsupsert_run(created_at=...). Store construction plus an empty event iterator does not distinguish an existing empty run from a missing run.Proposed solution
Make load/resume strictly non-creating:
run_idis explicit.The exact error type and any legacy migration behavior are design choices; the required invariant is that a failed lookup cannot create a run.
Repeatable acceptance tests
list_runs()unchanged, and accepts no event.These tests should run against the common store/runtime lifecycle contract rather than only one CLI call site.
Invariant at risk
list_runs()after an unknown-run lookup, or different behavior across SQLite and PostgreSQL.Failure-path evidence
Before the call, the run catalog is empty. No exception is raised. After the call,
run_missingexists in canonical run metadata even though no creation API was invoked and no event was accepted.Framework version
Python version
OS
Anything else worth knowing
The reproduction uses only the standard-library SQLite backend and no custom store, pack, provider, network call, or private payload.