feat(python): durable transaction subsystem and Kiro IDE 1.0 adapter

Land the crash-recoverable managed-file transaction engine (journal,
staging, commit, restart recovery, opaque snapshots, private artifacts,
Windows DACL hardening) and the Kiro IDE 1.0 workspace hook plugin with
legacy migration.

Repair five defects that kept the suite red (426 failures to zero):

- Anchor the transaction state directory at ~/.elydora/transactions
  instead of the shared system temp directory. The recovery journal now
  shares fate with the runtimes it repairs, survives temp cleaners, and
  isolates naturally under per-test fake homes.
- Skip pending-transaction recovery when no state directory exists so
  rejected installs never create runtime directories.
- Create the runtime root's parent before hardening it, matching the Go
  SDK's expand behavior for fresh homes.
- Stop using file timestamps as identity evidence: Windows exposes
  mtime/ctime with delayed cross-handle visibility. Reads now prove
  stability through a verification re-read; snapshots compare device,
  inode, size, and SHA-256.
- Stop comparing synthetic Windows permission bits across stat
  boundaries: path stats derive execute bits from file extensions while
  handle stats do not, which falsely flagged every managed .cmd wrapper.
  Snapshots store raw modes for restoration and compare portable bits.

Reservation cleanup now verifies content before discarding so a
concurrent writer's file at a rollback path is preserved, and the
install-failure tests assert the precise contract: no agent runtime
residue, with the durable state directory allowed to remain.

Gates: pytest 807 passed, mypy clean, pip check clean, wheel builds.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-21 18:53:06 -04:00
1 parent dce7d1288a
commit e37418c489
53 files changed
+11855 -531

No files matched your search

