Skip to content

v0.9 sub-issue #3: Stage 2 Resolve (Provider Registry + LifeCapabilityProvider + tier-aware resolution + bundled refusal) #122

@devin-ai-integration

Description

@devin-ai-integration

v0.9 Sub-issue #3 — Stage 2 Resolve

Part of v0.9 epic.

Implements the v0.8 Part B Stage 2 (Resolve): map each capability declared
in binding/runtime_binding.json::capabilities to a concrete
LifeCapabilityProvider via the Provider Registry, walking
engine_compatibility[] and applying tier-aware preference.

Spec ref

  • docs/LIFE_RUNTIME_STANDARD.md Part B §B.2 (Provider Registry)
  • docs/LIFE_RUNTIME_STANDARD.md Part B §B.2.1 (LifeCapabilityProvider)
  • docs/LIFE_RUNTIME_STANDARD.md Part B §B.3 (tier-aware resolution)
  • docs/LIFE_RUNTIME_STANDARD.md Part B §B.4 (sandbox classes)
  • docs/LIFE_BINDING_SPEC.md §5 (engine_compatibility / strict / version_range)

Scope

LifeCapabilityProvider abstract

runtime/resolve/provider_interface.py:

class LifeCapabilityProvider(abc.ABC):
    @abc.abstractmethod
    def capability_name(self) -> str: ...
    @abc.abstractmethod
    def provider_name(self) -> str: ...
    @abc.abstractmethod
    def provider_version(self) -> str: ...
    @abc.abstractmethod
    def sandbox_class(self) -> Literal["built_in", "user_installed", "bundled_in_life"]: ...

    @abc.abstractmethod
    def initialize(self, asset_paths: list[Path], params: dict, hard_constraints: dict) -> None: ...
    @abc.abstractmethod
    def teardown(self) -> None: ...
    @abc.abstractmethod
    def invoke(self, input: dict) -> dict: ...

(Implementations for built_in echo Provider land in sub-issue 7.
Implementations for user_installed IPC subprocess shim land in
sub-issue 4.)

Provider Registry

runtime/resolve/registry.py:

  • ProviderRegistry: in-memory, populated from a config file
    (~/.config/dlrs/providers.json or ${DLRS_PROVIDERS} env var).
    Built-in Providers are auto-registered at import time.
  • list_providers(capability) → ordered list (built-in first, then user-installed).
  • resolve(capability, engine_compatibility[]) → ProviderRef — implements
    the spec walk:
    1. For each entry in engine_compatibility[] in order:
      • find Providers whose capability_name() == capability and
        provider_name() matches the entry's engine.name
      • filter by version_range (semver-compatible match using
        packaging.specifiers.SpecifierSet)
      • if engine.strict == true, require exact (name, version) match
      • if any survive, return the highest version
    2. After exhausting engine_compatibility[]: fail-close with
      engine_resolution_exhausted.
  • metadata(ProviderRef) → ProviderMetadata returning at minimum
    (name, version, sandbox_class).

bundled_in_life defence in depth

Per §B.4 + §B.4.1, even though the binding schema (#111) statically rejects
engine_kind: bundled_in_life, the Provider Registry MUST also refuse at
resolve-time. Add an explicit check: any Provider whose
sandbox_class() == "bundled_in_life" is filtered out before resolution
runs. If after filtering no Provider remains, emit
assembly_aborted{stage: "resolve", reason: "bundled_in_life_refused"}.

Tier-aware preference (B.3)

The resolve walk above respects issuer-declared engine_compatibility[]
order. When two Providers tie (same engine.name, same satisfied
version_range), break the tie by tier band:

  • I–IV: prefer Providers with metadata.fidelity_class == "low" (a hint
    field in registry entries; absent → neutral).
  • V–VIII: no preference.
  • IX–XII: prefer metadata.fidelity_class == "high"; for capabilities
    permitted by hosted_api_preference.allowed == true, prefer
    metadata.deployment == "hosted" (hosted-API actual use still gated
    at Stage 4 via the AND-gate — Stage 2 only declares preference).

The package's tier.level is read from the tier block (life-format
v0.1.1, descriptor field). Absent → assume tier band V–VIII (neutral).

tier_floor honouring

Per binding spec §5.1: if a capability_binding.tier_floor is present
and the package's tier.level is below it, emit a warning audit event
tier_floor_below_warning{capability, tier_floor, tier_actual} and
continue (SHOULD-level, not MUST-level). Reasons listed in spec.

Audit events emitted

  • assembly_aborted{stage: "resolve", reason} — on any resolve failure
    (engine exhaustion, bundled refusal, etc.).
  • tier_floor_below_warning{capability, tier_floor, tier_actual}
    per offending capability.

(No provider_resolved event — that's emitted by Stage 3 as
capability_bound after Assemble finishes.)

CLI surface

lifectl run <pkg.life> after this PR: runs Stage 1 + Stage 2; on PASS
prints a resolution summary:

Stage 1 Verify  ✓  package_id=... lifecycle=active
Stage 2 Resolve ✓  3 capabilities resolved:
  • text_chat → echo-builtin@1.0.0 (built_in)
  • voice_synthesis → xtts-v2@2.1.0 (user_installed)
  • memory_recall → graphrag-local@0.4.0 (built_in)
Stage 3+ pending sub-issues 4-7

Tests

tools/test_runtime_resolve.py:

  1. Happy path: minimal-life-package + a registry containing a built-in
    echo Provider for text_chat → resolves cleanly.
  2. Engine_compat walk: binding declares [xtts-v2, builtin-tts];
    only builtin-tts registered → walks past xtts, returns builtin-tts.
  3. Strict version mismatch: binding declares xtts-v2 >=2.0.0 strict=true; registry has xtts-v2@1.5.0 → fail-close.
  4. All entries exhausted: empty registry → engine_resolution_exhausted.
  5. Bundled refusal: registry contains a fake Provider with
    sandbox_class == "bundled_in_life" → filtered + fail.
  6. Tier-floor warning: binding has tier_floor: VIII, package
    tier.level == VI → warning event emitted, resolve still succeeds.
  7. Tier preference tie-break IX: two equally valid providers, package
    tier IX, one with fidelity_class: high, one without → high wins.

Acceptance

  • LifeCapabilityProvider abstract published
  • ProviderRegistry implemented with all spec-listed operations
  • All 7 test cases pass
  • Existing tests unchanged
  • CI runtime-resolve job green

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions