diff --git a/cli/pyproject.toml b/cli/pyproject.toml index a79c572e9..d99d0c73b 100644 --- a/cli/pyproject.toml +++ b/cli/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "click>=8.0", "pyyaml>=6.0", "pydantic>=2.0", + "jsonschema>=4.23", "rfc8785==0.1.4", ] diff --git a/cli/src/limen/cloud_routine.py b/cli/src/limen/cloud_routine.py new file mode 100644 index 000000000..78aa8e9b8 --- /dev/null +++ b/cli/src/limen/cloud_routine.py @@ -0,0 +1,427 @@ +"""Typed cloud-routine outcome receipts and idempotent task planning.""" + +from __future__ import annotations + +import hashlib +import re +from collections.abc import Iterable +from dataclasses import dataclass +from datetime import datetime, timedelta, timezone +from typing import Literal + +from pydantic import field_validator, model_validator + +from limen.conduct.models import ProtocolModel +from limen.intake import is_executable_predicate +from limen.models import Task + + +_ROUTINE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$") +_FINDING_KEY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,255}$") +_REPO_RE = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$") +_MAX_FUTURE_SKEW_SECONDS = 300 + + +def _valid_repo_ref(value: str) -> bool: + if not _REPO_RE.fullmatch(value): + return False + owner, repository = value.split("/", 1) + return owner not in {".", ".."} and repository not in {".", ".."} + + +_LEVER_REF_RE = re.compile(r"^lever:[A-Za-z0-9][A-Za-z0-9._-]{0,127}$") +_DURABLE_OWNER_RE = re.compile( + r"^(?:lever:[A-Za-z0-9][A-Za-z0-9._-]{0,127}|" + r"irf:[A-Za-z0-9][A-Za-z0-9._:-]{0,127}|" + r"https://github\.com/(?!\.\.?/)(?![A-Za-z0-9_.-]+/\.\.?/)" + r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/" + r"(?:issues|pull|actions/runs)/[0-9]+)$" +) +_OCCURRENCE_TASK_RE = re.compile(r"^(CLOUD-[0-9A-F]{20})(?:-[0-9]{8}T[0-9]{6}(?:\.[0-9]{6})?Z)?$") +_PREDICATE_SCHEMA_RE = re.compile( + r"^(?!.*(?:<[^>]+>|\b(?:tbd|todo|fixme|replace[-_ ]me)\b))" + r"(?=(?:[^']*'[^']*')*[^']*$)" + r'(?=(?:[^"]*"[^"]*")*[^"]*$)' + r"""(?=(?:(?:[^'";|&])|'[^']*'|"[^"]*")*$)""" + r"(?!.*`)" + r"(?!.*\\$).+$", + re.IGNORECASE, +) + + +_MAX_SUBSTITUTION_DEPTH = 32 + + +def _substitution_end( + command: str, + start: int, + depth: int = 0, +) -> int | None: + """Find a command substitution's closing parenthesis with nested quote contexts.""" + if depth > _MAX_SUBSTITUTION_DEPTH: + return None + quote: str | None = None + escaped = False + index = start + while index < len(command): + char = command[index] + if escaped: + escaped = False + index += 1 + continue + if char == chr(92): + escaped = True + index += 1 + continue + if quote == "'": + if char == "'": + quote = None + index += 1 + continue + if quote == '"': + if char == '"': + quote = None + index += 1 + continue + if char == "$" and index + 1 < len(command) and command[index + 1] == "(": + nested_end = _substitution_end(command, index + 2, depth + 1) + if nested_end is None: + return None + index = nested_end + 1 + continue + index += 1 + continue + if char in {"'", '"'}: + quote = char + index += 1 + continue + if char == "$" and index + 1 < len(command) and command[index + 1] == "(": + nested_end = _substitution_end(command, index + 2, depth + 1) + if nested_end is None: + return None + index = nested_end + 1 + continue + if char == ")": + return index + index += 1 + return None + + +def _contains_shell_composition(command: str) -> bool: + # Return whether shell composition occurs outside quoted literals. + quote: str | None = None + escaped = False + for char in command: + if escaped: + escaped = False + continue + if char == chr(92): + escaped = True + continue + if quote is not None: + if char == quote: + quote = None + continue + if char in {"'", '"'}: + quote = char + elif char in {";", "|", "&"}: + return True + return False + + +def _has_unsafe_command_substitution(command: str, depth: int = 0) -> bool: + # Reject legacy backticks and composition hidden inside a command substitution. + if depth > _MAX_SUBSTITUTION_DEPTH: + return True + if "`" in command: + return True + quote: str | None = None + escaped = False + index = 0 + while index < len(command): + char = command[index] + if escaped: + escaped = False + index += 1 + continue + if char == chr(92): + escaped = True + index += 1 + continue + if quote == "'": + if char == "'": + quote = None + index += 1 + continue + if quote == '"': + if char == '"': + quote = None + index += 1 + continue + if char == "$" and index + 1 < len(command) and command[index + 1] == "(": + end = _substitution_end(command, index + 2, depth + 1) + if end is None: + return True + body = command[index + 2 : end] + if _contains_shell_composition(body) or _has_unsafe_command_substitution(body, depth + 1): + return True + index = end + 1 + continue + index += 1 + continue + if char == "'": + quote = "'" + index += 1 + continue + if char == '"': + quote = '"' + index += 1 + continue + if char == "$" and index + 1 < len(command) and command[index + 1] == "(": + end = _substitution_end(command, index + 2, depth + 1) + if end is None: + return True + body = command[index + 2 : end] + if _contains_shell_composition(body) or _has_unsafe_command_substitution(body, depth + 1): + return True + index = end + 1 + continue + index += 1 + return False + + +CloudRoutineStatus = Literal["ok", "finding", "failed"] +CloudRoutineDisposition = Literal[ + "no_change", + "superseded", + "owned", + "new_work", + "human_gate", +] + + +class CloudRoutineReceiptV1(ProtocolModel): + """One routine observation with stable ownership and executable closure truth.""" + + schema_version: Literal["limen.cloud_routine_receipt.v1"] = "limen.cloud_routine_receipt.v1" + routine_id: str + observed_at: datetime + status: CloudRoutineStatus + stable_finding_key: str + disposition: CloudRoutineDisposition + owner_ref: str | None + predicate: str + + @field_validator("routine_id") + @classmethod + def validate_routine_id(cls, value: str) -> str: + if not _ROUTINE_ID_RE.fullmatch(value): + raise ValueError("routine_id must be a bounded protocol identifier") + return value + + @field_validator("observed_at") + @classmethod + def validate_observed_at(cls, value: datetime) -> datetime: + if value.tzinfo is None or value.utcoffset() is None: + raise ValueError("observed_at must include a timezone") + if value.astimezone(timezone.utc) > datetime.now(timezone.utc) + timedelta(seconds=_MAX_FUTURE_SKEW_SECONDS): + raise ValueError(f"observed_at cannot be more than {_MAX_FUTURE_SKEW_SECONDS} seconds in the future") + return value + + @field_validator("stable_finding_key") + @classmethod + def validate_finding_key(cls, value: str) -> str: + if not _FINDING_KEY_RE.fullmatch(value): + raise ValueError("stable_finding_key must be a bounded protocol identifier") + return value + + @field_validator("owner_ref") + @classmethod + def validate_owner_ref(cls, value: str | None) -> str | None: + if value is None: + return None + normalized = value.strip() + if not normalized or "\x00" in normalized or len(normalized) > 1024: + raise ValueError("owner_ref must be a non-empty bounded reference") + return normalized + + @field_validator("predicate") + @classmethod + def validate_predicate(cls, value: str) -> str: + normalized = value.strip() + if len(normalized) > 8192: + raise ValueError("predicate must be at most 8192 characters") + if _has_unsafe_command_substitution(normalized) or not _PREDICATE_SCHEMA_RE.fullmatch(normalized): + raise ValueError("predicate must match the published bounded shell grammar") + if not is_executable_predicate(normalized): + raise ValueError("predicate must be one executable command") + return normalized + + @model_validator(mode="after") + def validate_material_ownership(self) -> "CloudRoutineReceiptV1": + material = self.status in {"finding", "failed"} + if self.disposition == "human_gate" and not material: + raise ValueError("human_gate is only valid for a material finding") + if (material or self.disposition == "human_gate") and not self.owner_ref: + raise ValueError("material cloud-routine findings require owner_ref") + if self.disposition == "human_gate" and not _LEVER_REF_RE.fullmatch(self.owner_ref or ""): + raise ValueError("human_gate owner_ref must be a lever: reference") + if ( + material + and self.disposition not in {"new_work", "human_gate"} + and not _DURABLE_OWNER_RE.fullmatch(self.owner_ref or "") + ): + raise ValueError("material cloud-routine findings require a durable owner_ref") + if self.disposition == "new_work": + if not material: + raise ValueError("new_work is only valid for a material finding") + if not self.owner_ref or not _valid_repo_ref(self.owner_ref): + raise ValueError("new_work owner_ref must be an exact owner/repo") + return self + + +def task_id_for(receipt: CloudRoutineReceiptV1) -> str: + """Return the stable TABVLARIVS task identity for one finding lineage.""" + lineage = f"{receipt.routine_id}\x00{receipt.stable_finding_key}".encode() + digest = hashlib.sha256(lineage).hexdigest()[:20].upper() + return f"CLOUD-{digest}" + + +def task_for( + receipt: CloudRoutineReceiptV1, + *, + task_id: str | None = None, +) -> Task: + """Translate one new-work disposition into the provider-neutral intake model.""" + if receipt.disposition != "new_work" or not receipt.owner_ref: + raise ValueError("only new_work receipts can become tasks") + task_id = task_id or task_id_for(receipt) + return Task( + id=task_id, + title=f"Cloud routine finding: {receipt.stable_finding_key}", + description=( + f"Resolve finding {receipt.stable_finding_key} from " + f"{receipt.routine_id} observed at {receipt.observed_at.isoformat()}." + ), + repo=receipt.owner_ref, + type="code", + target_agent="any", + priority="medium", + budget_cost=1, + status="open", + created=receipt.observed_at.date(), + predicate=receipt.predicate, + receipt_target=f"github:{receipt.owner_ref}:pull-request:{task_id}", + origin="system_debt", + horizon="present", + value_case=("Convert a material recurring cloud observation into one owned, predicate-bound correction."), + owner_surface=receipt.owner_ref, + context=( + f"CloudRoutineReceiptV1 {receipt.schema_version}; " + f"disposition={receipt.disposition}; " + f"stable_finding_key={receipt.stable_finding_key}; " + f"observed_at={receipt.observed_at.isoformat()}" + ), + ) + + +@dataclass(frozen=True) +class CloudRoutineIngestPlan: + tasks: tuple[Task, ...] + classified: int + duplicates: int + + +def plan_task_upserts( + receipts: Iterable[CloudRoutineReceiptV1], + *, + existing_ids: Iterable[str] = (), + pending_ids: Iterable[str] = (), + historical_ids: Iterable[str] = (), + historical_observed_at: dict[str, datetime] | None = None, +) -> CloudRoutineIngestPlan: + """Plan only novel live work while allowing a terminal lineage to recur. + + A delivery can contain retries or delayed observations for the same stable + lineage. Keep only the newest observation before classifying it, so a later + owned/superseded receipt can never be resurrected by an older new_work row. + """ + receipt_rows = tuple(receipts) + latest_by_lineage: dict[str, CloudRoutineReceiptV1] = {} + receipts_by_timestamp: dict[str, dict[datetime, CloudRoutineReceiptV1]] = {} + collapsed = 0 + for receipt in receipt_rows: + lineage_id = task_id_for(receipt) + by_timestamp = receipts_by_timestamp.setdefault(lineage_id, {}) + previous_at_timestamp = by_timestamp.get(receipt.observed_at) + if previous_at_timestamp is not None: + collapsed += 1 + if previous_at_timestamp != receipt: + raise ValueError(f"conflicting cloud-routine observations share the same timestamp for {lineage_id}") + continue + by_timestamp[receipt.observed_at] = receipt + previous = latest_by_lineage.get(lineage_id) + if previous is None: + latest_by_lineage[lineage_id] = receipt + continue + collapsed += 1 + if receipt.observed_at > previous.observed_at: + latest_by_lineage[lineage_id] = receipt + + active = set(existing_ids) | set(pending_ids) + historical = set(historical_ids) | active + observed_history = historical_observed_at or {} + + def lineage_for(task_id: str) -> str: + match = _OCCURRENCE_TASK_RE.fullmatch(task_id) + return match.group(1) if match else task_id + + latest_historical_observed_at: dict[str, datetime] = {} + for historical_id in historical: + observed_at = observed_history.get(historical_id) + if observed_at is None: + continue + lineage_id = lineage_for(historical_id) + previous_observed_at = latest_historical_observed_at.get(lineage_id) + if previous_observed_at is None or observed_at > previous_observed_at: + latest_historical_observed_at[lineage_id] = observed_at + active_lineages = {lineage_for(task_id) for task_id in active} + seen_lineages: set[str] = set() + tasks: list[Task] = [] + # Every terminal observation is classified even when a later receipt for the + # same lineage controls task emission. This preserves the audit denominator + # without allowing an older new_work receipt to resurrect superseded work. + classified = sum(receipt.disposition != "new_work" for receipt in receipt_rows) + duplicates = collapsed + + for receipt in latest_by_lineage.values(): + lineage_id = task_id_for(receipt) + if receipt.disposition != "new_work": + continue + if lineage_id in active_lineages or lineage_id in seen_lineages: + duplicates += 1 + continue + task_id = lineage_id + if lineage_id in historical: + previous_observed_at = latest_historical_observed_at.get(lineage_id) + if previous_observed_at is None or receipt.observed_at <= previous_observed_at: + duplicates += 1 + continue + observed_utc = receipt.observed_at.astimezone(timezone.utc) + occurrence = observed_utc.strftime("%Y%m%dT%H%M%S") + if observed_utc.microsecond: + occurrence += f".{observed_utc.microsecond:06d}" + task_id = f"{lineage_id}-{occurrence}Z" + if task_id in active or task_id in historical or task_id in seen_lineages: + duplicates += 1 + continue + task = task_for(receipt, task_id=task_id) + tasks.append(task) + seen_lineages.add(lineage_id) + seen_lineages.add(task_id) + + return CloudRoutineIngestPlan( + tasks=tuple(tasks), + classified=classified, + duplicates=duplicates, + ) diff --git a/cli/src/limen/intake.py b/cli/src/limen/intake.py index bbbf6d8ac..3a9431006 100644 --- a/cli/src/limen/intake.py +++ b/cli/src/limen/intake.py @@ -143,6 +143,45 @@ def is_executable_predicate(value: Any) -> bool: if command_index >= len(argv): return False first = argv[command_index] + if first in {"bash", "sh", "zsh"}: + index = command_index + 1 + value_options = {"-o", "+o", "--rcfile", "--init-file"} + while index < len(argv): + option = argv[index] + if option == "--" or not option.startswith("-"): + # After a script operand, later -c-like values are positional arguments. + break + if option in value_options: + if index + 1 >= len(argv) or argv[index + 1].startswith("-"): + return False + index += 2 + continue + if option.startswith(("--rcfile=", "--init-file=")): + index += 1 + continue + short_flags = option[1:] if not option.startswith("--") else "" + if short_flags and "o" in short_flags: + # Ambiguous clusters such as -oc are safer to reject than to misparse. + if "c" in short_flags: + return False + if index + 1 >= len(argv) or argv[index + 1].startswith("-"): + return False + index += 2 + continue + combined_shell_option = bool(short_flags and "c" in short_flags) + if option in {"-c", "-lc", "-ic", "--command"} or combined_shell_option: + if index + 1 >= len(argv): + return False + program = argv[index + 1] + if ( + any(token in program for token in (";", "|", "&", "$(", "`")) + or "\\n" in program + or "\\r" in program + or (("$" + "'") in command and ("\\n" in command or "\\r" in command)) + ): + return False + break + index += 1 return bool(first in EXECUTABLES or "/" in first or first.endswith((".py", ".sh"))) diff --git a/cli/tests/test_cloud_routine.py b/cli/tests/test_cloud_routine.py new file mode 100644 index 000000000..7e49eadb8 --- /dev/null +++ b/cli/tests/test_cloud_routine.py @@ -0,0 +1,763 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path + +import pytest +from jsonschema import Draft202012Validator, FormatChecker +from pydantic import ValidationError + +from limen.cloud_routine import ( + CloudRoutineReceiptV1, + plan_task_upserts, + task_for, + task_id_for, +) +from limen.intake import is_executable_predicate, validate_intake_contract + + +ROOT = Path(__file__).resolve().parents[2] + + +def _receipt(**overrides) -> CloudRoutineReceiptV1: + payload = { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "fleet-audit", + "observed_at": "2026-08-08T12:00:00Z", + "status": "finding", + "stable_finding_key": "fleet.session-meta-push-ci", + "disposition": "new_work", + "owner_ref": "organvm/limen", + "predicate": "python scripts/check-cloud-routine-ingest.py", + } + payload.update(overrides) + return CloudRoutineReceiptV1.model_validate(payload) + + +def test_material_finding_without_owner_is_rejected() -> None: + with pytest.raises(ValidationError, match="require owner_ref"): + _receipt(owner_ref=None) + + +def test_new_work_requires_exact_repository_owner() -> None: + with pytest.raises(ValidationError, match="exact owner/repo"): + _receipt(owner_ref="organvm/limen#2120") + with pytest.raises(ValidationError, match="exact owner/repo"): + _receipt(owner_ref="../..") + + +def test_material_non_new_work_requires_durable_owner() -> None: + with pytest.raises(ValidationError, match="durable owner_ref"): + _receipt( + disposition="owned", + owner_ref="missing-owner", + ) + with pytest.raises(ValidationError, match="durable owner_ref"): + _receipt( + disposition="owned", + owner_ref="https://github.com/../limen/issues/1", + ) + with pytest.raises(ValidationError, match="durable owner_ref"): + _receipt( + disposition="owned", + owner_ref="https://github.com/organvm/../issues/1", + ) + + +def test_observation_time_must_be_timezone_aware() -> None: + with pytest.raises(ValidationError, match="include a timezone"): + _receipt(observed_at="2026-08-08T12:00:00") + + +def test_observation_time_rejects_excessive_future_skew() -> None: + with pytest.raises(ValidationError, match="more than 300 seconds"): + _receipt(observed_at="9999-01-01T00:00:00Z") + + +def test_non_material_observation_can_have_no_owner() -> None: + receipt = _receipt( + status="ok", + disposition="no_change", + owner_ref=None, + stable_finding_key="fleet.daily-green", + predicate="python scripts/check-cloud-routine-ingest.py", + ) + + assert receipt.owner_ref is None + + +def test_predicate_bound_matches_published_schema() -> None: + with pytest.raises(ValidationError, match="at most 8192"): + _receipt(predicate="x" * 8193) + + +def test_human_gate_requires_material_status_and_owner() -> None: + with pytest.raises(ValidationError, match="material finding"): + _receipt(status="ok", disposition="human_gate") + with pytest.raises(ValidationError, match="require owner_ref"): + _receipt(status="finding", disposition="human_gate", owner_ref=None) + with pytest.raises(ValidationError, match="lever:"): + _receipt( + status="finding", + disposition="human_gate", + owner_ref="https://github.com/organvm/limen/issues/2120", + ) + + +def test_task_translation_preserves_predicate_and_intake_contract() -> None: + receipt = _receipt() + + task = task_for(receipt) + + assert task.id == task_id_for(receipt) + assert task.repo == "organvm/limen" + assert task.created.isoformat() == "2026-08-08" + assert task.predicate == receipt.predicate + assert validate_intake_contract(task, is_new=True) is not None + + +def test_repeated_finding_and_pending_ticket_are_idempotent() -> None: + receipt = _receipt() + duplicate_batch = plan_task_upserts([receipt, receipt]) + pending_batch = plan_task_upserts( + [receipt], + pending_ids={task_id_for(receipt)}, + ) + + assert len(duplicate_batch.tasks) == 1 + assert duplicate_batch.duplicates == 1 + assert pending_batch.tasks == () + assert pending_batch.duplicates == 1 + + +def test_equal_timestamp_conflicts_are_rejected_deterministically() -> None: + owned = _receipt( + disposition="owned", + owner_ref="https://github.com/organvm/limen/issues/2120", + ) + + with pytest.raises(ValueError, match="conflicting cloud-routine observations"): + plan_task_upserts([_receipt(), owned]) + + +def test_latest_lineage_disposition_wins_before_task_planning() -> None: + older = _receipt(observed_at="2026-08-08T11:00:00Z") + newer = _receipt( + observed_at="2026-08-08T12:00:00Z", + disposition="owned", + owner_ref="https://github.com/organvm/limen/issues/2120", + ) + + plan = plan_task_upserts([older, newer]) + + assert plan.tasks == () + assert plan.classified == 1 + assert plan.duplicates == 1 + + +def test_active_recurrence_occurrence_blocks_another_lineage_task() -> None: + receipt = _receipt() + lineage_id = task_id_for(receipt) + + plan = plan_task_upserts( + [receipt], + pending_ids={f"{lineage_id}-20260808T110000Z"}, + historical_ids={lineage_id}, + ) + + assert plan.tasks == () + assert plan.duplicates == 1 + + +def test_terminal_lineage_can_emit_a_new_occurrence() -> None: + receipt = _receipt() + lineage_id = task_id_for(receipt) + + plan = plan_task_upserts( + [receipt], + historical_ids={lineage_id}, + historical_observed_at={lineage_id: _receipt(observed_at="2026-08-08T11:00:00Z").observed_at}, + ) + + assert len(plan.tasks) == 1 + assert plan.tasks[0].id == f"{lineage_id}-20260808T120000Z" + assert plan.duplicates == 0 + + +def test_terminal_lineage_preserves_subsecond_recurrences() -> None: + receipt_one = _receipt(observed_at="2026-08-08T12:00:00.000001Z") + receipt_two = _receipt(observed_at="2026-08-08T12:00:00.000002Z") + lineage_id = task_id_for(receipt_one) + baseline = _receipt(observed_at="2026-08-08T11:00:00Z").observed_at + + first = plan_task_upserts( + [receipt_one], + historical_ids={lineage_id}, + historical_observed_at={lineage_id: baseline}, + ) + first_id = first.tasks[0].id + second = plan_task_upserts( + [receipt_two], + historical_ids={lineage_id, first_id}, + historical_observed_at={lineage_id: baseline}, + ) + + assert first_id != second.tasks[0].id + assert second.duplicates == 0 + + +def test_historical_occurrence_timestamp_blocks_older_replay() -> None: + receipt = _receipt(observed_at="2026-08-08T12:00:00Z") + lineage_id = task_id_for(receipt) + first = plan_task_upserts( + [receipt], + historical_ids={lineage_id}, + historical_observed_at={lineage_id: _receipt(observed_at="2026-08-08T11:00:00Z").observed_at}, + ) + occurrence_id = first.tasks[0].id + + delayed = _receipt(observed_at="2026-08-08T11:30:00Z") + plan = plan_task_upserts( + [delayed], + historical_ids={lineage_id, occurrence_id}, + historical_observed_at={ + lineage_id: _receipt(observed_at="2026-08-08T11:00:00Z").observed_at, + occurrence_id: receipt.observed_at, + }, + ) + + assert plan.tasks == () + assert plan.duplicates == 1 + + +def test_terminal_lineage_replay_is_a_duplicate() -> None: + receipt = _receipt() + lineage_id = task_id_for(receipt) + + plan = plan_task_upserts( + [receipt], + historical_ids={lineage_id}, + historical_observed_at={lineage_id: receipt.observed_at}, + ) + + assert plan.tasks == () + assert plan.duplicates == 1 + + +def test_owned_and_superseded_findings_do_not_create_tasks() -> None: + owned = _receipt( + stable_finding_key="owned-finding", + disposition="owned", + owner_ref="https://github.com/organvm/limen/issues/2120", + ) + superseded = _receipt( + stable_finding_key="superseded-finding", + disposition="superseded", + owner_ref="https://github.com/organvm/limen/pull/2121", + ) + + plan = plan_task_upserts([owned, superseded]) + + assert plan.tasks == () + assert plan.classified == 2 + + +def test_manifest_publishes_the_receipt_contract() -> None: + manifest = json.loads((ROOT / "cloud-routines.json").read_text()) + + assert manifest["receipt_schema_version"] == "limen.cloud_routine_receipt.v1" + assert manifest["receipt_schema"] == ("spec/contracts/cloud-routine-receipt-v1.schema.json") + assert manifest["consumer"] == "scripts/cloud-routine-ingest.py" + + +def test_published_schema_carries_executable_and_human_gate_constraints() -> None: + schema = json.loads((ROOT / "spec" / "contracts" / "cloud-routine-receipt-v1.schema.json").read_text()) + validator = Draft202012Validator(schema, format_checker=FormatChecker()) + valid = _receipt().model_dump(mode="json") + invalid_placeholder = {**valid, "predicate": "python "} + invalid_quote = {**valid, "predicate": "python '"} + valid_substitution = {**valid, "predicate": 'test "$(git rev-parse --show-toplevel)" = /tmp'} + invalid_backtick = {**valid, "predicate": "test `false` = success"} + invalid_semicolon = {**valid, "predicate": "python check.py; true"} + invalid_nested_substitution = {**valid, "predicate": 'test "$(false; echo success)" = success'} + invalid_pipeline = {**valid, "predicate": "python check.py | true"} + invalid_owner = {**valid, "disposition": "owned", "owner_ref": " "} + invalid_durable_owner = {**valid, "disposition": "owned", "owner_ref": "missing-owner"} + invalid_path_owner = {**valid, "owner_ref": "../.."} + invalid_dotted_owner = { + **valid, + "disposition": "owned", + "owner_ref": "https://github.com/../limen/issues/1", + } + invalid_dotted_repo_owner = { + **valid, + "disposition": "owned", + "owner_ref": "https://github.com/organvm/../issues/1", + } + invalid_clustered_shell = {**valid, "predicate": "bash -uc 'false; true'"} + invalid_ansi_c = {**valid, "predicate": "bash -c $'false\\ntrue'"} + assert not list(validator.iter_errors(valid)) + assert not list(validator.iter_errors(valid_substitution)) + assert list(validator.iter_errors(invalid_placeholder)) + assert list(validator.iter_errors(invalid_quote)) + assert list(validator.iter_errors(invalid_backtick)) + assert list(validator.iter_errors(invalid_semicolon)) + assert list(validator.iter_errors(invalid_nested_substitution)) + assert list(validator.iter_errors(invalid_pipeline)) + assert list(validator.iter_errors(invalid_owner)) + assert list(validator.iter_errors(invalid_durable_owner)) + assert list(validator.iter_errors(invalid_path_owner)) + assert list(validator.iter_errors(invalid_dotted_owner)) + assert list(validator.iter_errors(invalid_dotted_repo_owner)) + assert list(validator.iter_errors(invalid_clustered_shell)) + assert list(validator.iter_errors(invalid_ansi_c)) + human_gate = schema["allOf"][-1] + assert human_gate["if"]["properties"]["disposition"]["const"] == "human_gate" + assert human_gate["then"]["properties"]["status"]["enum"] == ["finding", "failed"] + assert human_gate["then"]["properties"]["owner_ref"]["pattern"].startswith("^lever:") + + +def test_model_allows_safe_substitution_but_rejects_composition() -> None: + safe = 'test "$(git rev-parse --show-toplevel)" = /tmp' + assert _receipt(predicate=safe).predicate == safe + with pytest.raises(ValidationError, match="bounded shell grammar"): + _receipt(predicate="python check.py; true") + with pytest.raises(ValidationError, match="one executable command"): + _receipt(predicate="bash -c 'false; true'") + with pytest.raises(ValidationError, match="bounded shell grammar"): + _receipt(predicate="python check.py | true") + with pytest.raises(ValidationError, match="bounded shell grammar"): + _receipt(predicate='test "$(false; echo success)" = success') + with pytest.raises(ValidationError, match="bounded shell grammar"): + _receipt(predicate='test -z "$(printf \'%s\' "$(false \\"x; true \\")")"') + with pytest.raises(ValidationError, match="bounded shell grammar"): + _receipt(predicate="test `false` = success") + + +def test_deep_substitution_is_rejected_without_recursion_error() -> None: + predicate = "test " + "$(" * 40 + "true" + ")" * 40 + with pytest.raises(ValidationError, match="bounded shell grammar"): + _receipt(predicate=predicate) + + +def test_lineage_conflicts_are_order_independent() -> None: + first = _receipt(observed_at="2026-08-08T12:00:00Z") + newer = _receipt(observed_at="2026-08-08T13:00:00Z") + conflicting = _receipt( + observed_at="2026-08-08T12:00:00Z", + disposition="owned", + owner_ref="https://github.com/organvm/limen/issues/2120", + ) + with pytest.raises(ValueError, match="conflicting cloud-routine observations"): + plan_task_upserts([first, newer, conflicting]) + + +def test_shell_option_scan_advances_past_valid_options() -> None: + assert is_executable_predicate("bash -e check.sh") + assert is_executable_predicate("bash -c 'test -f marker'") + assert not is_executable_predicate("bash -o pipefail -c 'echo x; rm -rf /'") + assert not is_executable_predicate("bash --rcfile /tmp/rc -c 'echo x; rm -rf /'") + assert not is_executable_predicate("bash --init-file /tmp/rc -c 'echo x; rm -rf /'") + + +def test_shell_predicate_parsing_stops_at_script_and_rejects_ansi_c_newline() -> None: + assert is_executable_predicate("bash check.sh -c 'value;literal'") + with pytest.raises(ValidationError, match="one executable command"): + _receipt(predicate="bash -c $'false\\ntrue'") + + +def test_tracked_lineage_remains_a_duplicate(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location( + "cloud_routine_ingest_tracked_test", + script, + ) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + monkeypatch.setattr(module, "ROOT", tmp_path) + + receipt = _receipt() + lineage = tmp_path / "docs" / "receipts" + lineage.mkdir(parents=True) + (lineage / "cloud-routine-lineage.json").write_text( + json.dumps( + { + "schema_version": "limen.cloud_routine_lineage.v1", + "entries": [receipt.model_dump(mode="json")], + } + ), + encoding="utf-8", + ) + + historical_ids, observed = module._historical_cloud_task_state(tmp_path / "tasks.yaml") + + assert task_id_for(receipt) in historical_ids + assert observed[task_id_for(receipt)] == receipt.observed_at + assert ( + plan_task_upserts( + [receipt], + historical_ids=historical_ids, + historical_observed_at=observed, + ).tasks + == () + ) + + +def test_tracked_lineage_rejects_invalid_entry(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_invalid_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + monkeypatch.setattr(module, "ROOT", tmp_path) + + lineage = tmp_path / "docs" / "receipts" + lineage.mkdir(parents=True) + (lineage / "cloud-routine-lineage.json").write_text( + json.dumps({"schema_version": "limen.cloud_routine_lineage.v1", "entries": [{"bad": True}]}), + encoding="utf-8", + ) + + with pytest.raises(ValueError, match="tracked cloud lineage entry\[0\] is invalid"): + module._historical_cloud_task_state(tmp_path / "tasks.yaml") + + +def test_pruned_archive_lineage_remains_a_duplicate(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_archive_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + receipt = _receipt() + lineage_id = task_id_for(receipt) + archive = tmp_path / "logs" / "tickets" / "archive" + archive.mkdir(parents=True) + (archive / "removed.json").write_text( + json.dumps( + { + "intent": "task.upsert", + "task_id": lineage_id, + "patch": { + "id": lineage_id, + "context": "CloudRoutineReceiptV1; observed_at=2026-08-08T12:00:00+00:00", + }, + } + ), + encoding="utf-8", + ) + + historical_ids, observed = module._historical_cloud_task_state(tmp_path / "tasks.yaml") + + assert lineage_id in historical_ids + assert observed[lineage_id].isoformat() == "2026-08-08T12:00:00+00:00" + assert ( + plan_task_upserts( + [receipt], + historical_ids=historical_ids, + historical_observed_at=observed, + ).tasks + == () + ) + + +def test_scoped_gate_covers_every_external_cloud_contract_artifact() -> None: + gates = (ROOT / "institutio" / "governance" / "gates.yaml").read_text(encoding="utf-8") + for path in ( + "spec/contracts/cloud-routine-receipt-v1.schema.json", + "scripts/cloud-routine-ingest.py", + "scripts/check-cloud-routine-ingest.py", + "docs/receipts/cloud-routine-findings-20260808.json", + "docs/receipts/cloud-routine-lineage.json", + "docs/receipts/irf-p0-owner-classification-20260808.json", + ): + assert path in gates + + +def test_current_findings_are_typed_and_already_owned() -> None: + payload = json.loads((ROOT / "docs" / "receipts" / "cloud-routine-findings-20260808.json").read_text()) + receipts = [CloudRoutineReceiptV1.model_validate(item) for item in payload] + + assert len(receipts) == 11 + assert all(receipt.owner_ref for receipt in receipts) + assert all(receipt.disposition != "new_work" for receipt in receipts) + irf = next(receipt for receipt in receipts if receipt.stable_finding_key == "open-p0.denominator-41") + assert "human_gate_irf_ids" in irf.predicate + + +def test_irf_denominator_is_fully_classified_without_packet_emissions() -> None: + receipt = json.loads((ROOT / "docs" / "receipts" / "irf-p0-owner-classification-20260808.json").read_text()) + rows = receipt["rows"] + by_id = {row["irf_id"]: row for row in rows} + human_ids = set(receipt["human_gate_irf_ids"]) + + assert receipt["denominator"] == receipt["classified"] == len(rows) == 41 + assert len(by_id) == 41 + assert len(human_ids) == 18 + assert all( + by_id[irf_id]["owner_kind"] == "lever" + and by_id[irf_id]["owner_ref"] == receipt["human_gate_owner"] + and by_id[irf_id]["disposition"] == "human_gate" + for irf_id in human_ids + ) + assert all( + row["owner_kind"] == "irf" and row["owner_ref"] == f"irf:{row['irf_id']}" and row["disposition"] == "owned" + for row in rows + if row["irf_id"] not in human_ids + ) + assert receipt["unowned"] == [] + assert receipt["packet_emissions"] == [] + + +def test_submitted_lineage_append_is_idempotent(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_lineage_append_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + monkeypatch.setattr(module, "ROOT", tmp_path) + receipt = _receipt() + + module._append_cloud_lineage_receipt(receipt) + module._append_cloud_lineage_receipt(receipt) + + lineage = json.loads((tmp_path / "docs" / "receipts" / "cloud-routine-lineage.json").read_text()) + assert len(lineage["entries"]) == 1 + assert lineage["entries"][0]["stable_finding_key"] == receipt.stable_finding_key + + +def test_consumer_rejects_a_nonexistent_non_human_lever(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_non_human_lever_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + registry = tmp_path / "his-hand-levers.json" + registry.write_text('{"levers": []}', encoding="utf-8") + receipt = _receipt(disposition="owned", owner_ref="lever:L-NOT-REGISTERED") + + with pytest.raises(ValueError, match="does not resolve"): + module.validate_lever_owners([receipt], lever_path=registry) + + +def test_consumer_rejects_a_nonexistent_human_lever(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + registry = tmp_path / "his-hand-levers.json" + registry.write_text('{"levers": []}', encoding="utf-8") + receipt = _receipt( + disposition="human_gate", + owner_ref="lever:L-NOT-REGISTERED", + ) + + with pytest.raises(ValueError, match="does not resolve"): + module.validate_human_gate_owners([receipt], lever_path=registry) + + +def test_consumer_accepts_a_statusless_active_human_lever(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_statusless_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + registry = tmp_path / "his-hand-levers.json" + registry.write_text( + json.dumps({"levers": [{"id": "L-ACTIVE"}]}), + encoding="utf-8", + ) + receipt = _receipt( + disposition="human_gate", + owner_ref="lever:L-ACTIVE", + ) + + module.validate_human_gate_owners([receipt], lever_path=registry) + assert "L-ACTIVE" in module.active_lever_ids(registry) + + +def test_consumer_collapses_lineages_before_live_owner_resolution(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_lineage_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + registry = tmp_path / "his-hand-levers.json" + registry.write_text( + json.dumps({"levers": [{"id": "L-CLOSED", "status": "discharged"}]}), + encoding="utf-8", + ) + receipt_path = tmp_path / "receipts.json" + receipt_path.write_text( + json.dumps( + [ + _receipt( + observed_at="2026-08-08T11:00:00Z", + disposition="human_gate", + owner_ref="lever:L-CLOSED", + ).model_dump(mode="json"), + _receipt( + observed_at="2026-08-08T12:00:00Z", + disposition="owned", + owner_ref="https://github.com/organvm/limen/issues/2120", + ).model_dump(mode="json"), + ] + ), + encoding="utf-8", + ) + + loaded = module.load_receipts([receipt_path], lever_path=registry) + assert len(loaded) == 2 + + +@pytest.mark.parametrize("payload", ["[]", "\n"]) +def test_consumer_rejects_empty_delivery(tmp_path: Path, payload: str) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_empty_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + receipt_path = tmp_path / "empty.json" + receipt_path.write_text(payload, encoding="utf-8") + + with pytest.raises(ValueError, match="receipt delivery is empty"): + module.load_receipts([receipt_path]) + + +def test_consumer_rejects_a_terminal_human_lever(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_terminal_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + registry = tmp_path / "his-hand-levers.json" + registry.write_text( + json.dumps({"levers": [{"id": "L-DONE", "status": "discharged"}]}), + encoding="utf-8", + ) + receipt = _receipt( + disposition="human_gate", + owner_ref="lever:L-DONE", + ) + + with pytest.raises(ValueError, match="terminal/inactive"): + module.validate_human_gate_owners([receipt], lever_path=registry) + + +def test_consumer_rejects_a_routine_absent_from_manifest(tmp_path: Path) -> None: + script = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_manifest_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + receipt_path = tmp_path / "receipt.json" + receipt_path.write_text( + json.dumps([_receipt(routine_id="fleat-audit").model_dump(mode="json")]), + encoding="utf-8", + ) + manifest = tmp_path / "cloud-routines.json" + manifest.write_text(json.dumps({"routines": [{"name": "fleet-audit"}]}), encoding="utf-8") + + with pytest.raises(ValueError, match="absent from cloud-routines.json"): + module.load_receipts([receipt_path], manifest_path=manifest) + + +def test_closure_checker_delegates_manifest_validation(capsys, monkeypatch) -> None: + script = ROOT / "scripts" / "check-cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_checker_manifest_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + class _FakeIngest: + @staticmethod + def validate_routine_ids(_receipts, *, manifest_path): + raise ValueError(f"manifest sentinel: {manifest_path.name}") + + @staticmethod + def validate_human_gate_owners(_receipts, *, lever_path): + return None + + @staticmethod + def active_lever_ids(_path): + return {"L-IRF-P0-HUMAN-ACTIONS-20260808"} + + monkeypatch.setattr(module, "_load_ingest_module", lambda: _FakeIngest) + + assert module.main() == 1 + assert "manifest sentinel" in capsys.readouterr().out + + +def test_irf_validator_derives_every_row_owner() -> None: + script = ROOT / "scripts" / "check-cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_checker_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + receipt = json.loads((ROOT / "docs" / "receipts" / "irf-p0-owner-classification-20260808.json").read_text()) + broken = json.loads(json.dumps(receipt)) + owned_row = next(row for row in broken["rows"] if row["disposition"] == "owned") + owned_row.pop("owner_ref") + + failures = module.validate_irf_receipt( + broken, + active_levers={str(receipt["human_gate_owner"]).removeprefix("lever:")}, + ) + + assert any("owned-row ownership drift" in failure for failure in failures) + + +def test_irf_validator_allows_terminal_empty_human_partition() -> None: + script = ROOT / "scripts" / "check-cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_checker_empty_human_test", script) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + receipt = json.loads((ROOT / "docs" / "receipts" / "irf-p0-owner-classification-20260808.json").read_text()) + human_ids = set(receipt["human_gate_irf_ids"]) + for row in receipt["rows"]: + if row["irf_id"] in human_ids: + row.update( + owner_kind="irf", + owner_ref=f"irf:{row['irf_id']}", + disposition="owned", + ) + receipt["human_gate_irf_ids"] = [] + + assert module.validate_irf_receipt(receipt, active_levers=set()) == [] + + +def test_cloud_human_gates_have_named_levers() -> None: + lever_data = json.loads((ROOT / "his-hand-levers.json").read_text()) + lever_ids = {lever["id"] for lever in lever_data["levers"]} + + assert { + "L-CLOUD-BULK-PR-CLOSE-N75", + "L-CLOUD-EXTERNAL-GOVERNANCE-N77", + "L-CLOUD-SESSION-SCOPE-EXPANSION", + "L-CLOUD-ARCHIVE-ENTERPRISE-PLUGIN-N80", + "L-LAUNCHDARKLY-OAUTH-CONSENT", + "L-IRF-P0-HUMAN-ACTIONS-20260808", + } <= lever_ids + + +def test_cloud_lever_predicates_require_terminal_status() -> None: + rows = json.loads((ROOT / "docs" / "receipts" / "cloud-routine-findings-20260808.json").read_text()) + by_key = {row["stable_finding_key"]: row for row in rows} + + for key in ( + "LIMEN-N75", + "LIMEN-N77", + "hosted-session.repository-scope", + ): + predicate = by_key[key]["predicate"] + assert "{'discharged','retired','done','closed'}" in predicate + assert "!= 'open'" not in predicate + + +def test_model_rejects_clustered_shell_command_options() -> None: + with pytest.raises(ValidationError, match="one executable command"): + _receipt(predicate="bash -uc 'false; true'") diff --git a/cloud-routines.json b/cloud-routines.json index d334d695c..3d3938c3c 100644 --- a/cloud-routines.json +++ b/cloud-routines.json @@ -1,6 +1,10 @@ { "_doc": "SSOT manifest of claude.ai scheduled cloud routines. The RemoteTrigger API is in-session-only so beat scripts audit the DELIVERY side (rolling-issue comments) via gh; any session that edits routines in claude.ai must update this manifest.", "generated_at": "2026-07-08", + "receipt_schema_version": "limen.cloud_routine_receipt.v1", + "receipt_schema": "spec/contracts/cloud-routine-receipt-v1.schema.json", + "consumer": "scripts/cloud-routine-ingest.py", + "delivery_contract": "Every delivery emits one receipt per stable finding. Material findings must name an owner; only new_work dispositions may submit idempotent TABVLARIVS upsert tickets.", "routines": [ { "name": "stale-pr-sweep", diff --git a/docs/receipts/cloud-routine-findings-20260808.json b/docs/receipts/cloud-routine-findings-20260808.json new file mode 100644 index 000000000..a4009c44e --- /dev/null +++ b/docs/receipts/cloud-routine-findings-20260808.json @@ -0,0 +1,112 @@ +[ + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "fleet-audit", + "observed_at": "2026-08-08T13:34:00Z", + "status": "finding", + "stable_finding_key": "session-meta.scheduled-ci-darkness", + "disposition": "superseded", + "owner_ref": "https://github.com/organvm/session-meta/actions/runs/31217884467", + "predicate": "test \"$(gh run view 31217884467 --repo organvm/session-meta --json conclusion --jq .conclusion)\" = success" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "fleet-audit", + "observed_at": "2026-08-08T13:34:00Z", + "status": "failed", + "stable_finding_key": "session-meta.push-ci", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/session-meta/issues/169", + "predicate": "test \"$(gh run list --repo organvm/session-meta --workflow ci.yml --branch main --event push --status completed --limit 1 --json conclusion --jq '.[0].conclusion')\" = success" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N74", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/session-meta/issues/170", + "predicate": "test \"$(gh issue view 170 --repo organvm/session-meta --json state --jq .state)\" = CLOSED" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N78", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/limen/issues/1981", + "predicate": "test \"$(gh issue view 1981 --repo organvm/limen --json comments --jq '[.comments[].body | select(contains(\"CLOUD-N78-ROUTED\"))] | length')\" -gt 0" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N79", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/session-meta/issues/34", + "predicate": "test \"$(gh issue view 34 --repo organvm/session-meta --json comments --jq '[.comments[].body | select(contains(\"CLOUD-N79-ROUTED\"))] | length')\" -gt 0" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N36", + "disposition": "superseded", + "owner_ref": "https://github.com/organvm/limen/pull/2121", + "predicate": "test \"$(gh pr view 2121 --repo organvm/limen --json state --jq .state)\" = MERGED" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N75", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-BULK-PR-CLOSE-N75", + "predicate": "python3 -c \"import json; d=json.load(open('his-hand-levers.json')); assert next(x for x in d['levers'] if x['id']=='L-CLOUD-BULK-PR-CLOSE-N75')['status'] in {'discharged','retired','done','closed'}\"" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N77", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-EXTERNAL-GOVERNANCE-N77", + "predicate": "python3 -c \"import json; d=json.load(open('his-hand-levers.json')); assert next(x for x in d['levers'] if x['id']=='L-CLOUD-EXTERNAL-GOVERNANCE-N77')['status'] in {'discharged','retired','done','closed'}\"" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N80", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-ARCHIVE-ENTERPRISE-PLUGIN-N80", + "predicate": "test \"$(gh repo view organvm-iii-ergon/enterprise-plugin --json isArchived --jq .isArchived)\" = true" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "daily-cloud-digest", + "observed_at": "2026-08-08T19:09:00Z", + "status": "failed", + "stable_finding_key": "hosted-session.repository-scope", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-SESSION-SCOPE-EXPANSION", + "predicate": "python3 -c \"import json; d=json.load(open('his-hand-levers.json')); assert next(x for x in d['levers'] if x['id']=='L-CLOUD-SESSION-SCOPE-EXPANSION')['status'] in {'discharged','retired','done','closed'}\"" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "irf-p0-surface", + "observed_at": "2026-08-08T11:38:37Z", + "status": "finding", + "stable_finding_key": "open-p0.denominator-41", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/organvm-corpvs-testamentvm/issues/489", + "predicate": "python3 -c \"import json; d=json.load(open('docs/receipts/irf-p0-owner-classification-20260808.json')); assert not d['human_gate_irf_ids']\"" + } +] diff --git a/docs/receipts/cloud-routine-lineage.json b/docs/receipts/cloud-routine-lineage.json new file mode 100644 index 000000000..51fbf3555 --- /dev/null +++ b/docs/receipts/cloud-routine-lineage.json @@ -0,0 +1,116 @@ +{ + "schema_version": "limen.cloud_routine_lineage.v1", + "description": "Tracked append-only cloud receipt lineage; consumers may use this as the durable duplicate boundary.", + "entries": [ + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "fleet-audit", + "observed_at": "2026-08-08T13:34:00Z", + "status": "finding", + "stable_finding_key": "session-meta.scheduled-ci-darkness", + "disposition": "superseded", + "owner_ref": "https://github.com/organvm/session-meta/actions/runs/31217884467", + "predicate": "test \"$(gh run view 31217884467 --repo organvm/session-meta --json conclusion --jq .conclusion)\" = success" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "fleet-audit", + "observed_at": "2026-08-08T13:34:00Z", + "status": "failed", + "stable_finding_key": "session-meta.push-ci", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/session-meta/issues/169", + "predicate": "test \"$(gh run list --repo organvm/session-meta --workflow ci.yml --branch main --event push --status completed --limit 1 --json conclusion --jq '.[0].conclusion')\" = success" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N74", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/session-meta/issues/170", + "predicate": "test \"$(gh issue view 170 --repo organvm/session-meta --json state --jq .state)\" = CLOSED" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N78", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/limen/issues/1981", + "predicate": "test \"$(gh issue view 1981 --repo organvm/limen --json comments --jq '[.comments[].body | select(contains(\"CLOUD-N78-ROUTED\"))] | length')\" -gt 0" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N79", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/session-meta/issues/34", + "predicate": "test \"$(gh issue view 34 --repo organvm/session-meta --json comments --jq '[.comments[].body | select(contains(\"CLOUD-N79-ROUTED\"))] | length')\" -gt 0" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N36", + "disposition": "superseded", + "owner_ref": "https://github.com/organvm/limen/pull/2121", + "predicate": "test \"$(gh pr view 2121 --repo organvm/limen --json state --jq .state)\" = MERGED" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N75", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-BULK-PR-CLOSE-N75", + "predicate": "python3 -c \"import json; d=json.load(open('his-hand-levers.json')); assert next(x for x in d['levers'] if x['id']=='L-CLOUD-BULK-PR-CLOSE-N75')['status'] in {'discharged','retired','done','closed'}\"" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N77", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-EXTERNAL-GOVERNANCE-N77", + "predicate": "python3 -c \"import json; d=json.load(open('his-hand-levers.json')); assert next(x for x in d['levers'] if x['id']=='L-CLOUD-EXTERNAL-GOVERNANCE-N77')['status'] in {'discharged','retired','done','closed'}\"" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "jules-backlog-curation", + "observed_at": "2026-08-08T19:09:00Z", + "status": "finding", + "stable_finding_key": "LIMEN-N80", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-ARCHIVE-ENTERPRISE-PLUGIN-N80", + "predicate": "test \"$(gh repo view organvm-iii-ergon/enterprise-plugin --json isArchived --jq .isArchived)\" = true" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "daily-cloud-digest", + "observed_at": "2026-08-08T19:09:00Z", + "status": "failed", + "stable_finding_key": "hosted-session.repository-scope", + "disposition": "human_gate", + "owner_ref": "lever:L-CLOUD-SESSION-SCOPE-EXPANSION", + "predicate": "python3 -c \"import json; d=json.load(open('his-hand-levers.json')); assert next(x for x in d['levers'] if x['id']=='L-CLOUD-SESSION-SCOPE-EXPANSION')['status'] in {'discharged','retired','done','closed'}\"" + }, + { + "schema_version": "limen.cloud_routine_receipt.v1", + "routine_id": "irf-p0-surface", + "observed_at": "2026-08-08T11:38:37Z", + "status": "finding", + "stable_finding_key": "open-p0.denominator-41", + "disposition": "owned", + "owner_ref": "https://github.com/organvm/organvm-corpvs-testamentvm/issues/489", + "predicate": "python3 scripts/check-cloud-routine-ingest.py" + } + ] +} diff --git a/docs/receipts/irf-p0-owner-classification-20260808.json b/docs/receipts/irf-p0-owner-classification-20260808.json new file mode 100644 index 000000000..5f077cac8 --- /dev/null +++ b/docs/receipts/irf-p0-owner-classification-20260808.json @@ -0,0 +1,322 @@ +{ + "schema_version": "limen.irf_p0_owner_classification.v1", + "observed_at": "2026-08-08T11:38:37Z", + "source": "https://github.com/organvm/organvm-corpvs-testamentvm/issues/489#issuecomment-5225934939", + "denominator": 41, + "classified": 41, + "unowned": [], + "packet_emissions": [], + "ownership_contract": "Rows with owner_kind=irf and owner_ref=irf: are machine-ownable IRF rows; human actions use the named lever owner instead of self-referential IRF ownership.", + "rows": [ + { + "irf_id": "IRF-SYS-166", + "source_key": "**IRF-SYS-166**", + "source_status": "S-2026-04-29-prompt-registry-bleed-stop (sessions f14f2d23, d8688a3d, 4330dd64)", + "owner_kind": "irf", + "owner_ref": "irf:IRF-SYS-166", + "disposition": "owned" + }, + { + "irf_id": "IRF-APP-087", + "source_status": "Human action", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-APP-088", + "source_status": "Human action", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-CRP-018", + "source_status": "Each file requires independent human judgment — three separate decisions.", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-III-026", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-III-026", + "disposition": "owned" + }, + { + "irf_id": "IRF-III-027", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-III-027", + "disposition": "owned" + }, + { + "irf_id": "IRF-III-035", + "source_status": "Maddie response", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-III-047", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-III-047", + "disposition": "owned" + }, + { + "irf_id": "IRF-INST-001", + "source_status": "IRF-INST-015 (human review)", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-INST-015", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-INST-015", + "disposition": "owned" + }, + { + "irf_id": "IRF-INST-016", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-INST-016", + "disposition": "owned" + }, + { + "irf_id": "IRF-OPS-014", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-OPS-014", + "disposition": "owned" + }, + { + "irf_id": "IRF-OPS-028", + "source_status": "Metric generator repair + golden-set test", + "owner_kind": "irf", + "owner_ref": "irf:IRF-OPS-028", + "disposition": "owned" + }, + { + "irf_id": "IRF-OPS-061", + "source_status": "Once-run closure unblocked; durable cadence requires scheduling-mechanism choice.", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-OPS-069", + "source_status": "Agent", + "owner_kind": "irf", + "owner_ref": "irf:IRF-OPS-069", + "disposition": "owned" + }, + { + "irf_id": "IRF-PRT-027", + "source_status": "None (5-min checkout)", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-PRT-028", + "source_status": "IRF-PRT-027 (domain registration)", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-PRT-060", + "source_status": "User must obtain key", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-PRT-061", + "source_status": "User must purchase", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-RES-003", + "source_status": "RP-07 SS7 I1; SYN-02 SS4.4; SYN-04 SS4.1", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-003", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-004", + "source_status": "RP-07 SS6.2, SS6.3; SYN-02 SS5.5 R2", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-004", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-006", + "source_status": "RP-04 SS5.1 P4; SYN-03 SS6.4", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-006", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-007", + "source_status": "RP-02 SS5.5; SYN-02 SS5.5 R1", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-007", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-008", + "source_status": "RP-02 SS6.3; SYN-02 SS4.5, SS5.3, SS5.5 R5", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-008", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-009", + "source_status": "RP-02 SS6.2, SS6.4; SYN-02 SS5.4", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-009", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-010", + "source_status": "RP-02 SS5.7, SS6.4", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-010", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-012", + "source_status": "RP-05 SS4.3, SS7.1; SYN-02 SS4.3; SYN-03 SS5.1-5.3", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-012", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-013", + "source_status": "RP-02 SS5.1, SS6.1; SYN-02 SS4.1, SS5.2", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-013", + "disposition": "owned" + }, + { + "irf_id": "IRF-RES-014", + "source_status": "RP-07 SS5.5, SS7 I4; SYN-02 SS5.5 R3; SYN-04 SS5.2", + "owner_kind": "irf", + "owner_ref": "irf:IRF-RES-014", + "disposition": "owned" + }, + { + "irf_id": "IRF-SEC-002", + "source_status": "Browser login", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SEC-005", + "source_status": "Browser login", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SEC-011", + "source_status": "Rotation precedes any public disclosure", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SEC-012", + "source_status": "Rotation precedes Phase B `--execute`", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SYS-009", + "source_status": "Human action: 2 min at github.com/settings/notifications + Gmail", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SYS-011", + "source_status": "GoDaddy access", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SYS-087", + "source_status": "IRF-SYS-085", + "owner_kind": "irf", + "owner_ref": "irf:IRF-SYS-087", + "disposition": "owned" + }, + { + "irf_id": "IRF-SYS-137", + "source_status": "Google Takeout delivery", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-SYS-156", + "source_status": "None (Human action: ~30min bulk + ~1-2h explicit-12 triage)", + "owner_kind": "lever", + "owner_ref": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "disposition": "human_gate" + }, + { + "irf_id": "IRF-TAX-VAC-001", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-TAX-VAC-001", + "disposition": "owned" + }, + { + "irf_id": "IRF-THE-VAC-004", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-THE-VAC-004", + "disposition": "owned" + }, + { + "irf_id": "IRF-VAC-001a", + "source_status": "None", + "owner_kind": "irf", + "owner_ref": "irf:IRF-VAC-001a", + "disposition": "owned" + } + ], + "predicate": "python3 -c \"import json; p=json.load(open('docs/receipts/irf-p0-owner-classification-20260808.json')); rows={r['irf_id']:r for r in p['rows']}; ids=set(p['human_gate_irf_ids']); assert p['denominator']==p['classified']==len(rows)==41; assert not p['unowned'] and all(rows[i]['owner_kind']=='lever' and rows[i]['owner_ref']==p['human_gate_owner'] and rows[i]['disposition']=='human_gate' for i in ids)\"", + "human_gate_owner": "lever:L-IRF-P0-HUMAN-ACTIONS-20260808", + "human_gate_irf_ids": [ + "IRF-APP-087", + "IRF-APP-088", + "IRF-CRP-018", + "IRF-III-035", + "IRF-INST-001", + "IRF-OPS-061", + "IRF-PRT-027", + "IRF-PRT-028", + "IRF-PRT-060", + "IRF-PRT-061", + "IRF-SEC-002", + "IRF-SEC-005", + "IRF-SEC-011", + "IRF-SEC-012", + "IRF-SYS-009", + "IRF-SYS-011", + "IRF-SYS-137", + "IRF-SYS-156" + ] +} diff --git a/his-hand-levers.json b/his-hand-levers.json index c585c5328..9722ad571 100644 --- a/his-hand-levers.json +++ b/his-hand-levers.json @@ -1104,6 +1104,116 @@ ], "note": "Follow-on the fleet OWNS (not yours): the keeper should answer a quota wall with a structured, machine-readable code instead of an undifferentiated 500. cli/src/limen/conduct/client.py currently classifies this condition on the rejection prose — a documented, narrow exemption from the estate's own rule that callers classify on `status`, taken because a Cloudflare storage refusal carries no other signal. Once web/worker returns a code (and is deployed via wrangler, which does NOT happen on merge), the prose match becomes a compatibility fallback.", "issue": 2054 + }, + { + "id": "L-CLOUD-BULK-PR-CLOSE-N75", + "label": "Authorize the N75 terminal cleanup of 57 abandoned Jules draft PRs in organvm-iv-taxis/petasum-super-petasum.", + "owner": "yours", + "cost": "one bounded review of the live 57-PR denominator, then one bulk close operation with a receipt", + "unlocks": "removes the 7-8 month abandoned bot-draft cohort without allowing a cloud routine to perform a destructive external mutation from a stale count", + "source_task": "Jules backlog curation N75 (organvm/session-meta#29), expanded on 2026-08-06 from 11 to 57 live Jules drafts; umbrella closeout organvm/limen#2120", + "gate": "closing 57 GitHub pull requests is a destructive external-state mutation and requires explicit human authorization", + "status": "open", + "added": "2026-08-08", + "steps": [ + "Re-enumerate open draft PRs in organvm-iv-taxis/petasum-super-petasum authored by app/google-labs-jules; do not trust the historical count if live state differs.", + "Confirm every target is an abandoned Jules draft and that none has a human review, requested continuation, or unique unpreserved work.", + "Authorize the bounded close batch; attach the exact PR-number denominator and GitHub receipt to organvm/session-meta#29." + ], + "note": "Predicate after authorization: the exact reviewed target set is closed and the receipt records every result; unrelated drafts and all human-authored PRs remain untouched.", + "issue": 2120 + }, + { + "id": "L-CLOUD-EXTERNAL-GOVERNANCE-N77", + "label": "Disposition external collaborator PR 4444J99/victoroff-os#327, which proposes canonical repository authority.", + "owner": "yours", + "cost": "read one governance PR and choose merge, request-changes, or close with a public rationale", + "unlocks": "resolves N77 without allowing an agent to accept or reject an external contributor's authority claim on your behalf", + "source_task": "Jules backlog curation N77 (organvm/session-meta#29) from stale-PR-sweep evidence; umbrella closeout organvm/limen#2120", + "gate": "an external collaborator's governance proposal is a public authority and relationship decision", + "status": "open", + "added": "2026-08-08", + "steps": [ + "Read https://github.com/4444J99/victoroff-os/pull/327 and compare its canonical-authority claim with the current repository and ecosystem governance doctrine.", + "Post the chosen disposition and rationale on the PR.", + "Merge only if the authority claim is correct and required checks pass; otherwise request bounded changes or close." + ], + "note": "No agent may silently merge, close, or leave this governance proposal without the named human disposition.", + "issue": 2120 + }, + { + "id": "L-CLOUD-SESSION-SCOPE-EXPANSION", + "label": "Expand the hosted cloud-routine GitHub scope from session-meta-only to the current canonical sources.", + "owner": "yours", + "cost": "one hosted-session repository-scope update and one subsequent routine run", + "unlocks": "lets fleet audit, IRF surface, Jules curation, and disposition triage read organvm/limen plus organvm/organvm-corpvs-testamentvm instead of repeating access-denied prose", + "source_task": "cloud routine access failures recorded in organvm/session-meta#29, #31, #34, #79 and attached-alert closeout organvm/limen#2120", + "gate": "changing a hosted account/session allowed-repository set is an external access-governance action", + "status": "open", + "added": "2026-08-08", + "steps": [ + "Add organvm/limen and organvm/organvm-corpvs-testamentvm to the hosted routine session's allowed repositories; do not add the obsolete a-organvm route.", + "Run one bounded fleet-audit or Jules-curation delivery.", + "Done when the delivery emits CloudRoutineReceiptV1 observations from both canonical sources with no access-denied finding." + ], + "note": "Private repository contents and credentials remain out of receipts; scope expansion grants read access only to the named canonical sources unless separately authorized.", + "issue": 2120 + }, + { + "id": "L-CLOUD-ARCHIVE-ENTERPRISE-PLUGIN-N80", + "label": "Archive organvm-iii-ergon/enterprise-plugin so GitHub matches its repository-owned seed lifecycle.", + "owner": "yours", + "cost": "one GitHub repository archive setting change after final confirmation", + "unlocks": "resolves N80's four-day description/archive mismatch using the authority already declared by the repository", + "source_task": "N80 in organvm/session-meta#29; seed.yaml declares implementation_status=ARCHIVED, promotion_status=ARCHIVED, tier=archive; existing owner issue organvm-iii-ergon/enterprise-plugin#1", + "gate": "archiving a GitHub repository changes external repository capabilities and remains a human-gated account setting", + "status": "open", + "added": "2026-08-08", + "steps": [ + "Confirm seed.yaml on the default branch still declares implementation_status and promotion_status ARCHIVED.", + "Archive with: gh api --method PATCH repos/organvm-iii-ergon/enterprise-plugin -F archived=true", + "Verify: test \"$(gh repo view organvm-iii-ergon/enterprise-plugin --json isArchived --jq .isArchived)\" = true, then attach the receipt to organvm-iii-ergon/enterprise-plugin#1." + ], + "note": "Do not remove the [ARCHIVED] description prefix: the repository-owned seed resolves the mismatch in favor of archived=true. The shared-checkout admission hook denied the attempted GitHub PATCH before delivery, so no external state changed in this session.", + "issue": 2120 + }, + { + "id": "L-LAUNCHDARKLY-OAUTH-CONSENT", + "label": "Complete LaunchDarkly hosted MCP browser consent once, but only if the semantic MCP probe reports auth_needed after the split-owned hosted configuration lands.", + "owner": "yours", + "cost": "one bounded browser consent and Codex restart; zero secrets pasted or stored", + "unlocks": "authenticated LaunchDarkly hosted tool discovery instead of a transport-only startup warning", + "source_task": "attached-alert closeout #2120; domus-genoma PR #366; limen PR #2124", + "gate": "OAuth browser consent acts as your hosted LaunchDarkly identity and cannot be completed headlessly on your behalf", + "condition": "active only when codex mcp list --json reports auth_needed for launchdarkly", + "status": "open", + "added": "2026-08-08", + "steps": [ + "After the split-owned config is applied, run the semantic health probe and stop here if launchdarkly is already authenticated.", + "If and only if it reports auth_needed, run: codex mcp login launchdarkly and approve the hosted LaunchDarkly OAuth request in the browser.", + "Restart Codex, then verify codex mcp list --json reports authenticated and the hosted LaunchDarkly tool catalog is discoverable.", + "Attach the green semantic-auth receipt to organvm/limen#2120; never add an SDK key, bearer token, or local npx server." + ], + "note": "The hosted URL and OAuth model are source-owned by domus-genoma. Reachable transport is not proof of authentication; this lever owns only the consent transition when the semantic status requires it.", + "issue": 2120 + }, + { + "id": "L-IRF-P0-HUMAN-ACTIONS-20260808", + "label": "Disposition the 18 IRF P0 rows whose next step is a human identity, credential, purchase, relationship, or governance action.", + "owner": "yours", + "cost": "review the named 18-row denominator and discharge each IRF through its original owner evidence", + "unlocks": "keeps the IRF P0 denominator truthful: human actions remain visible in the lever registry instead of being counted as owned by self-referential IRF IDs", + "source_task": "IRF P0 surface receipt attached to organvm/limen#2120; exact IDs are tracked in docs/receipts/irf-p0-owner-classification-20260808.json", + "gate": "the rows require human identity, credentials, spend, relationship judgment, or governance choices that agents cannot exercise", + "status": "open", + "added": "2026-08-08", + "steps": [ + "Read human_gate_irf_ids in docs/receipts/irf-p0-owner-classification-20260808.json; that versioned 18-ID set is the denominator.", + "For each ID, use its original IRF owner to perform or explicitly decline the human action and attach the durable receipt there.", + "Discharge this lever only when every named IRF has a terminal owner receipt and the classification predicate passes." + ], + "note": "This umbrella lever does not merge the underlying decisions. It provides one durable human surface while each IRF retains its own source lineage and evidence.", + "issue": 2120 } ] } diff --git a/institutio/governance/gates.yaml b/institutio/governance/gates.yaml index 316b19833..3f72c249e 100644 --- a/institutio/governance/gates.yaml +++ b/institutio/governance/gates.yaml @@ -736,7 +736,7 @@ gates: note: "Format-check the same estate ruff-lint lints; ran unregistered in pr-gate until the scoped rewrite (issue #1048). Same #1658 version lock as ruff-lint; same 2026-08-06 apps/organs extension." pytest-cli: command: "bash scripts/run-pytest-hermetic.sh cli/tests -q -n auto" - paths: ["cli/**", "ianva/**", "his-hand-levers.json", "cloud-routines.json"] + paths: ["cli/**", "cli/tests/test_cloud_routine.py", "ianva/**", "his-hand-levers.json", "cloud-routines.json", "spec/contracts/cloud-routine-receipt-v1.schema.json", "scripts/cloud-routine-ingest.py", "scripts/check-cloud-routine-ingest.py", "docs/receipts/cloud-routine-findings-20260808.json", "docs/receipts/cloud-routine-lineage.json", "docs/receipts/irf-p0-owner-classification-20260808.json"] tier: heavy serialize: true timeout_seconds: 1500 diff --git a/institutio/governance/parameters.yaml b/institutio/governance/parameters.yaml index f2ff3509c..6d45a9ec8 100644 --- a/institutio/governance/parameters.yaml +++ b/institutio/governance/parameters.yaml @@ -5157,6 +5157,12 @@ parameters: self_update: none owner: limen-daemon note: "Per-description character cap the Codex slimmer distills to. Lower it if Codex still warns that descriptions were shortened; raise it to preserve more of each description. The knob, not a hardcode." + LIMEN_CLOUD_ROUTINE_INGEST_APPLY: + default: "0" + env: LIMEN_CLOUD_ROUTINE_INGEST_APPLY + self_update: none + owner: continuity + note: "Arms scripts/cloud-routine-ingest.py --apply. The default dry-run validates and classifies CloudRoutineReceiptV1 deliveries without emitting work; 1 permits only novel new_work dispositions to enter TABVLARIVS as idempotent upsert tickets." LIMEN_ROUTINE_FRESHNESS: default: "1" # ON — audit cloud-routine delivery freshness each beat env: LIMEN_ROUTINE_FRESHNESS diff --git a/mcp/src/limen_mcp/intake.py b/mcp/src/limen_mcp/intake.py index bbbf6d8ac..3a9431006 100644 --- a/mcp/src/limen_mcp/intake.py +++ b/mcp/src/limen_mcp/intake.py @@ -143,6 +143,45 @@ def is_executable_predicate(value: Any) -> bool: if command_index >= len(argv): return False first = argv[command_index] + if first in {"bash", "sh", "zsh"}: + index = command_index + 1 + value_options = {"-o", "+o", "--rcfile", "--init-file"} + while index < len(argv): + option = argv[index] + if option == "--" or not option.startswith("-"): + # After a script operand, later -c-like values are positional arguments. + break + if option in value_options: + if index + 1 >= len(argv) or argv[index + 1].startswith("-"): + return False + index += 2 + continue + if option.startswith(("--rcfile=", "--init-file=")): + index += 1 + continue + short_flags = option[1:] if not option.startswith("--") else "" + if short_flags and "o" in short_flags: + # Ambiguous clusters such as -oc are safer to reject than to misparse. + if "c" in short_flags: + return False + if index + 1 >= len(argv) or argv[index + 1].startswith("-"): + return False + index += 2 + continue + combined_shell_option = bool(short_flags and "c" in short_flags) + if option in {"-c", "-lc", "-ic", "--command"} or combined_shell_option: + if index + 1 >= len(argv): + return False + program = argv[index + 1] + if ( + any(token in program for token in (";", "|", "&", "$(", "`")) + or "\\n" in program + or "\\r" in program + or (("$" + "'") in command and ("\\n" in command or "\\r" in command)) + ): + return False + break + index += 1 return bool(first in EXECUTABLES or "/" in first or first.endswith((".py", ".sh"))) diff --git a/scripts/check-cloud-routine-ingest.py b/scripts/check-cloud-routine-ingest.py new file mode 100644 index 000000000..96805f324 --- /dev/null +++ b/scripts/check-cloud-routine-ingest.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Verify the CloudRoutineReceiptV1 contract and current owned denominator.""" + +from __future__ import annotations + +import importlib.util +import json +import sys +from pathlib import Path + +from jsonschema import Draft202012Validator, FormatChecker + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "cli" / "src")) + +from limen.cloud_routine import CloudRoutineReceiptV1 # noqa: E402 + + +def _load_ingest_module(): + path = ROOT / "scripts" / "cloud-routine-ingest.py" + spec = importlib.util.spec_from_file_location("cloud_routine_ingest_check", path) + if spec is None or spec.loader is None: + raise RuntimeError(f"cannot load {path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def validate_irf_receipt( + irf: object, + *, + active_levers: set[str], +) -> list[str]: + """Derive the complete 41-row ownership partition from row-level evidence.""" + failures: list[str] = [] + if not isinstance(irf, dict): + return ["IRF receipt must be an object"] + rows = irf.get("rows") + if not isinstance(rows, list): + return ["IRF rows must be a list"] + valid_rows = [row for row in rows if isinstance(row, dict)] + if len(valid_rows) != len(rows): + failures.append("IRF rows contain a non-object entry") + by_id = { + str(row.get("irf_id")): row + for row in valid_rows + if isinstance(row.get("irf_id"), str) and row.get("irf_id") + } + if not ( + irf.get("denominator") + == irf.get("classified") + == len(rows) + == len(by_id) + == 41 + ): + failures.append("IRF denominator/classification is not exactly 41 unique rows") + if irf.get("unowned") != []: + failures.append(f"IRF receipt has unowned rows: {irf.get('unowned')}") + + declared_human = irf.get("human_gate_irf_ids") + human_ids = ( + {str(irf_id) for irf_id in declared_human} + if isinstance(declared_human, list) + else set() + ) + human_owner = irf.get("human_gate_owner") + derived_human: set[str] = set() + for irf_id, row in by_id.items(): + owner_kind = row.get("owner_kind") + owner_ref = row.get("owner_ref") + disposition = row.get("disposition") + if irf_id in human_ids: + derived_human.add(irf_id) + if ( + owner_kind != "lever" + or owner_ref != human_owner + or disposition != "human_gate" + ): + failures.append(f"IRF human-gate ownership drift: {irf_id}") + elif ( + owner_kind != "irf" + or owner_ref != f"irf:{irf_id}" + or disposition != "owned" + ): + failures.append(f"IRF owned-row ownership drift: {irf_id}") + if derived_human != human_ids: + failures.append("IRF human-gate ID set does not match the row partition") + + # A non-empty human partition must remain attached to a live lever. Once every + # human action is discharged and rows are reclassified as ordinary owned work, + # the empty partition is the terminal green state rather than a false failure. + if human_ids: + owner_id = str(human_owner or "").removeprefix("lever:") + if owner_id not in active_levers: + failures.append(f"IRF human-gate lever is not active: {owner_id or ''}") + return failures + + +def main() -> int: + failures: list[str] = [] + schema_path = ROOT / "spec" / "contracts" / "cloud-routine-receipt-v1.schema.json" + receipt_path = ROOT / "docs" / "receipts" / "cloud-routine-findings-20260808.json" + irf_path = ROOT / "docs" / "receipts" / "irf-p0-owner-classification-20260808.json" + lever_path = ROOT / "his-hand-levers.json" + + try: + schema = json.loads(schema_path.read_text(encoding="utf-8")) + raw_receipts = json.loads(receipt_path.read_text(encoding="utf-8")) + validator = Draft202012Validator(schema, format_checker=FormatChecker()) + if not isinstance(raw_receipts, list) or len(raw_receipts) != 11: + failures.append("current cloud-routine denominator is not exactly 11 receipts") + raw_receipts = raw_receipts if isinstance(raw_receipts, list) else [] + receipts: list[CloudRoutineReceiptV1] = [] + for index, raw in enumerate(raw_receipts): + schema_errors = sorted( + validator.iter_errors(raw), + key=lambda error: list(error.absolute_path), + ) + failures.extend( + f"receipt[{index}] schema: {error.message}" for error in schema_errors + ) + try: + receipts.append(CloudRoutineReceiptV1.model_validate(raw)) + except Exception as exc: + failures.append(f"receipt[{index}] model: {exc}") + if len(receipts) == len(raw_receipts): + ingest = _load_ingest_module() + ingest.validate_routine_ids( + receipts, + manifest_path=ROOT / "cloud-routines.json", + ) + ingest.validate_human_gate_owners( + receipts, + lever_path=lever_path, + ) + if any(receipt.disposition == "new_work" for receipt in receipts): + failures.append( + "current cloud-routine denominator contains novel new_work; " + "the broker task upsert is not yet durably classified" + ) + except Exception as exc: + failures.append(f"receipt contract: {exc}") + + try: + irf = json.loads(irf_path.read_text(encoding="utf-8")) + active_levers = _load_ingest_module().active_lever_ids(lever_path) + failures.extend( + validate_irf_receipt( + irf, + active_levers=active_levers, + ) + ) + except Exception as exc: + failures.append(f"IRF denominator: {exc}") + + if failures: + for failure in failures: + print(f"FAIL: {failure}") + return 1 + print( + "OK: CloudRoutineReceiptV1 schema/model/owner parity; " + "11 current receipts and 41 IRF P0 rows are durably classified" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/cloud-routine-ingest.py b/scripts/cloud-routine-ingest.py new file mode 100644 index 000000000..46cebedc8 --- /dev/null +++ b/scripts/cloud-routine-ingest.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python3 +"""Validate CloudRoutineReceiptV1 deliveries and submit novel work via TABVLARIVS.""" + +from __future__ import annotations + +import argparse +import json +import os +import re +import sys +from datetime import datetime +from pathlib import Path + +from pydantic import ValidationError + +ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT / "cli" / "src")) + +from limen.cloud_routine import ( + CloudRoutineReceiptV1, + plan_task_upserts, + task_id_for, +) # noqa: E402 +from limen.io import load_limen_file # noqa: E402 +from limen.tabularius import pending_task_ids, submit_task_upsert # noqa: E402 + + +TERMINAL_LEVER_STATUSES = frozenset({"discharged", "retired", "done", "closed"}) + + +def _objects_from_path(path: Path) -> list[object]: + raw = path.read_text(encoding="utf-8") + try: + payload = json.loads(raw) + except json.JSONDecodeError: + objects = [ + json.loads(line) + for line in raw.splitlines() + if line.strip() + ] + else: + objects = payload if isinstance(payload, list) else [payload] + if not objects: + raise ValueError(f"{path}: receipt delivery is empty") + return objects + + +def _lever_states(path: Path) -> dict[str, str]: + payload = json.loads(path.read_text(encoding="utf-8")) + levers = payload.get("levers") if isinstance(payload, dict) else None + if not isinstance(levers, list): + raise ValueError("human-lever registry must contain a levers list") + states: dict[str, str] = {} + for lever in levers: + if not isinstance(lever, dict) or not isinstance(lever.get("id"), str): + continue + status = str(lever.get("status") or "").strip().lower() + # Legacy active levers often omit status; only an explicit discharge closes one. + if lever.get("discharged"): + status = "discharged" + states[str(lever["id"])] = status + return states + + +def active_lever_ids(path: Path) -> set[str]: + """Return registered levers that still represent live human ownership.""" + return { + lever_id + for lever_id, status in _lever_states(path).items() + if status not in TERMINAL_LEVER_STATUSES + } + + + +def latest_receipts_by_lineage( + receipts: list[CloudRoutineReceiptV1], +) -> list[CloudRoutineReceiptV1]: + """Keep only the newest observation for live owner resolution.""" + latest: dict[str, CloudRoutineReceiptV1] = {} + for receipt in receipts: + lineage_id = task_id_for(receipt) + previous = latest.get(lineage_id) + if previous is None: + latest[lineage_id] = receipt + continue + if receipt.observed_at == previous.observed_at: + if receipt != previous: + raise ValueError( + "conflicting cloud-routine observations share the same " + f"timestamp for {lineage_id}" + ) + continue + if receipt.observed_at > previous.observed_at: + latest[lineage_id] = receipt + return list(latest.values()) + +def validate_human_gate_owners( + receipts: list[CloudRoutineReceiptV1], + *, + lever_path: Path, +) -> None: + """Reject human gates whose named durable lever does not exist.""" + human_gate_receipts = [ + receipt for receipt in receipts if receipt.disposition == "human_gate" + ] + if not human_gate_receipts: + return + states = _lever_states(lever_path) + owner_ids = { + (receipt.owner_ref or "").removeprefix("lever:") + for receipt in human_gate_receipts + } + missing = sorted(owner_ids - states.keys()) + if missing: + raise ValueError( + "human_gate owner_ref does not resolve in his-hand-levers.json: " + + ", ".join(missing) + ) + terminal = sorted( + lever_id + for lever_id in owner_ids + if states[lever_id] in TERMINAL_LEVER_STATUSES + ) + if terminal: + raise ValueError( + "human_gate owner_ref resolves only to a terminal/inactive lever: " + + ", ".join(terminal) + ) + + +def validate_lever_owners( + receipts: list[CloudRoutineReceiptV1], + *, + lever_path: Path, +) -> None: + """Reject any material receipt that names a missing or terminal lever.""" + lever_receipts = [ + receipt + for receipt in receipts + if (receipt.owner_ref or "").startswith("lever:") + ] + if not lever_receipts: + return + states = _lever_states(lever_path) + owner_ids = { + (receipt.owner_ref or "").removeprefix("lever:") + for receipt in lever_receipts + } + missing = sorted(owner_ids - states.keys()) + if missing: + raise ValueError( + "lever owner_ref does not resolve in his-hand-levers.json: " + + ", ".join(missing) + ) + terminal = sorted( + lever_id + for lever_id in owner_ids + if states[lever_id] in TERMINAL_LEVER_STATUSES + ) + if terminal: + raise ValueError( + "lever owner_ref resolves only to a terminal/inactive lever: " + + ", ".join(terminal) + ) + + +def registered_routine_ids(path: Path = ROOT / "cloud-routines.json") -> set[str]: + payload = json.loads(path.read_text(encoding="utf-8")) + routines = payload.get("routines") if isinstance(payload, dict) else None + if not isinstance(routines, list): + raise ValueError("cloud-routines.json must contain a routines list") + routine_ids = { + str(routine.get("name")) + for routine in routines + if isinstance(routine, dict) and isinstance(routine.get("name"), str) + } + if not routine_ids: + raise ValueError("cloud-routines.json contains no routine names") + return routine_ids + + +def validate_routine_ids( + receipts: list[CloudRoutineReceiptV1], + *, + manifest_path: Path = ROOT / "cloud-routines.json", +) -> None: + registered = registered_routine_ids(manifest_path) + unknown = sorted({receipt.routine_id for receipt in receipts} - registered) + if unknown: + raise ValueError("routine_id is absent from cloud-routines.json: " + ", ".join(unknown)) + + +def load_receipts( + paths: list[Path], + *, + lever_path: Path = ROOT / "his-hand-levers.json", + manifest_path: Path = ROOT / "cloud-routines.json", +) -> list[CloudRoutineReceiptV1]: + """Validate every input and resolve every human owner before task emission.""" + receipts: list[CloudRoutineReceiptV1] = [] + for path in paths: + receipts.extend( + CloudRoutineReceiptV1.model_validate(item) + for item in _objects_from_path(path) + ) + validate_routine_ids(receipts, manifest_path=manifest_path) + latest = latest_receipts_by_lineage(receipts) + validate_human_gate_owners( + latest, + lever_path=lever_path, + ) + validate_lever_owners(latest, lever_path=lever_path) + return receipts + + +def _merge_historical_observation( + historical_ids: set[str], + observed_at: dict[str, datetime], + task_id: str, + stamp: datetime | None, +) -> None: + historical_ids.add(task_id) + if stamp is None: + return + previous = observed_at.get(task_id) + if previous is None or stamp > previous: + observed_at[task_id] = stamp + + +def _tracked_cloud_lineage_path() -> Path: + return ROOT / "docs" / "receipts" / "cloud-routine-lineage.json" + + +def _tracked_cloud_task_state() -> tuple[set[str], dict[str, datetime]]: + """Read append-only cloud lineage from a tracked receipt envelope.""" + source = _tracked_cloud_lineage_path() + historical_ids: set[str] = set() + observed_at: dict[str, datetime] = {} + if not source.is_file(): + return historical_ids, observed_at + try: + payload = json.loads(source.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError) as exc: + raise ValueError(f"tracked cloud lineage is unreadable: {source}") from exc + entries = payload.get("entries") if isinstance(payload, dict) else None + if not isinstance(entries, list): + raise ValueError(f"tracked cloud lineage entries must be a list: {source}") + for index, entry in enumerate(entries): + if not isinstance(entry, dict): + raise ValueError(f"tracked cloud lineage entry[{index}] is not an object") + try: + receipt = CloudRoutineReceiptV1.model_validate(entry) + except ValidationError as exc: + raise ValueError(f"tracked cloud lineage entry[{index}] is invalid: {exc}") from exc + _merge_historical_observation( + historical_ids, + observed_at, + task_id_for(receipt), + receipt.observed_at, + ) + return historical_ids, observed_at + + +def _append_cloud_lineage_receipt(receipt: CloudRoutineReceiptV1) -> None: + """Append one accepted receipt to the tracked duplicate boundary idempotently.""" + source = _tracked_cloud_lineage_path() + payload: dict[str, object] = { + "schema_version": "limen.cloud_routine_lineage.v1", + "description": "Tracked append-only cloud receipt lineage; consumers may use this as the durable duplicate boundary.", + "entries": [], + } + if source.is_file(): + try: + loaded = json.loads(source.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError) as exc: + raise ValueError(f"tracked cloud lineage is unreadable: {source}") from exc + if not isinstance(loaded, dict) or not isinstance(loaded.get("entries"), list): + raise ValueError(f"tracked cloud lineage entries must be a list: {source}") + payload = loaded + entries = payload.get("entries") + if not isinstance(entries, list): + raise ValueError(f"tracked cloud lineage entries must be a list: {source}") + existing: list[CloudRoutineReceiptV1] = [] + for index, entry in enumerate(entries): + try: + existing.append(CloudRoutineReceiptV1.model_validate(entry)) + except ValidationError as exc: + raise ValueError(f"tracked cloud lineage entry[{index}] is invalid: {exc}") from exc + if any(previous == receipt for previous in existing): + return + entries.append(receipt.model_dump(mode="json")) + source.parent.mkdir(parents=True, exist_ok=True) + temporary = source.with_suffix(source.suffix + ".tmp") + temporary.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") + temporary.replace(source) + + +def _receipt_for_task(task_id: str, receipts: list[CloudRoutineReceiptV1]) -> CloudRoutineReceiptV1: + matches = [ + receipt + for receipt in latest_receipts_by_lineage(receipts) + if task_id == task_id_for(receipt) or task_id.startswith(task_id_for(receipt) + "-") + ] + if len(matches) != 1: + raise ValueError(f"cannot map submitted cloud task to one receipt: {task_id}") + return matches[0] + + +def _historical_cloud_task_state(tasks_path: Path) -> tuple[set[str], dict[str, datetime]]: + """Combine tracked lineage with legacy keeper tickets after board pruning.""" + historical_ids, observed_at = _tracked_cloud_task_state() + archive = tasks_path.parent / "logs" / "tickets" / "archive" + observed_pattern = re.compile(r"observed_at=([^;]+)") + if not archive.is_dir(): + return historical_ids, observed_at + for path in sorted(archive.glob("*.json")): + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError): + continue + if not isinstance(payload, dict): + continue + patch = payload.get("patch") if isinstance(payload.get("patch"), dict) else {} + candidates = { + value + for value in (payload.get("task_id"), patch.get("id")) + if isinstance(value, str) and value.startswith("CLOUD-") + } + if not candidates: + continue + context = str(patch.get("context") or "") + match = observed_pattern.search(context) + stamp = None + if match: + try: + stamp = datetime.fromisoformat(match.group(1).replace("Z", "+00:00")) + except ValueError: + stamp = None + for task_id in candidates: + _merge_historical_observation( + historical_ids, + observed_at, + task_id, + stamp, + ) + return historical_ids, observed_at + + +def _tasks_path() -> Path: + """Resolve the read-only board projection independently of this script checkout.""" + explicit = os.environ.get("LIMEN_TASKS") + if explicit: + return Path(explicit).expanduser() + limen_root = os.environ.get("LIMEN_ROOT") + if limen_root: + return Path(limen_root).expanduser() / "tasks.yaml" + return ROOT / "tasks.yaml" + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="Ingest typed cloud-routine outcomes through TABVLARIVS." + ) + parser.add_argument("receipts", nargs="+", type=Path) + parser.add_argument("--apply", action="store_true") + parser.add_argument("--json", action="store_true") + args = parser.parse_args(argv) + + if args.apply and os.environ.get("LIMEN_CLOUD_ROUTINE_INGEST_APPLY", "0") != "1": + parser.error("--apply requires LIMEN_CLOUD_ROUTINE_INGEST_APPLY=1") + + try: + receipts = load_receipts(args.receipts) + except (OSError, json.JSONDecodeError, ValidationError, ValueError) as exc: + print(f"cloud-routine-ingest: invalid receipt: {exc}", file=sys.stderr) + return 2 + + tasks_path = _tasks_path() + board = load_limen_file(tasks_path) + active_statuses = { + "open", + "dispatched", + "in_progress", + "failed", + "failed_blocked", + "needs_human", + } + historical_observed_at: dict[str, datetime] = {} + historical_ids = {task.id for task in board.tasks} + for task in board.tasks: + context = str(task.context or "") + match = re.search(r"observed_at=([^;]+)", context) + if match: + try: + historical_observed_at[task.id] = datetime.fromisoformat( + match.group(1).replace("Z", "+00:00") + ) + except ValueError: + pass + try: + archived_ids, archived_observed_at = _historical_cloud_task_state(tasks_path) + except (OSError, ValueError) as exc: + print(f"cloud-routine-ingest: invalid tracked lineage: {exc}", file=sys.stderr) + return 2 + historical_ids.update(archived_ids) + historical_observed_at.update(archived_observed_at) + plan = plan_task_upserts( + receipts, + existing_ids=( + task.id for task in board.tasks if str(task.status) in active_statuses + ), + pending_ids=pending_task_ids(tasks_path), + historical_ids=historical_ids, + historical_observed_at=historical_observed_at, + ) + + submitted: list[str] = [] + submit_error: str | None = None + if args.apply: + for task in plan.tasks: + try: + receipt = _receipt_for_task(task.id, receipts) + submit_task_upsert( + tasks_path, + task, + agent="cloud-routine-ingest", + session_id=os.environ.get( + "LIMEN_SESSION_ID", + "cloud-routine-ingest", + ), + ) + submitted.append(task.id) + _append_cloud_lineage_receipt(receipt) + except Exception as exc: + submit_error = f"{task.id}: {exc}" + break + + payload = { + "schema_version": "limen.cloud_routine_ingest_result.v1", + "mode": "apply" if args.apply else "dry-run", + "receipts": len(receipts), + "classified": plan.classified, + "duplicates": plan.duplicates, + "new_work": [task.id for task in plan.tasks], + "submitted": submitted, + "submit_error": submit_error, + } + if args.json: + print(json.dumps(payload, sort_keys=True)) + else: + print( + "cloud-routine-ingest: " + f"{len(receipts)} receipt(s), " + f"{plan.classified} classified without new work, " + f"{plan.duplicates} duplicate(s), " + f"{len(plan.tasks)} novel task(s) " + f"[{payload['mode']}]" + ) + submitted_ids = set(submitted) + for task in plan.tasks: + if not args.apply: + verb = "would submit" + elif task.id in submitted_ids: + verb = "submitted" + else: + verb = "not submitted" + print(f" {verb} {task.id} -> {task.repo}") + if submit_error: + print(f" submit_error: {submit_error}", file=sys.stderr) + return 1 if submit_error else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/spec/contracts/cloud-routine-receipt-v1.schema.json b/spec/contracts/cloud-routine-receipt-v1.schema.json new file mode 100644 index 000000000..b47bbb0a1 --- /dev/null +++ b/spec/contracts/cloud-routine-receipt-v1.schema.json @@ -0,0 +1,195 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/organvm/limen/spec/contracts/cloud-routine-receipt-v1.schema.json", + "title": "CloudRoutineReceiptV1", + "description": "One recurring cloud-routine observation with stable ownership and executable closure truth.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "routine_id", + "observed_at", + "status", + "stable_finding_key", + "disposition", + "owner_ref", + "predicate" + ], + "properties": { + "schema_version": { + "const": "limen.cloud_routine_receipt.v1" + }, + "routine_id": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" + }, + "observed_at": { + "type": "string", + "format": "date-time", + "description": "Timezone-aware observation time; producers must not emit timestamps more than 300 seconds ahead of the consumer clock.", + "x-limen-max-future-skew-seconds": 300 + }, + "status": { + "enum": [ + "ok", + "finding", + "failed" + ] + }, + "stable_finding_key": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,255}$" + }, + "disposition": { + "enum": [ + "no_change", + "superseded", + "owned", + "new_work", + "human_gate" + ] + }, + "owner_ref": { + "anyOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 1024, + "pattern": "^(?=.*\\S)[^\\u0000]{1,1024}$" + }, + { + "type": "null" + } + ] + }, + "predicate": { + "type": "string", + "minLength": 1, + "maxLength": 8192, + "pattern": "^(?!.*`)(?!.*\"[^\"]*[$][(][^;|&]*[;|&])(?=(?:(?:[^'\";|&])|'[^']*'|\"[^\"]*\")*$)(?!.*(?:<[^>]+>|\\b(?:tbd|todo|fixme|replace[-_ ]me)\\b))(?=(?:[^']*'[^']*')*[^']*$)(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)(?!.*\\\\$)(?!.*[\\r\\n])\\s*(?:(?:command|env|sudo)\\s+|(?:[A-Za-z_][A-Za-z0-9_]*=[^\\s]+|-[^\\s]+)\\s+)*(?:\\[|bash|bundle|cargo|curl|gh|git|go|just|limen|make|node|nox|npm|pnpm|py\\.test|pytest|python|python3|ruby|sh|test|tox|uv|yarn|zsh|[^\\s]*/[^\\s]+|[^\\s]+\\.(?:py|sh))(?:\\s+.*)?$", + "$comment": "Kept in parity with CloudRoutineReceiptV1.validate_predicate: nonblank input, no placeholders, balanced quotes, no trailing escape or unquoted shell composition, clustered shell -c/-uc options with composition are rejected, and one admitted executable." + } + }, + "allOf": [ + { + "not": { + "properties": { + "predicate": { + "pattern": "(?:^|\\s)(?:bash|sh|zsh)\\s+[^\\r\\n]*-[^\\s]*c[^\\s]*\\s+[^\\r\\n]*[;|&]" + } + } + } + }, + { + "not": { + "properties": { + "predicate": { + "pattern": "(?:^|\\s)(?:bash|sh|zsh)\\s+[^\\r\\n]*-[^\\s]*c[^\\s]*\\s+[^\\r\\n]*[$]'[^']*\\\\(?:n|r)" + } + } + } + }, + { + "if": { + "properties": { + "status": { + "enum": [ + "finding", + "failed" + ] + } + } + }, + "then": { + "properties": { + "owner_ref": { + "type": "string", + "minLength": 1 + } + } + } + }, + { + "if": { + "allOf": [ + { + "properties": { + "status": { + "enum": [ + "finding", + "failed" + ] + } + } + }, + { + "properties": { + "disposition": { + "enum": [ + "no_change", + "superseded", + "owned" + ] + } + } + } + ] + }, + "then": { + "properties": { + "owner_ref": { + "type": "string", + "pattern": "^(?:lever:[A-Za-z0-9][A-Za-z0-9._-]{0,127}|irf:[A-Za-z0-9][A-Za-z0-9._:-]{0,127}|https://github\\.com/(?!\\.\\.?/)(?![A-Za-z0-9_.-]+/\\.\\.?/)[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/(?:issues|pull|actions/runs)/[0-9]+)$" + } + } + } + }, + { + "if": { + "properties": { + "disposition": { + "const": "new_work" + } + } + }, + "then": { + "properties": { + "status": { + "enum": [ + "finding", + "failed" + ] + }, + "owner_ref": { + "type": "string", + "pattern": "^(?!(?:\\.{1,2})/)(?!.*/(?:\\.{1,2})$)[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" + } + } + } + }, + { + "if": { + "properties": { + "disposition": { + "const": "human_gate" + } + } + }, + "then": { + "properties": { + "status": { + "enum": [ + "finding", + "failed" + ] + }, + "owner_ref": { + "type": "string", + "minLength": 1, + "pattern": "^lever:[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" + } + } + } + } + ] +} diff --git a/web/api/limen_intake.py b/web/api/limen_intake.py index bbbf6d8ac..3a9431006 100644 --- a/web/api/limen_intake.py +++ b/web/api/limen_intake.py @@ -143,6 +143,45 @@ def is_executable_predicate(value: Any) -> bool: if command_index >= len(argv): return False first = argv[command_index] + if first in {"bash", "sh", "zsh"}: + index = command_index + 1 + value_options = {"-o", "+o", "--rcfile", "--init-file"} + while index < len(argv): + option = argv[index] + if option == "--" or not option.startswith("-"): + # After a script operand, later -c-like values are positional arguments. + break + if option in value_options: + if index + 1 >= len(argv) or argv[index + 1].startswith("-"): + return False + index += 2 + continue + if option.startswith(("--rcfile=", "--init-file=")): + index += 1 + continue + short_flags = option[1:] if not option.startswith("--") else "" + if short_flags and "o" in short_flags: + # Ambiguous clusters such as -oc are safer to reject than to misparse. + if "c" in short_flags: + return False + if index + 1 >= len(argv) or argv[index + 1].startswith("-"): + return False + index += 2 + continue + combined_shell_option = bool(short_flags and "c" in short_flags) + if option in {"-c", "-lc", "-ic", "--command"} or combined_shell_option: + if index + 1 >= len(argv): + return False + program = argv[index + 1] + if ( + any(token in program for token in (";", "|", "&", "$(", "`")) + or "\\n" in program + or "\\r" in program + or (("$" + "'") in command and ("\\n" in command or "\\r" in command)) + ): + return False + break + index += 1 return bool(first in EXECUTABLES or "/" in first or first.endswith((".py", ".sh")))