+492
View File
@@ -0,0 +1,492 @@
"""Kiro IDE workspace discovery and managed runtime inspection."""
from __future__ import annotations
import base64
from dataclasses import dataclass
import os
from typing import Any, List, Optional
import urllib.parse
from elydora._runtime_paths import resolve_agent_directory, runtime_root
from ._managed_files import (
DirectorySnapshot,
FileSnapshot,
MAX_CONFIG_BYTES,
MAX_SECRET_BYTES,
physical_directory_exists,
physical_file_exists,
read_physical_directory,
read_physical_file,
)
from ._strict_json import JsonObject, parse_json_object
from .guard_template import generate_guard_script
from .hook_template import generate_hook_script
from .kiroide_command import same_kiroide_agent_id, same_kiroide_path
from .kiroide_contract import (
AGENT_KEY,
CONFIG_FILE,
LEGACY_CONFIG_FILE,
KiroIdeDocument,
KiroIdeRuntimeContract,
create_kiroide_document,
legacy_kiroide_runtime_contract,
parse_kiroide_document,
)
@dataclass(frozen=True)
class KiroIdePaths:
workspace_root: str
kiro_directory: str
hooks_directory: str
config_path: str
legacy_config_path: str
@dataclass(frozen=True)
class LegacyKiroIdeDocument:
exists: bool
file_path: str
raw: Optional[str] = None
snapshot: Optional[FileSnapshot] = None
contract: Optional[KiroIdeRuntimeContract] = None
@dataclass(frozen=True)
class KiroIdeSources:
paths: KiroIdePaths
directories: KiroIdeDirectoryState
document: KiroIdeDocument
legacy: LegacyKiroIdeDocument
@dataclass(frozen=True)
class KiroIdeDirectoryState:
workspace: DirectorySnapshot
kiro: Optional[DirectorySnapshot]
hooks: Optional[DirectorySnapshot]
def resolve_kiroide_paths(
workspace_root: Optional[str] = None,
home_directory: Optional[str] = None,
) -> KiroIdePaths:
workspace = os.path.abspath(workspace_root or os.getcwd())
home = home_directory or os.path.expanduser("~")
kiro_directory = os.path.join(workspace, ".kiro")
hooks_directory = os.path.join(kiro_directory, "hooks")
return KiroIdePaths(
workspace,
kiro_directory,
hooks_directory,
os.path.join(hooks_directory, CONFIG_FILE),
os.path.join(home, ".kiro", "hooks", LEGACY_CONFIG_FILE),
)
def _inspect_workspace(paths: KiroIdePaths) -> KiroIdeDirectoryState:
workspace = read_physical_directory(
paths.workspace_root, "Kiro IDE workspace"
)
if workspace is None:
raise OSError(f"Kiro IDE workspace is missing: {paths.workspace_root}")
kiro = read_physical_directory(
paths.kiro_directory, "Kiro IDE configuration directory"
)
hooks = read_physical_directory(
paths.hooks_directory, "Kiro IDE hooks directory"
)
return KiroIdeDirectoryState(workspace, kiro, hooks)
def require_kiroide_directory_state(
paths: KiroIdePaths,
expected: KiroIdeDirectoryState,
operation: str,
) -> None:
current = _inspect_workspace(paths)
for label, path, before, after in (
(
"Kiro IDE workspace",
paths.workspace_root,
expected.workspace,
current.workspace,
),
(
"Kiro IDE configuration directory",
paths.kiro_directory,
expected.kiro,
current.kiro,
),
(
"Kiro IDE hooks directory",
paths.hooks_directory,
expected.hooks,
current.hooks,
),
):
if before != after:
raise OSError(f"{label} changed during {operation}: {path}")
def _read_document(paths: KiroIdePaths) -> KiroIdeDocument:
snapshot = read_physical_file(paths.config_path, "Kiro IDE hooks")
if snapshot is None:
return create_kiroide_document(paths.config_path)
return parse_kiroide_document(
paths.config_path,
snapshot.contents,
snapshot,
)
def _read_legacy(paths: KiroIdePaths) -> LegacyKiroIdeDocument:
snapshot = read_physical_file(paths.legacy_config_path, "legacy Kiro IDE hook")
if snapshot is None:
return LegacyKiroIdeDocument(False, paths.legacy_config_path)
return LegacyKiroIdeDocument(
True,
paths.legacy_config_path,
snapshot.contents,
snapshot,
legacy_kiroide_runtime_contract(
snapshot.contents, paths.legacy_config_path
),
)
def read_kiroide_sources() -> KiroIdeSources:
paths = resolve_kiroide_paths()
directories = _inspect_workspace(paths)
document = _read_document(paths)
legacy = _read_legacy(paths)
require_kiroide_directory_state(
paths, directories, "Kiro IDE source discovery"
)
return KiroIdeSources(paths, directories, document, legacy)
def require_physical_legacy_directory(legacy: LegacyKiroIdeDocument) -> None:
if not legacy.exists:
return
hooks_directory = os.path.dirname(legacy.file_path)
kiro_directory = os.path.dirname(hooks_directory)
if not physical_directory_exists(
kiro_directory, "legacy Kiro IDE configuration directory"
):
raise OSError(
f"Legacy Kiro IDE configuration directory is missing: {kiro_directory}"
)
if not physical_directory_exists(
hooks_directory, "legacy Kiro IDE hooks directory"
):
raise OSError(
f"Legacy Kiro IDE hooks directory is missing: {hooks_directory}"
)
def validate_api_origin(value: str, label: str = "base_url") -> None:
try:
parsed = urllib.parse.urlsplit(value)
hostname = parsed.hostname
parsed.port
except ValueError as error:
raise ValueError(f"{label} must be an absolute HTTP or HTTPS URL") from error
invalid_character = "\\" in value or any(
character.isspace() or ord(character) < 32 for character in value
)
if (
parsed.scheme not in ("http", "https")
or not parsed.netloc
or hostname is None
or invalid_character
):
raise ValueError(f"{label} must be an absolute HTTP or HTTPS URL")
if (
parsed.username is not None
or parsed.password is not None
or parsed.query
or parsed.fragment
):
raise ValueError(
f"{label} must exclude credentials, query parameters, and fragments"
)
def validate_private_key(value: str, label: str = "private_key") -> None:
try:
padded = value + "=" * ((4 - len(value) % 4) % 4)
seed = base64.b64decode(
padded.replace("-", "+").replace("_", "/"), validate=True
)
canonical = base64.urlsafe_b64encode(seed).rstrip(b"=").decode("ascii")
except (ValueError, UnicodeEncodeError) as error:
raise ValueError(
f"{label} must be a canonical 32-byte base64url value"
) from error
if len(seed) != 32 or canonical != value:
raise ValueError(f"{label} must be a canonical 32-byte base64url value")
def _require_non_empty_string(value: Any, field: str, config_path: str) -> str:
if not isinstance(value, str) or not value.strip():
raise ValueError(f"Elydora runtime config {field} is invalid: {config_path}")
return value
def _read_runtime_config(file_path: str) -> Optional[JsonObject]:
snapshot = read_physical_file(
file_path, "Elydora runtime config", MAX_CONFIG_BYTES
)
if snapshot is None:
return None
return parse_json_object(
snapshot.contents, f"Elydora runtime config at {file_path}"
)
def _validate_runtime_config(
config: JsonObject,
expected_agent_id: str,
config_path: str,
expected_workspace_root: str,
allow_missing_workspace_root: bool = False,
allow_legacy_ownerless_config: bool = False,
) -> None:
supported = {
"org_id",
"agent_id",
"kid",
"base_url",
"token",
"agent_name",
"workspace_root",
}
extra = next((key for key in config if key not in supported), None)
if extra is not None:
raise ValueError(
f'Elydora runtime config has unsupported field "{extra}": {config_path}'
)
_require_non_empty_string(config.get("org_id"), "org_id", config_path)
_require_non_empty_string(config.get("kid"), "kid", config_path)
agent_id = _require_non_empty_string(
config.get("agent_id"), "agent_id", config_path
)
if (
not same_kiroide_agent_id(agent_id, expected_agent_id)
or config.get("agent_name") != AGENT_KEY
):
raise ValueError(
"Elydora runtime identity does not match Kiro IDE hooks: "
f"{config_path}"
)
configured_workspace = config.get("workspace_root")
legacy_ownerless_config = (
allow_legacy_ownerless_config and configured_workspace is None
)
if "token" in config and not (
legacy_ownerless_config and config.get("token") == ""
):
_require_non_empty_string(config.get("token"), "token", config_path)
base_url = _require_non_empty_string(
config.get("base_url"), "base_url", config_path
)
validate_api_origin(base_url, "Elydora runtime config base_url")
if configured_workspace is None and (
allow_missing_workspace_root or legacy_ownerless_config
):
return
workspace_root = _require_non_empty_string(
configured_workspace, "workspace_root", config_path
)
if not os.path.isabs(workspace_root):
raise ValueError(
f"Elydora runtime config workspace_root must be absolute: {config_path}"
)
if not same_kiroide_path(workspace_root, expected_workspace_root):
raise ValueError(
"Elydora Kiro IDE agent is bound to another workspace: "
f"{workspace_root}"
)
def validate_kiroide_runtime_config(
config: JsonObject,
expected_agent_id: str,
config_path: str,
expected_workspace_root: str,
allow_missing_workspace_root: bool = False,
allow_legacy_ownerless_config: bool = False,
) -> None:
"""Validate a runtime config already read through a pinned directory."""
_validate_runtime_config(
config,
expected_agent_id,
config_path,
expected_workspace_root,
allow_missing_workspace_root,
allow_legacy_ownerless_config,
)
def validate_runtime_tree(
agent_directory: str,
agent_id: str,
workspace_root: str,
allow_missing_workspace_root: bool = False,
allow_legacy_ownerless_config: bool = False,
) -> None:
root = runtime_root()
if not physical_directory_exists(root, "Elydora runtime directory"):
return
if not physical_directory_exists(
agent_directory, "Elydora agent runtime directory"
):
return
config_path = os.path.join(agent_directory, "config.json")
config = _read_runtime_config(config_path)
artifact_exists = any(
physical_file_exists(os.path.join(agent_directory, name), label)
for name, label in (
("private.key", "Elydora private key"),
("guard.py", "Elydora guard runtime"),
("hook.py", "Elydora audit runtime"),
("chain-state.json", "Elydora chain state"),
("status-cache.json", "Elydora status cache"),
("error.log", "Elydora error log"),
)
)
if config is None:
if artifact_exists:
raise ValueError(
"Elydora runtime identity cannot be verified without config.json: "
f"{agent_directory}"
)
return
_validate_runtime_config(
config,
agent_id,
config_path,
workspace_root,
allow_missing_workspace_root,
allow_legacy_ownerless_config,
)
def _runtime_contract_exists(
contract: KiroIdeRuntimeContract, workspace_root: str
) -> bool:
root = runtime_root()
agent_directory = os.path.dirname(contract.guard_path)
if (
not same_kiroide_path(os.path.dirname(agent_directory), root)
or not same_kiroide_path(
contract.audit_path, os.path.join(agent_directory, "hook.py")
)
):
return False
if not physical_directory_exists(root, "Elydora runtime directory"):
return False
if not physical_directory_exists(
agent_directory, "Elydora agent runtime directory"
):
return False
config_path = os.path.join(agent_directory, "config.json")
config_snapshot = read_physical_file(
config_path, "Elydora runtime config", MAX_CONFIG_BYTES
)
config = (
None
if config_snapshot is None
else parse_json_object(
config_snapshot.contents,
f"Elydora runtime config at {config_path}",
)
)
key = read_physical_file(
os.path.join(agent_directory, "private.key"),
"Elydora private key",
MAX_SECRET_BYTES,
)
guard = read_physical_file(contract.guard_path, "Elydora guard runtime")
audit = read_physical_file(contract.audit_path, "Elydora audit runtime")
if config is None or key is None or guard is None or audit is None:
return False
_validate_runtime_config(
config,
contract.agent_id,
config_path,
workspace_root,
)
if os.name != "nt" and key.mode & 0o077:
raise ValueError(
f"Elydora private key must be accessible only by its owner: "
f"{os.path.join(agent_directory, 'private.key')}"
)
if (
os.name != "nt"
and "token" in config
and config_snapshot is not None
and config_snapshot.mode & 0o077
):
raise ValueError(
"Token-bearing Elydora runtime config must be accessible only by "
f"its owner: {config_path}"
)
validate_private_key(key.contents, "Elydora private key")
expected_guard = generate_guard_script(AGENT_KEY, contract.agent_id)
expected_audit = generate_hook_script(
org_id=str(config["org_id"]),
agent_id=contract.agent_id,
kid=str(config["kid"]),
base_url=str(config["base_url"]),
native_payload=True,
agent_name=AGENT_KEY,
)
return guard.contents == expected_guard and audit.contents == expected_audit
def kiroide_runtime_files_exist(
contracts: List[KiroIdeRuntimeContract],
workspace_root: str,
) -> bool:
return any(
_runtime_contract_exists(contract, workspace_root) for contract in contracts
)
def require_kiroide_workspace_owner(
agent_id: str,
workspace_root: str,
allow_missing_workspace_root: bool = False,
allow_legacy_ownerless_config: bool = False,
) -> None:
root = runtime_root()
agent_directory = resolve_agent_directory(root, agent_id)
validate_runtime_tree(
agent_directory,
agent_id,
workspace_root,
allow_missing_workspace_root,
allow_legacy_ownerless_config,
)
def legacy_kiroide_contract_matches_agent(
legacy: LegacyKiroIdeDocument, agent_id: str
) -> bool:
contract = legacy.contract
if contract is None or not same_kiroide_agent_id(
contract.agent_id, agent_id
):
return False
agent_directory = resolve_agent_directory(runtime_root(), agent_id)
return same_kiroide_path(
contract.guard_path,
os.path.join(agent_directory, "guard.py"),
) and same_kiroide_path(
contract.audit_path,
os.path.join(agent_directory, "hook.py"),
)