Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci-maintenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ jobs:
if (!(await cancellationScopeUnchanged())) return core.info('PR or source branch changed; stop force cancellation.');
await github.request('POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel', params);
let terminal = false;
for (let attempt = 0; attempt < 10; attempt++) {
// Cancellation is asynchronous: runners can take tens of
// seconds to settle after force-cancel is accepted.
const deadline = Date.now() + 60_000;
while (Date.now() < deadline) {
if (!(await cancellationScopeUnchanged())) return core.info('PR or source branch changed; stop cancellation readback.');
await new Promise(resolve => setTimeout(resolve, 1000));
if ((await github.rest.actions.getWorkflowRun(params)).data.status === 'completed') {
terminal = true;
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,14 @@ jobs:
tests/test_windows_atomic_writes.py
tests/test_windows_install.py

- name: Qualify private IO on native Windows
run: >-
python -m pytest -q --basetemp .t-private-io
tests/control_plane/test_windows_snapshot_privacy.py
tests/test_file_lock_windows_atomic.py
tests/control_plane/test_document_io.py::test_ordinary_windows_long_path_publish_preserves_bytes_and_create_only
tests/control_plane/test_todo_completion_validation.py::test_private_validation_store_is_owner_only_and_detects_tampering

- name: Check Windows collaboration lock sidecars
run: python -m pytest -q --basetemp .t tests/test_collaboration_sidecar_scan.py

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ export const researchDisplayLanguageScenario = {
assert.deepEqual(await judgments.locator('.research-assessment-verdict').allTextContents(),
['证据不足', '证据不足', '证据不足', '未建立', '有依据', '有依据', '有依据', '有依据']);
const firstJudgment = judgments.locator('.research-assessment').first();
await firstJudgment.locator(':scope > summary').focus(); await page.keyboard.press('Enter');
await page.waitForFunction(element => element.open, await firstJudgment.elementHandle());
await firstJudgment.locator(':scope > summary').press('Enter');
await page.waitForFunction(() => document.querySelectorAll('.research-effect-evidence .research-assessment')[0]?.open);
const peerJudgment = judgments.locator('.research-assessment').nth(1);
await peerJudgment.locator(':scope > summary').click();
await page.waitForFunction(element => element.open, await peerJudgment.elementHandle());
await page.waitForFunction(() => document.querySelectorAll('.research-effect-evidence .research-assessment')[1]?.open);
assert.equal(await judgments.locator('.research-assessment[open]').count(), 2, "Independent row expansion is preserved");
assert.equal(await judgments.locator('.research-assessment-question:visible').count(), 0, "Opening several rationales never repeats the shared question");
assert.equal(await firstJudgment.locator('.research-assessment-reason').innerText(), capture.analyses.at(-1).outputs.zh.assessments[0].reason);
Expand Down
9 changes: 5 additions & 4 deletions loopx/authority.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

from typing import Any, cast

import copy
import hashlib
import re
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

from .control_plane.projects.registry_codec import (
Expand Down Expand Up @@ -219,7 +220,7 @@ def render_authority_source_markdown(payload: dict[str, Any]) -> str:
if payload.get("error"):
lines.append(f"- error: {payload.get('error')}")
return "\n".join(lines)
entry = payload.get("entry") if isinstance(payload.get("entry"), dict) else {}
entry = cast(dict[str, Any], payload.get("entry")) if isinstance(payload.get("entry"), dict) else {}
lines.extend(
[
f"- registry: `{payload.get('registry')}`",
Expand Down Expand Up @@ -313,7 +314,7 @@ def render_doc_registry_authority_import_markdown(payload: dict[str, Any]) -> st
if payload.get("error"):
lines.append(f"- error: {payload.get('error')}")
return "\n".join(lines)
entry = payload.get("entry") if isinstance(payload.get("entry"), dict) else {}
entry = cast(dict[str, Any], payload.get("entry")) if isinstance(payload.get("entry"), dict) else {}
lines.extend(
[
f"- source_ref_redacted: `{entry.get('source_ref_redacted')}`",
Expand Down Expand Up @@ -536,7 +537,7 @@ def import_doc_registry_authority(
)
entry["schema_version"] = DOC_REGISTRY_AUTHORITY_IMPORT_VERSION
default_entries = [str(item) for item in contract.get("default_entry_docs") or []]
topic_authority = contract.get("topic_authority") if isinstance(contract.get("topic_authority"), dict) else {}
topic_authority = cast(dict[str, Any], contract.get("topic_authority")) if isinstance(contract.get("topic_authority"), dict) else {}
imported_topics: dict[str, str] = {}
if import_topic_prefix:
prefix = public_safe_optional("import_topic_prefix", import_topic_prefix) or ""
Expand Down
7 changes: 4 additions & 3 deletions loopx/capabilities/change_quality/policy.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
from __future__ import annotations

from typing import Any
from typing import Any, cast



CHANGE_QUALITY_POLICY_SCHEMA_VERSION = "change_quality_qualification_policy_v0"


def change_quality_goal_policy(goal: dict[str, Any]) -> dict[str, Any]:
control_plane = (
goal.get("control_plane")
cast(dict[str, Any], goal.get("control_plane"))
if isinstance(goal.get("control_plane"), dict)
else {}
)
raw = (
control_plane.get("change_quality_qualification")
cast(dict[str, Any], control_plane.get("change_quality_qualification"))
if isinstance(control_plane.get("change_quality_qualification"), dict)
else {}
)
Expand Down
26 changes: 17 additions & 9 deletions loopx/capabilities/context_providers/openviking.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Any, TypedDict, cast

import json
import os
import re
Expand All @@ -8,7 +10,6 @@
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from ...control_plane.runtime.public_safety import public_safe_compact_text
from .base import (
Expand All @@ -31,6 +32,13 @@
PEER_SCOPE_MINIMUM_VERSION = "0.4.18"
PEER_SCOPE_MINIMUM_SERVER_VERSION = "0.4.19"

class _ScopeMetadata(TypedDict):
visibility: str
target_scope_kind: str
write_strategy: str
actor_binding_verified: bool


CommandRunner = Callable[..., subprocess.CompletedProcess[str]]


Expand Down Expand Up @@ -133,7 +141,7 @@ def classify_openviking_scope(scope_ref: str) -> OpenVikingScope:
)


def _compact(value: Any, *, limit: int) -> str:
def _compact(value: Any, *, limit: int) -> str | None:
return public_safe_compact_text(value, limit=limit)


Expand All @@ -157,11 +165,11 @@ def _extract_json(text: str) -> Any:
raise ValueError("provider output did not contain JSON")


def _version_tuple(value: str) -> tuple[int, int, int]:
def _version_tuple(value: Any) -> tuple[int, int, int]:
match = re.search(r"(\d+)\.(\d+)\.(\d+)", value)
if not match:
raise ValueError("provider version is not semantic")
return tuple(int(part) for part in match.groups())
return cast(tuple[int, int, int], tuple(int(part) for part in match.groups()))


def _mapping_candidates(value: Any) -> list[Mapping[str, Any]]:
Expand Down Expand Up @@ -292,7 +300,7 @@ def _validated_scope(self, scope_ref: str) -> OpenVikingScope:
return scope

@staticmethod
def _scope_metadata(scopes: Sequence[OpenVikingScope]) -> dict[str, object]:
def _scope_metadata(scopes: Sequence[OpenVikingScope]) -> _ScopeMetadata:
visibilities = {scope.visibility for scope in scopes}
kinds = {scope.scope_kind for scope in scopes}
strategies = {scope.write_strategy for scope in scopes}
Expand Down Expand Up @@ -410,10 +418,10 @@ def _preflight(
def retrieve(
self,
*,
namespace: str,
namespace: str | None,
scope_ref: str,
query: str,
query_summary: str,
query: str | None,
query_summary: str | None,
max_results: int,
timeout_seconds: float,
observed_at: str,
Expand Down Expand Up @@ -745,7 +753,7 @@ def _reconcile_uncertain_sync_write(
def sync(
self,
*,
namespace: str,
namespace: str | None,
resources: Sequence[tuple[str, str]],
timeout_seconds: float,
observed_at: str,
Expand Down
2 changes: 1 addition & 1 deletion loopx/capabilities/decision_context/outcome_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def _retrieval_counts(
rejected_record_count: int,
) -> tuple[int, int, str]:
if packet is None:
result_count = promoted_count + rejected_record_count
result_count: Any = promoted_count + rejected_record_count
return result_count, rejected_record_count, "evidence_records"
if not isinstance(packet, Mapping):
raise TypeError("retrieval_receipt must be an object")
Expand Down
38 changes: 19 additions & 19 deletions loopx/capabilities/decision_context/packets.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _normalize_value(value: Any, *, field: str, kind: str) -> Any:


def _normalize_record(
value: Mapping[str, Any],
value: object,
*,
field: str,
spec: Mapping[str, str],
Expand Down Expand Up @@ -274,9 +274,9 @@ def _capability_contract(*, packet_role: str) -> dict[str, Any]:

def build_decision_evidence_packet(
*,
goal_id: str,
decision_id: str,
observed_at: str,
goal_id: object,
decision_id: object,
observed_at: object,
changed_facts: Sequence[Mapping[str, Any]] | None = None,
recalled_claims: Sequence[Mapping[str, Any]] | None = None,
stale_or_rejected_claims: Sequence[Mapping[str, Any]] | None = None,
Expand Down Expand Up @@ -468,14 +468,14 @@ def build_decision_proposal(

def build_decision_review_receipt(
*,
goal_id: str,
decision_id: str,
evidence_packet_ref: str,
recorded_at: str,
disposition: str,
actor_ref: str,
reason_code: str,
summary: str,
goal_id: object,
decision_id: object,
evidence_packet_ref: object,
recorded_at: object,
disposition: object,
actor_ref: object,
reason_code: object,
summary: object,
proposal_packet_ref: str | None = None,
gate_todo_id: str | None = None,
source_event_id: str | None = None,
Expand Down Expand Up @@ -586,16 +586,16 @@ def build_decision_review_receipt(

def build_decision_outcome_receipt(
*,
goal_id: str,
decision_id: str,
proposal_packet_ref: str,
recorded_at: str,
verification_status: str,
accepted_decision: Mapping[str, Any],
goal_id: object,
decision_id: object,
proposal_packet_ref: object,
recorded_at: object,
verification_status: object,
accepted_decision: object,
resulting_transitions: Sequence[Mapping[str, Any]] | None = None,
observed_outcomes: Sequence[Mapping[str, Any]] | None = None,
invalidated_assumptions: Sequence[Mapping[str, Any]] | None = None,
review_at: str,
review_at: object,
) -> dict[str, Any]:
"""Build an append-only receipt suitable for existing event/run history."""

Expand Down
18 changes: 10 additions & 8 deletions loopx/capabilities/explore/composition_frontier.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from __future__ import annotations

from typing import Any, cast

import hashlib
import json
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any

from ...control_plane.quota.goal_boundary import registry_goal_by_id
from ...control_plane.todos.contract import (
Expand Down Expand Up @@ -152,12 +153,13 @@ def build_explore_composition_frontier(
inputs = [nodes_by_id.get(input_id) for input_id in sorted(input_ids)]
if len(inputs) < 2 or any(not isinstance(node, Mapping) for node in inputs):
continue
if any(str(node.get("status") or "") not in _CLOSED_INPUT_STATUSES for node in inputs):
validated_inputs = cast(list[Mapping[str, Any]], inputs)
if any(str(node.get("status") or "") not in _CLOSED_INPUT_STATUSES for node in validated_inputs):
continue
if any(not _evidence_backed(node) for node in inputs):
if any(not _evidence_backed(node) for node in validated_inputs):
continue
scheduled = experiment_id in bound_refs
input_refs = [str(node.get("node_id") or "") for node in inputs]
input_refs = [str(node.get("node_id") or "") for node in validated_inputs]
gap_identity = {
"goal_id": goal_id,
"experiment_node_ref": experiment_id,
Expand Down Expand Up @@ -239,7 +241,7 @@ def project_live_explore_composition_frontier(
goal = resolve_explore_goal(goal, runtime_root)
orchestration = compact_orchestration_policy(goal.get("spawn_policy"))
harness = (
orchestration.get("explore_harness")
cast(Mapping[str, Any], orchestration.get("explore_harness"))
if isinstance(orchestration.get("explore_harness"), Mapping)
else {}
)
Expand Down Expand Up @@ -283,13 +285,13 @@ def project_live_explore_composition_frontier(
{},
)
project_asset = (
item.get("project_asset")
cast(Mapping[str, Any], item.get("project_asset"))
if isinstance(item.get("project_asset"), Mapping)
else {}
)
item_todos = item.get("agent_todos") if isinstance(item.get("agent_todos"), Mapping) else {}
item_todos = cast(Mapping[str, Any], item.get("agent_todos")) if isinstance(item.get("agent_todos"), Mapping) else {}
asset_todos = (
project_asset.get("agent_todos")
cast(Mapping[str, Any], project_asset.get("agent_todos"))
if isinstance(project_asset.get("agent_todos"), Mapping)
else {}
)
Expand Down
5 changes: 3 additions & 2 deletions loopx/capabilities/explore/execution_observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from functools import lru_cache
import json
from typing import Any, cast

from ...control_plane.effect_runtime import effect_runtime_result
from ...control_plane.runtime.public_safety import validate_public_safe_value
Expand All @@ -14,8 +15,8 @@ def _normalize(encoded: str) -> str:
return json.dumps(result, sort_keys=True, allow_nan=False)


def normalize_execution_observation(value):
def normalize_execution_observation(value: Any) -> dict[str, Any]:
encoded = json.dumps(value, sort_keys=True, ensure_ascii=False, allow_nan=False)
validate_public_safe_value(value, path="execution_observation")
# Do not let readers mutate the cached canonical source.
return json.loads(_normalize(encoded))
return cast(dict[str, Any], json.loads(_normalize(encoded)))
7 changes: 4 additions & 3 deletions loopx/capabilities/explore/harness_gate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from typing import Any, Mapping
from typing import Any, Mapping, cast


from ...orchestration import compact_orchestration_policy

Expand Down Expand Up @@ -58,9 +59,9 @@ def resolve_explore_harness_gate(
"""

boundary_provided = isinstance(orchestration, Mapping) and bool(orchestration)
compact = compact_orchestration_policy(dict(orchestration) if boundary_provided else None)
compact = compact_orchestration_policy(dict(cast(Mapping[str, Any], orchestration)) if boundary_provided else None)
harness_policy = (
compact.get("explore_harness") if isinstance(compact.get("explore_harness"), dict) else {}
cast(dict[str, Any], compact.get("explore_harness")) if isinstance(compact.get("explore_harness"), dict) else {}
)
enabled = bool(harness_policy.get("enabled"))
spawn_allowed = bool(compact.get("spawn_allowed"))
Expand Down
7 changes: 4 additions & 3 deletions loopx/capabilities/explore/machine_defaults.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Machine preference IO and adapters to Explore's existing typed mode owner."""
from __future__ import annotations

from typing import Any, cast

from collections.abc import Mapping
from copy import deepcopy
from functools import lru_cache
import json
from pathlib import Path
from typing import Any

from ...control_plane.effect_runtime import effect_runtime_result
from ...explore_graph import plan_explore_configuration
Expand Down Expand Up @@ -47,7 +48,7 @@ def explore_machine_configuration_namespace() -> MachineConfigurationNamespace:

@lru_cache(maxsize=128)
def _resolve(encoded: str) -> dict[str, Any]:
return effect_runtime_result("explore.configuration.goal", json.loads(encoded))
return cast(dict[str, Any], effect_runtime_result("explore.configuration.goal", json.loads(encoded)))


def resolve_explore_configuration(
Expand All @@ -64,7 +65,7 @@ def resolve_explore_configuration(


def configuration_summary(goal: Mapping[str, Any]) -> dict[str, Any] | None:
return resolve_explore_configuration(goal)["goal_override"]
return cast(dict[str, Any] | None, resolve_explore_configuration(goal)["goal_override"])


def apply_explore_machine_default(
Expand Down
Loading
Loading