CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 48s
CORRECTION 1: #314 reopened — recall_with_associations mode field defect CONFIRMED in current source and FIXED (mode: association_merged). CORRECTION 3: Gitea credential removed from remote URL (credential helper). TOKEN EXPOSED: yes (in diagnostic output). TOKEN ROTATED: not yet (requires Gitea web UI access). Remote URL now shows username only, no secret. CORRECTION 4: duplicate-release-commit defect ROOT CAUSE: convergence computed warranted bump from commits-since-tag without checking if master version already satisfies the bump. FIX: already-converged guard in version_convergence.py (master version > last tag → no commit). Repo-aware (args.repo passed to read_version/latest_tag). CORRECTION 6: lifecycle terminalization — execution exception now guarantees task terminalizes as failed with evidence (no immortal RUNNING). CORRECTION 7: route instruction task-scoped on RuneTask.route_instruction (NOT shared operator state). Read-only leading-verb override. Isolation tests prove no cross-contamination between interleaved tasks. PL1-01/02/03/05: pl1_records.py (route resolution, proof level, failure class, candidate status), metrics with honest NOT_YET_MEASURABLE semantics, route-instruction learning loop (PE-1789102658-0269 SEED). 1253 tests green; ratchet 352/80 (shrink); henxels clean.
54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
"""Capability negotiation: baseline parity + structured absence."""
|
|
|
|
from rune.orchestration.capability import (
|
|
BASELINE_CAPABILITIES,
|
|
CapabilityNegotiator,
|
|
NegotiatedCapability,
|
|
)
|
|
from rune.orchestration.records import SurfaceRecord
|
|
from rune.orchestration.surface import Capability
|
|
|
|
|
|
FULL = tuple(Capability(c) for c in sorted(BASELINE_CAPABILITIES))
|
|
|
|
|
|
def test_negotiate_full_surface_is_parity():
|
|
s = SurfaceRecord(surface_id="sfc_1", external_id="r1", kind="remote", capabilities=FULL)
|
|
neg = CapabilityNegotiator().negotiate(s)
|
|
assert len(neg) == len(BASELINE_CAPABILITIES)
|
|
assert all(n.available for n in neg)
|
|
assert CapabilityNegotiator().is_parity(neg)
|
|
|
|
|
|
def test_missing_capability_is_honest_absence_not_parity():
|
|
s = SurfaceRecord(
|
|
surface_id="sfc_1",
|
|
external_id="r1",
|
|
kind="tui",
|
|
capabilities=tuple((Capability("images"),)),
|
|
)
|
|
neg = CapabilityNegotiator().negotiate(s)
|
|
assert not CapabilityNegotiator().is_parity(neg)
|
|
missing = {n.name for n in neg if not n.available}
|
|
assert "cancellation" in missing and "tools" in missing
|
|
absent = next(n for n in neg if n.name == "cancellation")
|
|
assert absent.owner == "" and absent.reason == "not provided"
|
|
|
|
|
|
def test_structured_absence_is_visible_not_fake_parity():
|
|
s = SurfaceRecord(surface_id="sfc_1", external_id="r1", kind="voice", capabilities=FULL)
|
|
neg = CapabilityNegotiator().negotiate(s)
|
|
assert CapabilityNegotiator().is_parity(neg)
|
|
|
|
|
|
def test_negotiated_capability_roundtrips():
|
|
n = NegotiatedCapability(
|
|
name="images",
|
|
available=False,
|
|
owner="nick",
|
|
reason="voice has no screen",
|
|
expires_at="2026-10-01",
|
|
restoration_canary="canary_voice_images",
|
|
)
|
|
assert NegotiatedCapability.from_dict(n.to_dict()) == n
|