Restructure refimpl into go-lang and python subdirectories

Move Go reference implementation to refimpl/go-lang/ and add new
Python reference implementation in refimpl/python/. Update build.sh
with renamed draft and simplified tool paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 23:11:55 +01:00
parent ff795c72e6
commit bbf557e54b
52 changed files with 3972 additions and 341 deletions

97
refimpl/python/ect/dag.py Normal file
View File

@@ -0,0 +1,97 @@
"""DAG validation per Section 6."""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ect.types import Payload
from ect.validate import DEFAULT_MAX_PAR_LENGTH
DEFAULT_CLOCK_SKEW_TOLERANCE = 30
DEFAULT_MAX_ANCESTOR_LIMIT = 10000
class ECTStore(ABC):
"""Lookup of ECTs by task ID for DAG validation."""
@abstractmethod
def get_by_tid(self, tid: str) -> "Payload | None":
pass
@abstractmethod
def contains(self, tid: str, wid: str) -> bool:
pass
class DAGConfig:
def __init__(
self,
clock_skew_tolerance: int = DEFAULT_CLOCK_SKEW_TOLERANCE,
max_ancestor_limit: int = DEFAULT_MAX_ANCESTOR_LIMIT,
max_par_length: int = 0,
):
self.clock_skew_tolerance = clock_skew_tolerance or DEFAULT_CLOCK_SKEW_TOLERANCE
self.max_ancestor_limit = max_ancestor_limit or DEFAULT_MAX_ANCESTOR_LIMIT
self.max_par_length = max_par_length or 0
def default_dag_config() -> DAGConfig:
return DAGConfig()
def _has_cycle(
target_tid: str,
parent_ids: list[str],
store: ECTStore,
visited: set[str],
max_depth: int,
) -> bool:
if len(visited) >= max_depth:
return True
for parent_id in parent_ids:
if parent_id == target_tid:
return True
if parent_id in visited:
continue
visited.add(parent_id)
parent = store.get_by_tid(parent_id)
if parent is not None:
if _has_cycle(target_tid, parent.par, store, visited, max_depth):
return True
return False
def validate_dag(
payload: "Payload",
store: ECTStore,
cfg: DAGConfig,
) -> None:
"""Section 6.2: uniqueness (by jti), parent existence, temporal ordering, acyclicity, parent policy."""
if cfg.max_par_length > 0 and len(payload.par) > cfg.max_par_length:
raise ValueError("ect: par exceeds max length")
if store.contains(payload.jti, payload.wid or ""):
raise ValueError(f"ect: task ID (jti) already exists: {payload.jti}")
from ect.types import POL_DECISION_REJECTED, POL_DECISION_PENDING_HUMAN_REVIEW
for parent_id in payload.par:
parent = store.get_by_tid(parent_id)
if parent is None:
raise ValueError(f"ect: parent task not found: {parent_id}")
if parent.iat >= payload.iat + cfg.clock_skew_tolerance:
raise ValueError(f"ect: parent task not earlier than current: {parent_id}")
visited: set[str] = set()
if _has_cycle(payload.jti, payload.par, store, visited, cfg.max_ancestor_limit):
raise ValueError("ect: circular dependency or depth limit exceeded")
# Parent policy decision: only when parent has policy claims per spec
for parent_id in payload.par:
parent = store.get_by_tid(parent_id)
if parent and parent.has_policy_claims() and parent.pol_decision in (POL_DECISION_REJECTED, POL_DECISION_PENDING_HUMAN_REVIEW):
if not payload.compensation_required():
raise ValueError(
"ect: parent has non-approved pol_decision; current ECT must be compensation/remediation or have ext.compensation_required true"
)