Skip to content

feat(storage): centralize system-record runtime authority - #2140

Merged
Jurij89 merged 6 commits into
integration/2052-system-record-syncfrom
feat/2052-system-record-runtime
Aug 8, 2026
Merged

feat(storage): centralize system-record runtime authority#2140
Jurij89 merged 6 commits into
integration/2052-system-record-syncfrom
feat/2052-system-record-runtime

Conversation

@Jurij89

@Jurij89 Jurij89 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace each managed SPARQL adapter's throwaway verified-replacement registry with a dedicated runtime composition keyed by its authentic, endpoint-bound Oxigraph ownership lease.
  • Enforce one process-wide 64 MiB runtime accountant and one nonqueued 12 MiB atomic reservation across all managed adapters through the same exact reservation-gate implementation; recovery retains that reservation until physical settlement.
  • Preserve the supported public activation-descriptor contract while rejecting descriptor and kinds-array proxies before any traps run. Lifecycle-minted opaque activation remains deferred until its supported caller ships.
  • Model current durable epoch and applied tuple epoch explicitly, deriving rematerialization from that single epoch relation. An exact prior-epoch tuple is atomically rematerialized into the current epoch, while extra epoch rows, future epochs, split state/receipt epochs, and receipt/state identity mismatches fail closed.
  • Keep the lane default-unused: this PR registers no producer, provider, requester, protocol, timer, or queue and performs no default-path store work.

This is the runtime-authority portion of Stack B4. It does not enable signed system-record sync. Same-version fork/root-collision quarantine and the live maximum-size/abort/lost-response activation evidence remain explicit gates before Stack C.

Related

Diagrams

Runtime authority and reservation ownership

Before:

sequenceDiagram
    participant C as Public caller
    participant A as Managed adapter A
    participant B as Managed adapter B
    participant RA as Local registry A
    participant RB as Local registry B
    C->>A: Open activation descriptor
    A->>RA: Create isolated proof registry
    C->>B: Open activation descriptor
    B->>RB: Create isolated proof registry
    Note over RA: Own reservation state
    Note over RB: Own reservation state
Loading

After:

sequenceDiagram
    participant C as Public caller
    participant A as Managed adapter A
    participant B as Managed adapter B
    participant R as Lease runtime
    participant G as Process gate
    C->>A: Open activation descriptor
    A->>R: Resolve proof runtime by lease
    B->>R: Resolve proof runtime by lease
    R->>G: Reserve one 12 MiB atomic lease
    R->>G: Retain during recovery
    R->>G: Release after settlement
    Note over C,A: Activation contract is unchanged
Loading

Durable epoch recovery

Before:

sequenceDiagram
    participant Runtime
    participant Decoder
    participant Deriver
    participant Store
    Runtime->>Decoder: Read prior tuple and current epoch
    Decoder-->>Runtime: Reject epoch binding
    Note over Deriver: No bounded rematerialization path
Loading

After:

sequenceDiagram
    participant Runtime
    participant Decoder
    participant Deriver
    participant Store
    Runtime->>Decoder: Read prior tuple and current epoch
    Decoder-->>Deriver: Authentic prior-epoch snapshot
    Deriver->>Store: One CAS replacement at epoch N
    Store-->>Runtime: Current-epoch receipt and state revision
Loading

Files changed

File What
packages/storage/src/system-record-reservation-gate-v1-internal.ts Implements the one exact nonqueued reservation state machine used by process and isolated runtimes.
packages/storage/src/system-record-runtime-v1-internal.ts Owns authentic lease-to-runtime resolution and instantiates the process-wide reservation gate.
packages/storage/src/system-record-verified-replacement-v1-internal.ts Keeps proof issuance/consumption focused and accepts an injected reservation gate from runtime composition.
packages/storage/src/adapters/sparql-http.ts Uses the ownership-lease runtime instead of creating an adapter-local registry.
packages/storage/src/system-record-materializer-v1.ts Preserves the public descriptor contract and rejects proxy-backed activation input before reflective access.
packages/storage/src/system-record-state-snapshot-v1-internal.ts Models durable/applied epochs explicitly and strengthens exact tuple, epoch, and receipt/state bindings.
packages/storage/src/system-record-next-state-v1-internal.ts Routes an equal head from a prior epoch through bounded atomic rematerialization instead of the no-write path.
packages/storage/test/system-record-*.test.ts Covers runtime sharing, liveness, recovery ownership, proxy rejection, prior/future/split epochs, and exact reserved rows.

Test plan

  • pnpm --filter @origintrail-official/dkg-storage build
  • pnpm exec vitest run test/system-record-state-snapshot-v1.test.ts test/system-record-next-state-v1.test.ts (22 passed)
  • pnpm exec vitest run test/blazegraph.unit.test.ts test/oxigraph-worker-resilience.test.ts test/oxigraph-worker-respawn.test.ts --maxWorkers=1 (68 passed)
  • pnpm exec vitest run --maxWorkers=1 from packages/storage (808 passed, 26 integration-only skipped)
  • pnpm -r --filter @origintrail-official/dkg... --filter '!@origintrail-official/dkg-evm-module' run build
  • pnpm typecheck:live:system-record-managed-ownership
  • pnpm test:live:system-record-managed-ownership (PASS: 27 checks, 3 predecessor entries)
  • git diff --check
  • Confirm the outbound diff contains no untracked files, ignored artifacts, generated output, or secret markers.
  • Loaded maximum-size/abort/lost-response benchmarking remains an activation gate for the later behavior-enabling stack; this PR has no producer/provider/requester.

Comment thread packages/storage/src/system-record-state-snapshot-v1-internal.ts Outdated
Comment thread packages/storage/src/system-record-materializer-v1.ts Outdated
Comment thread packages/storage/src/system-record-materializer-v1.ts Outdated
Comment thread packages/storage/src/system-record-state-snapshot-v1-internal.ts
Comment thread packages/storage/src/system-record-lane-activation-v1-internal.ts Outdated
Comment thread packages/storage/src/system-record-runtime-v1-internal.ts Outdated
: Object.freeze([]) as OwnedSubjectTableObjectV1;

if (authority.equalHead) {
if (authority.equalHead && !(snapshot.state === 'present'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Model rematerialization before the equal-head branch instead of negating it at the call site

What's wrong
The change threads rematerialization through the already-applied path as a special-case exception, so equalHead no longer means what its name suggests unless the reader also knows about the snapshot epoch helper. This spreads one state-machine decision across two modules and makes future changes to the equal-head path easier to get wrong or duplicate.

Example
For a present snapshot from an older applied tuple epoch, classifyAuthorityAdvance still returns { outcome: 'advance', equalHead: true }; the caller then has to reinterpret that as not actually reusable via !(snapshot.state === 'present' && requiresSystemRecordSnapshotRematerializationV1(snapshot)).

Suggested direction
Fold epoch freshness into classifyAuthorityAdvance, return a richer outcome such as already-applied versus advance, or make the snapshot model distinguish reusable present state from present-but-needs-rematerialization. That would let this call site stay direct and remove the double-negative special case.

For Agents
Refactor around classifyAuthorityAdvance and the present-snapshot epoch model. Preserve behavior for equal current-epoch heads and prior-epoch rematerialization, but make the classifier or snapshot type express whether the persisted tuple is reusable. Keep the existing rematerialization test proving a prior-epoch equal head advances to a fresh tuple.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Fold rematerialization into the authority classification instead of special-casing equal heads

What's wrong
The new condition bolts an epoch-rematerialization exception onto an already busy control path. Readers now need to understand that equalHead does not mean “already applied”; it means “same head, unless the snapshot tuple is from an older durable epoch.” That weakens the abstraction boundary between snapshot freshness and authority advancement and makes future tuple states likely to add more compound exceptions here.

Example
For a present snapshot with the same head but appliedTupleEpoch !== materializationEpoch, classifyAuthorityAdvance reports equalHead: true; line 235 then says this equal head must not take the already-applied path because it needs rematerialization. That behavior may be right, but the model now says both “equal head” and “not reusable” in separate places.

Suggested direction
Have the classifier or snapshot model expose the actual decision the caller needs, such as equalCurrentHead, rematerializeEqualHead, or canReusePersistedMaterialization, instead of returning equalHead and then negating it with a separate epoch freshness check at the call site.

For Agents
Look at classifyAuthorityAdvance, requiresSystemRecordSnapshotRematerializationV1, and the already-applied branch in deriveSystemRecordActiveReplacementV1. Preserve the prior-epoch rematerialization behavior, but move the freshness decision into the authority/snapshot classification so the main derivation branches on a direct state like alreadyApplied vs rematerializeEqualHead. Keep the existing prior-epoch next-state test passing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated as a maintainability refinement, not a correctness blocker for this default-off runtime foundation. Deferred explicitly to #2155, which now requires the authority decision to expose a direct reusable/rematerialize outcome before activation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Make equal-head rematerialization an explicit state, not a hidden exception

What's wrong
This adds a special-case conditional into the busiest part of the derivation function. The reader has to know that equalHead sometimes means already-applied and sometimes means rematerialize, depending on epoch state owned by a different module. That is exactly the kind of branching growth that will make future transition cases harder to place correctly.

Example
Equal head at the current epoch enters the already-applied branch; equal head from a prior tuple epoch falls through into the normal advance/rematerialization path. That distinction is real, but it is hidden in a compound condition instead of being named by the authority/snapshot classification.

Suggested direction
Reframe the branch so the top-level flow dispatches on a named derivation state rather than equalHead && !(present && requiresRematerialization(...)).

For Agents
In system-record-next-state-v1-internal.ts, move the prior-epoch equal-head classification into a named model: for example have classifyAuthorityAdvance return advanceKind: 'new-head' | 'rematerialize-equal-head' | 'already-applied-candidate', or compute a clearly named shouldRematerializeEqualHead before branching. Preserve the current already-applied and rematerialization behavior, and keep the existing tests for equal-head prior epoch/current epoch passing.

} from '@origintrail-official/dkg-core/system-record-v1';

export interface SystemRecordRuntimeReservationGateV1 {
acquire(owner: object, bytes: number): void;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Make the reservation gate own the lease token instead of accepting release metadata

What's wrong
The new gate is supposed to encapsulate runtime accounting, but its interface pushes the exact release identity and byte count back onto the caller. That keeps wrong-owner and partial-release states in the contract, then compensates with runtime checks. A smaller, more maintainable abstraction would make those combinations impossible and remove bookkeeping from the registry caller.

Example
Current shape: the registry manufactures an owner object, calls reservationGate.acquire(reservation.identity, reservation.bytes), then must later repeat the exact pair in reservationGate.release(reservation.identity, reservation.bytes). The new tests explicitly exercise wrong-owner and partial-release attempts, which is a sign the abstraction is exposing invalid states instead of making them unrepresentable.

Suggested direction
Change the gate API from acquire(owner, bytes) / release(owner, bytes) to an opaque lease/token model, for example const token = gate.acquire(bytes); gate.release(token). The registry can still expose a separate opaque reservationIdentity in facts if needed, but the accountant should not require callers to reconstruct its internal release key.

For Agents
Look at system-record-reservation-gate-v1-internal.ts and the reservation calls in system-record-verified-replacement-v1-internal.ts. Preserve the single nonqueued process-wide/accounted-byte behavior, but have acquire return an opaque reservation/token that captures owner/bytes internally, and release by that token or by a token method. Existing reservation lifecycle tests should still prove exact-once release and cross-runtime exclusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated as a worthwhile API hardening refactor, not a behavioral blocker for the default-off foundation. Deferred explicitly to #2155, which now requires an opaque gate-owned reservation token while preserving exact-once release and process-wide exclusion.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Return an opaque reservation token instead of exposing owner/byte bookkeeping

What's wrong
The new abstraction is too thin: it extracts accounting state into a separate module but leaves the validity protocol spread across both sides. That makes the boundary more fragile than the original local state because the type system cannot express “release exactly the reservation that was acquired.”

Example
gate.acquire(firstOwner, bytes); gate.release(firstOwner, bytes - 1) is representable through the public internal interface and only fails dynamically. The caller should not need to remember the exact owner/byte pair to release a reservation it just acquired.

Suggested direction
Let the gate own its reservation record and expose a token-based API, or collapse the gate back into the registry if there is only one meaningful caller. Either option removes the primitive protocol between modules.

For Agents
In system-record-reservation-gate-v1-internal.ts and system-record-verified-replacement-v1-internal.ts, make acquire(bytes) return an opaque/branded reservation capability that the gate owns, and release by that token. Keep the one-live-reservation and max-accounted-bytes behavior, but remove the raw owner/bytes round trip from the registry/gate boundary.

// the storage boundary. Every adapter for this lease receives the same
// registry, and all authentic leases share one process-wide accountant.
// The issuer remains outside the store and has no production caller in
// this default-unused stack; the later lifecycle verifier captures it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Issue: Adapter wiring is not verified with a lease-issued proof

What's wrong
The PR's key storage-boundary behavior is that the adapter receives the same lease-owned runtime consumer that the verifier issuer will use. Current tests exercise the runtime directly and only check that the adapter rejects a forged object, which would also pass if the adapter were wired to the wrong private registry. That leaves the changed integration contract unverified.

Example
A regression that accidentally changed the adapter back to a fresh private registry would still reject Object.freeze({}) and the direct runtime tests would still pass, but a handle issued by resolveOwnedSystemRecordRuntimeV1(ownership.lease).issuer would fail through session.applyVerified(handle) instead of being consumed by the adapter's atomic executor.

Suggested direction
Cover the production composition, not only the resolver in isolation: the test should prove the store consumes handles from the same lease-bound runtime it resolves here.

Confidence note
I found direct tests for the lease runtime and for the atomic executor, plus an adapter test that only sends a forged object. I did not find an adapter test that sends a valid handle issued from the lease-bound runtime.

For Agents
Add an adapter-level regression test around SparqlHttpStore.getSystemRecordLaneControllerV1: create a managed lease, resolve its runtime, issue a valid active replacement handle with the same lifecycle binding, pass it to an open session's applyVerified, and assert the path reaches the atomic executor behavior rather than returning capability-lost for an unrecognized registry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated as a missing integration-evidence case rather than an identified runtime defect. Added the exact lease-issued-handle-through-adapter regression to #2154; the current direct runtime, adapter rejection, atomic executor, and live ownership gates remain green.

@Jurij89
Jurij89 changed the base branch from feat/2052-system-record-atomic-apply to integration/2052-system-record-sync August 8, 2026 02:04
@Jurij89
Jurij89 merged commit d2b4b77 into integration/2052-system-record-sync Aug 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants