Skip to content

v1.11: make event acceptance and run ownership trustworthy - #78

Open
yoheinakajima wants to merge 2 commits into
mainfrom
codex/activegraph-v1.11-trust-boundary
Open

yoheinakajima wants to merge 2 commits into
mainfrom
codex/activegraph-v1.11-trust-boundary

Conversation

@yoheinakajima

Copy link
Copy Markdown
Owner

Why

The open integrity issues all point at one boundary problem: the runtime did not consistently define when an event became an owned, durable fact. That allowed caller payload mutation, projection-before-append divergence, stale shared-run writers, and false patch.applied records.

Decisions implemented

  • accepted events are canonical detached values on every storage mode
  • authoritative append precedes projection and observation
  • logical ids stay run-scoped; one logical writer owns a run
  • patch operations are the closed set update | replace
  • pack surface ownership is exclusive by name
  • framework-owned process globals are isolated between tests

Main changes

  • canonicalize/detach at Graph.emit, EventStore append/read, listeners, and sinks
  • transactional expected-head checks in SQLite and Postgres with ConcurrentWriterError
  • same-run/same-head guard on Graph.attach_store
  • InvalidPatchOperation, fail-closed live/replay validation, exhaustive projection
  • append-only CONTRACT v1.11 amendment, changelog, operator docs, error pages
  • expanded reusable EventStore conformance suite and focused integrity regressions

Closes #67
Closes #73
Closes #74
Closes #76
Closes #77

Verification

  • pytest -m 'not slow' -q --ignore=tests/test_sandbox_trial.py: 990 passed, 43 skipped, 13 deselected
  • full suite: 1004 passed with the 4 previously-known macOS RLIMIT_AS platform expectation failures
  • mypy: 54 files clean
  • docstring gate: Ring 0 125/125; Ring 1 90.6%
  • git diff --check: clean

@yoheinakajima

Copy link
Copy Markdown
Owner Author

Architecture record: this PR implements ADRs 0053–0055 / D078–D080 in activegraph-vision PR #44. Review the code against those invariant-level decisions and the appended v1.11 runtime contract, rather than as five unrelated issue patches.

@TrendpilotAI

Copy link
Copy Markdown

I am testing an external backend pattern that puts the authoritative EventStore and disposable GraphStore in one transactional database. PR #78's append-before-project rule is the correct safe default for independent stores, but it exposes one compatibility boundary worth deciding explicitly.

Reproducible ordering problem

The released v1.10 order is:

apply_event -> GraphStore.put_* stages projection delta
EventStore.append -> commits event + staged delta + projection cursor

An external co-located adapter can therefore commit the event and its derived projection delta in one database transaction.

At this PR's current head, the order is:

EventStore.append -> no current delta exists yet
apply_event -> GraphStore.put_* stages the delta after append returns

With the same adapter, event N commits the empty or event N-1 buffer, the durable projection remains one event behind, and the final staged delta may never flush. I confirmed the order change directly at 221d9ecd8dcba510c4707c219b463a9a1404efc2; this is not an argument to restore project-before-append as the default.

Architecture question

Should the post-v1.11 extension surface reserve an optional atomic acceptance coordinator for co-located backends?

A possible shape is:

  1. canonicalize and validate the event;
  2. prepare its projection delta in an isolated, non-visible overlay;
  3. CAS-commit the event, delta, run head, and projection cursor in one backend transaction;
  4. publish the accepted in-memory state and offer observers only after commit.

Independent EventStore/GraphStore pairs would keep this PR's append-first behavior unchanged.

Invariants and gates

  • The event remains authoritative; the projection remains replayable and disposable.
  • Projection preparation, stale-writer rejection, or commit failure changes neither durable log nor visible projection.
  • Success advances event head and projection cursor together.
  • Observers see only committed accepted events.
  • Replay produces the same projection digest.
  • An ambiguous commit result is resolved by reading the canonical event ID and projection cursor: event present/projection behind recovers by replay; projection present without its event is a provider-integrity failure and fails closed.
  • A process failure after database commit but before local publication recovers by reload/replay rather than re-accepting a different head.
  • No distributed two-phase commit, external-sink atomicity, provider-specific schema, or product behavior enters core.

If this belongs in a separate architecture issue after #78 rather than this release, I will follow that route and wait for status: ready-for-pr before proposing code. The immediate goal is to make the compatibility boundary explicit so external adapters do not accidentally depend on call order again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment