Runtime Component Graph
August 14, 2026 ยท View on GitHub
AC-960 adds a TypeScript-first reactive graph for live runtime components. It builds on the lifecycle-owned scopes from AC-959 and keeps graph reconciliation inside the trusted host. It is not a dependency-injection container, service broker, or cross-process discovery protocol.
Manifest contract
A component manifest declares:
- a stable logical component
id; - an
instanceIdidentifying the concrete component/provider generation; - typed capability keys it
requires; - typed capability values it
provides; and - an activation callback that receives its lifecycle scope and typed provider lookup.
defineRuntimeCapability<T>(id) carries the value type through provider and
consumer code. The key's string id is the stable graph and diagnostic identity.
Only one component may provide a capability in a desired graph. Multiplexing,
load balancing, and service brokers are intentionally deferred.
Activation lookup is limited to capabilities listed in that component's
requires array. A component cannot consume an ambient provider and omit the
dependency edge. Component, instance, and capability identifiers are audit
fields and must never contain credentials or other secret material.
An instanceId must change whenever a provider's activation or provided value
changes. Reusing the same logical component id and instance id means the graph
may preserve the live instance. Distinct instance ids trigger consumer
recomposition even if their provided values compare equal.
Validation and waiting
Every requested graph is copied and validated before any live component is changed. Duplicate component ids, duplicate exclusive providers, malformed manifests, and dependency cycles reject the reconciliation without partial activation.
A missing requirement is different: the consumer remains inactive with a
missing_requirement diagnostic. If its configured provider failed or is
blocked, it waits with provider_inactive. Adding a usable provider on a later
reconciliation activates the provider first and then the waiting consumer.
Replacement order
Provider removal or replacement follows this sequence:
- Remove the affected provider capabilities from the available lookup and
emit
provider_unavailable. - Dispose affected consumers in reverse dependency order.
- Dispose their providers.
- Activate replacement providers in topological order.
- Reactivate only affected consumers against the new provider identity.
Unrelated active components retain their existing scope and do not restart. Component disposal retains AC-959's LIFO and at-most-once guarantees.
If provider cleanup fails, its capabilities remain blocked even though they are
unavailable. A replacement cannot activate until a trusted supervisor repairs
or verifies the external state and calls acknowledgeProviderCleanup. This is
fail-closed because activating a second exclusive provider while the first may
still have effects would violate the graph contract.
Concurrency and failure containment
Reconciliations are serialized in request order. A request may arrive while an async activation or disposer is running; its validated graph runs next, so the eventual active state converges to the latest request. AC-962 adds broader interleaving and fault-injection coverage around this guarantee.
Activation failure automatically unwinds the component scope, records only the
safe activation_failed reason, and leaves its consumers waiting. Unrelated
components continue to activate. A failure whose unwind also fails blocks that
component's provided capabilities for supervisor repair.
Diagnostics
snapshot() exposes the applied revision, transition state, component and
provider identities, dependency bindings, waiting/failure reason codes, and
blocked capabilities. It never includes capability values or thrown error
text. createRuntimeSessionComponentGraphEventSink records the same safe
identities and transitions as component_graph runtime-session events, which
the generic timeline and background-session normalizer can render.
The AC-961 control-plane activation supervisor now owns the transaction journal, active candidate pointer, promotion connection, rollback, and restart recovery. Python parity is deferred until this experimental contract stabilizes.