Merge remote-tracking branch 'origin/main' into codex/leo-gcp-runtime-security-20260715
This commit is contained in:
commit
d299f1cdf5
53 changed files with 6628 additions and 634 deletions
|
|
@ -50,7 +50,7 @@ Name the canary before sending:
|
|||
|
||||
## Message Discipline
|
||||
|
||||
Use unique markers such as `WL-LIVE-TG-CORY-YYYYMMDD-Tn`.
|
||||
Use unique markers such as `WL-LIVE-TG-M3TAVERSAL-YYYYMMDD-Tn`.
|
||||
|
||||
Ask Leo for machine-checkable reply markers like:
|
||||
|
||||
|
|
@ -74,10 +74,14 @@ After each live Telegram canary:
|
|||
|
||||
## Current Known Proof
|
||||
|
||||
- Live memory and KB audit passed for marker `WL-LIVE-TG-CORY-20260709`.
|
||||
- Live staged write passed for `WL-LIVE-TG-CORY-20260709-T3`.
|
||||
- Readback passed for `WL-LIVE-TG-CORY-20260709-T4`.
|
||||
- Open-ended m3taversal-style read-only triage passed for the legacy marker
|
||||
`WL-LIVE-TG-CORY-OPEN-20260709`.
|
||||
- Live memory and KB audit are retained in
|
||||
`docs/reports/leo-working-state-20260709/telegram-live-canary-current.json`.
|
||||
- Live staged-write proof is retained in
|
||||
`docs/reports/leo-working-state-20260709/telegram-live-db-write-canary-20260709.json`.
|
||||
- Open-ended m3taversal-standard read-only triage is retained in
|
||||
`docs/reports/leo-working-state-20260709/telegram-live-open-ended-suite-current.md`.
|
||||
|
||||
Historical receipts contain immutable legacy marker IDs. Treat them only as
|
||||
evidence identifiers; never reuse them as a participant name or future marker.
|
||||
|
||||
Use `docs/reports/leo-working-state-20260709/current-truth-index.md` for artifact paths.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Prefer deterministic local surfaces before asking an LLM:
|
|||
- generated claim indexes from `lib/claim_index.py`;
|
||||
- search helpers in `lib/search.py`;
|
||||
- copied SQLite state through `teleo-db-operator`;
|
||||
- retained proof JSON in `.crabbox-results/` or `proof/`.
|
||||
- retained proof JSON at a caller-specified generated-output path.
|
||||
|
||||
Read outputs must include file paths, source paths, claim/entity IDs when available, and the exact query used.
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ If a runtime cannot implement one of these, record the missing tool as a blocker
|
|||
|
||||
## Expected Artifact
|
||||
|
||||
Write `.crabbox-results/kb-interop-proof.json` or a caller-specified proof path containing:
|
||||
Write `<proof-output>/kb-interop-proof.json` containing:
|
||||
|
||||
- runtime name;
|
||||
- model/provider if known;
|
||||
|
|
|
|||
|
|
@ -28,9 +28,10 @@ Create or update:
|
|||
- `AGENTS.md`: scope, repo boundaries, proof requirements;
|
||||
- `SOUL.md`: Rio or Theseus identity;
|
||||
- `TOOLS.md`: bounded tools only;
|
||||
- `skills/decision-engine-refinement/SKILL.md`;
|
||||
- `skills/living-ip-kb-interop/SKILL.md`;
|
||||
- `skills/teleo-db-operator/SKILL.md` only for read-only local copies unless explicitly authorized.
|
||||
- `<openclaw-workspace>/skills/decision-engine-refinement/SKILL.md`;
|
||||
- `<openclaw-workspace>/skills/living-ip-kb-interop/SKILL.md`;
|
||||
- `<openclaw-workspace>/skills/teleo-db-operator/SKILL.md` only for read-only
|
||||
local copies unless explicitly authorized.
|
||||
|
||||
## Tool Policy
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,22 @@ Do not describe a passing Hermes memory sync as canonical KB parity.
|
|||
- Persistent GCP `teleo_canonical` remains the older staging copy measured at
|
||||
`52,164` rows and `26` proposals. It has not been promoted or cut over.
|
||||
|
||||
## Open Least-Privilege Candidate
|
||||
|
||||
PR #148, `Scope GCP Leo runtime to least-privilege Cloud SQL access`, is open as
|
||||
of the 2026-07-15 skill-pack reconciliation. It proposes scoped runtime roles,
|
||||
secret access, fail-closed Cloud SQL behavior, deployment rollback, and positive
|
||||
plus negative permission checks. Those branch files and proposed live outcomes
|
||||
are candidate evidence only. Before using them, run:
|
||||
|
||||
```bash
|
||||
gh pr view 148 --json state,mergedAt,mergeCommit,headRefName,url
|
||||
```
|
||||
|
||||
Until the PR is merged and its runtime receipt passes, use only paths present on
|
||||
canonical `main`, keep persistent GCP classified as staging, do not promote it,
|
||||
and do not infer least-privilege cutover from a branch or dry run.
|
||||
|
||||
Primary retained proof:
|
||||
|
||||
- `docs/reports/leo-working-state-20260709/gcp-db-first-working-leo-20260714.md`
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ Prevent incorrect assumptions about where Leo/Teleo code, runtime state, DB stat
|
|||
- Hermes/leoclean runtime profile.
|
||||
- Postgres canonical DB.
|
||||
- `kb_stage` proposal ledger.
|
||||
- Retained proof artifacts under local `outputs/`.
|
||||
- Caller-specific external output directories, which are retained evidence but
|
||||
are not repository routes unless copied into the repo evidence pack.
|
||||
- Synced report artifacts under the VPS profile report directory.
|
||||
|
||||
## Current Known Path Map
|
||||
|
||||
- Canonical code/deployment repository: GitHub `living-ip/teleo-infrastructure`.
|
||||
- Repo-local evidence pack: `docs/reports/leo-working-state-20260709/`
|
||||
- VPS deploy/source area: `/opt/teleo-eval/workspaces/deploy-infra`
|
||||
- VPS deploy stamp: `/opt/teleo-eval/.last-deploy-sha`
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Run the deterministic source-to-proposal canary:
|
|||
```bash
|
||||
.venv/bin/python scripts/run_leo_local_ingestion_proposal_canary.py \
|
||||
--fixture fixtures/working-leo/document-ingestion-v1.json \
|
||||
--output outputs/leo-local-ingestion-proposal-canary-current.json
|
||||
--output /tmp/leo-local-ingestion-proposal-canary-current.json
|
||||
```
|
||||
|
||||
Require the source, claim, evidence, and proposal UUIDs to link exactly; claim
|
||||
|
|
@ -131,7 +131,8 @@ Read:
|
|||
|
||||
- `docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.json`
|
||||
- `docs/reports/leo-working-state-20260709/leo-source-composition-clone-checkpoint-current.json`
|
||||
- `outputs/leo-local-ingestion-proposal-canary-current.json`
|
||||
- `docs/reports/leo-working-state-20260709/leo-v3-document-source-lifecycle-current.md`
|
||||
- `docs/reports/leo-working-state-20260709/source-document-compiler-canary-20260713.md`
|
||||
|
||||
## Claim Ceiling
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
---
|
||||
name: teleo-leo-onboarding
|
||||
description: Use when a worker needs fast context on Teleo/Living IP, Leo, the product architecture, current VPS/GCP split, and the July 9 working-state evidence before doing Leo, Teleo, KB, Telegram, VPS, or GCP work.
|
||||
description: Use when a worker needs fast context on Teleo/Living IP, Leo architecture, VPS/GCP and Hermes runtime boundaries, database provenance, ingestion/apply/reconstruction, identity, testing, security, secrets, or incident recovery before doing Leo or Teleo work.
|
||||
---
|
||||
|
||||
# Teleo / Leo Onboarding
|
||||
|
||||
## Job
|
||||
|
||||
Orient the worker before action. Build a current, proof-linked understanding of the company/product, Leo's role, the infrastructure surfaces, and the exact claim ceiling.
|
||||
Orient the worker before action. Build a current, proof-linked understanding of
|
||||
the company/product, Leo's role, the infrastructure surfaces, and the exact
|
||||
claim ceiling without rediscovering the system from historical chat.
|
||||
|
||||
## Trigger Phrases
|
||||
|
||||
|
|
@ -17,6 +19,8 @@ Orient the worker before action. Build a current, proof-linked understanding of
|
|||
- "Fable handoff for Leo"
|
||||
- "before working on Teleo infra"
|
||||
- "explain the architecture"
|
||||
- "recover or reconstruct Leo"
|
||||
- "which skill should I use"
|
||||
|
||||
## Core Model
|
||||
|
||||
|
|
@ -46,6 +50,52 @@ Orient the worker before action. Build a current, proof-linked understanding of
|
|||
- `SOUL.md` is a rendered/runtime artifact. Direct file edits are not canonical
|
||||
identity changes without DB row and render/sync proof.
|
||||
|
||||
## Clean-Context First Commands
|
||||
|
||||
From the repository root, validate the pack without a virtual environment or
|
||||
network access:
|
||||
|
||||
```bash
|
||||
.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .
|
||||
```
|
||||
|
||||
Install exactly the manifest-indexed skills into an empty temporary agent skill
|
||||
root with:
|
||||
|
||||
```bash
|
||||
.agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py \
|
||||
--root . \
|
||||
--target /private/tmp/teleo-clean-agent/skills
|
||||
```
|
||||
|
||||
Installed skills still resolve repository-relative routes against the current
|
||||
`teleo-infrastructure` checkout. Keep the agent working directory at the repo
|
||||
root. For pytest, use `.venv/bin/python`; if `.venv` is absent, create it from
|
||||
`README.md` before running tests. Do not guess a bare `python` command.
|
||||
|
||||
Run the deterministic isolated acceptance canary with:
|
||||
|
||||
```bash
|
||||
.agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py \
|
||||
--root . \
|
||||
--output docs/reports/leo-working-state-20260709/skill-pack-clean-context-canary-current.json
|
||||
```
|
||||
|
||||
## Task Router
|
||||
|
||||
| Area | Skill | Canonical first route |
|
||||
|---|---|---|
|
||||
| Company, product, architecture | `teleo-leo-onboarding` | `docs/reports/leo-working-state-20260709/current-truth-index.md` |
|
||||
| VPS runtime and incidents | `teleo-vps-runtime-ops` | Fresh service, deploy, DB, and cleanup readbacks |
|
||||
| GCP and Cloud SQL | `teleo-gcp-parity-ops` | Current main evidence; keep open PR #148 candidate-only |
|
||||
| Hermes packaging/runtime | `nousresearch-hermes-agent` | `hermes-agent/` plus the live service-specific ops skill |
|
||||
| Database provenance | `teleo-infra-provenance` and `teleo-db-operator` | Separate Git, runtime, SQLite, Postgres, and retained proof |
|
||||
| Source ingestion and proposal/apply | `teleo-kb-db-change-workflow` | Hash-bound source, review, guarded apply, receipt |
|
||||
| Reconstruction and recovery | `teleo-reconstruction-recovery` | `docs/kb-rebuild-and-recompile.md` |
|
||||
| Identity and rendered soul | `working-leo-m3taversal-outcomes` | Canonical identity rows before `SOUL.md` |
|
||||
| Testing and proof tiers | `teleo-proof-handoff` | Exact tier, command, receipt, cleanup, claim ceiling |
|
||||
| Secrets | `private-password-storage` | Provider login first; never paste or retrieve a secret |
|
||||
|
||||
## Read First
|
||||
|
||||
From the repo root, read:
|
||||
|
|
@ -61,6 +111,7 @@ From the repo root, read:
|
|||
9. `.agents/skills/working-leo-m3taversal-outcomes/SKILL.md`
|
||||
10. `docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.md`
|
||||
11. `docs/reports/leo-working-state-20260709/gcp-cloud-sql-t3-live-readonly-current.md`
|
||||
12. `docs/kb-rebuild-and-recompile.md`
|
||||
|
||||
## Required Status Split
|
||||
|
||||
|
|
@ -82,6 +133,9 @@ Do not collapse GCP demo readiness into Telegram completion. Do not collapse pro
|
|||
- Do not production-apply DB packets unless explicitly authorized.
|
||||
- Do not expose secret contents.
|
||||
- Do not treat an old summary as current if a fresh readback is cheap.
|
||||
- PRs #146 and #147 are merged repository truth. PR #148 remains an open GCP
|
||||
least-privilege candidate until live `gh pr view 148` readback says otherwise;
|
||||
never route a deployment from its branch as though it were `main`.
|
||||
- Use `ssh -o BatchMode=yes teleo-gcp-staging true` to preflight passwordless
|
||||
GCP VM access. If the firewall route drifts, rediscover the authenticated
|
||||
non-secret route before declaring a blocker. The OIDC/IAP workflow is still
|
||||
|
|
|
|||
106
.agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py
Executable file
106
.agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py
Executable file
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Install the manifest-indexed Leo/Teleo skills into an empty skill root."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_MANIFEST = Path("docs/reports/leo-working-state-20260709/skill-pack-manifest.json")
|
||||
SKILL_NAME = re.compile(r"[a-z0-9-]{1,64}")
|
||||
|
||||
|
||||
def _tracked_skill_files(root: Path, skill_dir: Path) -> list[Path]:
|
||||
relative_dir = skill_dir.relative_to(root)
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(root), "ls-files", "-z", "--", str(relative_dir)],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(result.stderr.decode("utf-8", errors="replace"))
|
||||
files = [root / Path(item.decode("utf-8")) for item in result.stdout.split(b"\0") if item]
|
||||
if not files:
|
||||
raise ValueError(f"skill has no tracked files: {relative_dir}")
|
||||
for source in files:
|
||||
if source.is_symlink():
|
||||
raise ValueError(f"skill contains a symlink: {source.relative_to(root)}")
|
||||
if not source.is_file():
|
||||
raise ValueError(f"skill route is not a regular file: {source.relative_to(root)}")
|
||||
source.resolve().relative_to(skill_dir.resolve())
|
||||
return files
|
||||
|
||||
|
||||
def install(root: Path, manifest_path: Path, target: Path) -> dict[str, object]:
|
||||
manifest_file = root / manifest_path
|
||||
manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
|
||||
skills = manifest["skills"]
|
||||
if target.exists():
|
||||
raise ValueError(f"target already exists: {target}")
|
||||
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
stage = Path(tempfile.mkdtemp(prefix=".teleo-skill-pack-", dir=target.parent))
|
||||
installed: list[str] = []
|
||||
try:
|
||||
for entry in skills:
|
||||
name = entry["name"]
|
||||
if not isinstance(name, str) or SKILL_NAME.fullmatch(name) is None:
|
||||
raise ValueError(f"invalid skill name: {name!r}")
|
||||
expected_path = Path(".agents") / "skills" / name / "SKILL.md"
|
||||
if Path(entry["path"]) != expected_path:
|
||||
raise ValueError(f"skill path must be {expected_path}: {entry['path']!r}")
|
||||
if name in installed:
|
||||
raise ValueError(f"duplicate skill name: {name}")
|
||||
|
||||
source = root / expected_path.parent
|
||||
source.resolve().relative_to((root / ".agents" / "skills").resolve())
|
||||
if not source.is_dir():
|
||||
raise FileNotFoundError(source)
|
||||
destination = stage / name
|
||||
destination.resolve().relative_to(stage.resolve())
|
||||
for tracked_source in _tracked_skill_files(root, source):
|
||||
relative = tracked_source.relative_to(source)
|
||||
tracked_destination = destination / relative
|
||||
tracked_destination.resolve().relative_to(destination.resolve())
|
||||
tracked_destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(tracked_source, tracked_destination)
|
||||
installed.append(name)
|
||||
stage.rename(target)
|
||||
except Exception:
|
||||
shutil.rmtree(stage, ignore_errors=True)
|
||||
raise
|
||||
|
||||
return {
|
||||
"artifact": "leo_teleo_skill_pack_install",
|
||||
"status": "pass",
|
||||
"installed_skill_count": len(installed),
|
||||
"installed_skills": installed,
|
||||
"contains_secrets": False,
|
||||
"production_mutation_authorized": False,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, default=Path.cwd())
|
||||
parser.add_argument("--manifest", type=Path, default=DEFAULT_MANIFEST)
|
||||
parser.add_argument("--target", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
result = install(args.root.resolve(), args.manifest, args.target.resolve())
|
||||
except Exception as exc:
|
||||
result = {"artifact": "leo_teleo_skill_pack_install", "status": "fail", "error": str(exc)}
|
||||
sys.stdout.write(json.dumps(result, indent=2, sort_keys=True) + "\n")
|
||||
return 1
|
||||
sys.stdout.write(json.dumps(result, indent=2, sort_keys=True) + "\n")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
172
.agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py
Executable file
172
.agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py
Executable file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Run the deterministic T2 isolated-install acceptance canary for the skill pack."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_MANIFEST = Path("docs/reports/leo-working-state-20260709/skill-pack-manifest.json")
|
||||
|
||||
SEMANTIC_CONTRACT = {
|
||||
"authority": (
|
||||
"teleo-infra-provenance",
|
||||
"Canonical code/deployment repository: GitHub `living-ip/teleo-infrastructure`.",
|
||||
"GitHub living-ip/teleo-infrastructure",
|
||||
),
|
||||
"vps_database": (
|
||||
"teleo-leo-onboarding",
|
||||
"VPS canonical KB: Docker Postgres `teleo-pg`, database `teleo`.",
|
||||
"teleo-pg / teleo",
|
||||
),
|
||||
"gcp_database": (
|
||||
"teleo-leo-onboarding",
|
||||
"GCP canonical KB: private Cloud SQL database `teleo_canonical`.",
|
||||
"private Cloud SQL teleo_canonical; persistent copy remains staging",
|
||||
),
|
||||
"live_service": (
|
||||
"teleo-infra-provenance",
|
||||
"Live service: `leoclean-gateway.service`",
|
||||
"leoclean-gateway.service",
|
||||
),
|
||||
"merged_reconstruction": (
|
||||
"teleo-reconstruction-recovery",
|
||||
"PR #146 is merged",
|
||||
"PR #146 merged deterministic genesis-plus-ledger reconstruction",
|
||||
),
|
||||
"merged_reasoning_verifier": (
|
||||
"teleo-reconstruction-recovery",
|
||||
"PR #147 is merged",
|
||||
"PR #147 merged unseen-reasoning verifier hardening",
|
||||
),
|
||||
"candidate_only_gcp": (
|
||||
"teleo-reconstruction-recovery",
|
||||
"PR #148 is a separate, open GCP least-privilege candidate",
|
||||
"PR #148 open candidate only; not canonical main",
|
||||
),
|
||||
"next_safe_action": (
|
||||
"teleo-leo-onboarding",
|
||||
".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .",
|
||||
"run the repo-local path validator before any operational action",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _run(command: list[str]) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(command, check=False, capture_output=True, text=True)
|
||||
|
||||
|
||||
def _contains_contract(text: str, required_text: str) -> bool:
|
||||
"""Compare prose semantically across ordinary Markdown line wrapping."""
|
||||
|
||||
return " ".join(required_text.split()) in " ".join(text.split())
|
||||
|
||||
|
||||
def run_canary(root: Path, manifest_path: Path) -> dict[str, object]:
|
||||
manifest = json.loads((root / manifest_path).read_text(encoding="utf-8"))
|
||||
installer = root / manifest["installer"]
|
||||
selected_skills = sorted({contract[0] for contract in SEMANTIC_CONTRACT.values()})
|
||||
result: dict[str, object]
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="teleo-clean-context-") as temporary_text:
|
||||
temporary = Path(temporary_text)
|
||||
installed_root = temporary / "skills"
|
||||
install_process = _run(
|
||||
[str(installer), "--root", str(root), "--manifest", str(manifest_path), "--target", str(installed_root)]
|
||||
)
|
||||
try:
|
||||
install_receipt = json.loads(install_process.stdout)
|
||||
except json.JSONDecodeError:
|
||||
install_receipt = {"status": "fail", "error": install_process.stdout or install_process.stderr}
|
||||
|
||||
semantic_checks: dict[str, bool] = {}
|
||||
answers: dict[str, str] = {}
|
||||
evidence_paths: dict[str, str] = {}
|
||||
for key, (skill_name, required_text, answer) in SEMANTIC_CONTRACT.items():
|
||||
installed_skill = installed_root / skill_name / "SKILL.md"
|
||||
text = installed_skill.read_text(encoding="utf-8") if installed_skill.is_file() else ""
|
||||
semantic_checks[key] = _contains_contract(text, required_text)
|
||||
answers[key] = answer
|
||||
evidence_paths[key] = f".agents/skills/{skill_name}/SKILL.md"
|
||||
|
||||
installed_validator = installed_root / "teleo-leo-onboarding" / "scripts" / "validate_skill_pack.py"
|
||||
validator_process = _run([str(installed_validator), "--root", str(root), "--manifest", str(manifest_path)])
|
||||
try:
|
||||
validator_receipt = json.loads(validator_process.stdout)
|
||||
except json.JSONDecodeError:
|
||||
validator_receipt = {"status": "fail", "error": validator_process.stdout or validator_process.stderr}
|
||||
|
||||
problems: list[dict[str, str]] = []
|
||||
if install_process.returncode != 0 or install_receipt.get("status") != "pass":
|
||||
problems.append({"kind": "install_failed", "detail": str(install_receipt.get("error", "unknown"))})
|
||||
if validator_process.returncode != 0 or validator_receipt.get("status") != "pass":
|
||||
problems.append({"kind": "path_validation_failed", "detail": str(validator_receipt.get("problems", []))})
|
||||
for key, passed in semantic_checks.items():
|
||||
if not passed:
|
||||
problems.append({"kind": "semantic_route_missing", "detail": key})
|
||||
|
||||
passed = not problems
|
||||
result = {
|
||||
"artifact": "leo_teleo_skill_pack_isolated_install_canary",
|
||||
"status": "pass" if passed else "fail",
|
||||
"required_tier": "T2_runtime",
|
||||
"current_tier": "T2_runtime" if passed else "T1_model",
|
||||
"proof_scope": "deterministic_isolated_install_and_route_validation",
|
||||
"fresh_temporary_skill_root": True,
|
||||
"ambient_skill_root_used": False,
|
||||
"agent_reasoning_exercised": False,
|
||||
"installed_skill_count": install_receipt.get("installed_skill_count", 0),
|
||||
"selected_installed_skills": selected_skills,
|
||||
"semantic_checks": semantic_checks,
|
||||
"expected_answers": answers,
|
||||
"evidence_paths": evidence_paths,
|
||||
"canary_command": ".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .",
|
||||
"canary_exit_status": validator_process.returncode,
|
||||
"canary_receipt": validator_receipt,
|
||||
"missing_to_reach_required_tier": [] if passed else problems,
|
||||
"strongest_claim_allowed": (
|
||||
"T2 deterministic isolated local skill install, route-contract, and path validation only; this artifact "
|
||||
"does not claim an agent reasoning run, and retained product proofs keep their own VPS, GCP-staging, "
|
||||
"isolated-clone, Telegram, and production claim ceilings"
|
||||
),
|
||||
"contains_secrets": False,
|
||||
"production_mutation_authorized": False,
|
||||
"external_runtime_contacted": False,
|
||||
"problems": problems,
|
||||
}
|
||||
|
||||
result["cleanup_readback"] = {
|
||||
"temporary_skill_root_removed": not Path(temporary_text).exists(),
|
||||
"orphan_processes_started": False,
|
||||
}
|
||||
if not result["cleanup_readback"]["temporary_skill_root_removed"]:
|
||||
result["status"] = "fail"
|
||||
result["current_tier"] = "T1_model"
|
||||
result["problems"].append({"kind": "cleanup_failed", "detail": "temporary skill root remains"})
|
||||
return result
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, default=Path.cwd())
|
||||
parser.add_argument("--manifest", type=Path, default=DEFAULT_MANIFEST)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
root = args.root.resolve()
|
||||
result = run_canary(root, args.manifest)
|
||||
payload = json.dumps(result, indent=2, sort_keys=True) + "\n"
|
||||
if args.output:
|
||||
output = args.output if args.output.is_absolute() else root / args.output
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(payload, encoding="utf-8")
|
||||
sys.stdout.write(payload)
|
||||
return 0 if result["status"] == "pass" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
248
.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py
Executable file
248
.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py
Executable file
|
|
@ -0,0 +1,248 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate the repo-native Leo/Teleo skill pack and its local routes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_MANIFEST = Path("docs/reports/leo-working-state-20260709/skill-pack-manifest.json")
|
||||
REPO_PREFIXES = (
|
||||
".agents/",
|
||||
".github/",
|
||||
"deploy/",
|
||||
"docs/",
|
||||
"fixtures/",
|
||||
"hermes-agent/",
|
||||
"lib/",
|
||||
"ops/",
|
||||
"outputs/",
|
||||
"schemas/",
|
||||
"scripts/",
|
||||
"systemd/",
|
||||
"tests/",
|
||||
)
|
||||
PATH_SUFFIXES = (".json", ".jsonl", ".md", ".py", ".sh", ".sql", ".yaml", ".yml")
|
||||
CODE_SPAN = re.compile(r"`([^`\n]+)`")
|
||||
MARKDOWN_LINK = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
|
||||
FENCED_SHELL = re.compile(r"```(?:bash|sh|shell)\n(.*?)```", re.DOTALL)
|
||||
|
||||
|
||||
def _frontmatter(text: str) -> dict[str, str]:
|
||||
if not text.startswith("---\n"):
|
||||
return {}
|
||||
try:
|
||||
block = text.split("---\n", 2)[1]
|
||||
except IndexError:
|
||||
return {}
|
||||
values: dict[str, str] = {}
|
||||
for line in block.splitlines():
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, value = line.split(":", 1)
|
||||
values[key.strip()] = value.strip().strip('"')
|
||||
return values
|
||||
|
||||
|
||||
def _tracked_paths(root: Path) -> set[Path]:
|
||||
result = subprocess.run(
|
||||
["git", "-C", str(root), "ls-files", "-z"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(result.stderr.decode("utf-8", errors="replace"))
|
||||
return {Path(item.decode("utf-8")) for item in result.stdout.split(b"\0") if item}
|
||||
|
||||
|
||||
def _is_tracked(root: Path, path: Path, tracked: set[Path]) -> bool:
|
||||
relative = path.resolve().relative_to(root.resolve())
|
||||
if path.is_file():
|
||||
return relative in tracked
|
||||
prefix = f"{relative.as_posix().rstrip('/')}/"
|
||||
return any(item.as_posix().startswith(prefix) for item in tracked)
|
||||
|
||||
|
||||
def _clean_token(token: str) -> str:
|
||||
token = token.strip().strip("'\"")
|
||||
token = token.rstrip(",;.)]")
|
||||
token = re.sub(r":\d+$", "", token)
|
||||
return token
|
||||
|
||||
|
||||
def _candidate_tokens(span: str) -> list[str]:
|
||||
try:
|
||||
tokens = shlex.split(span)
|
||||
except ValueError:
|
||||
tokens = span.split()
|
||||
return [_clean_token(token) for token in tokens]
|
||||
|
||||
|
||||
def _resolve_candidate(root: Path, source: Path, token: str) -> Path | None:
|
||||
if not token or token in {".md", ".json", ".py", ".sh", ".sql"}:
|
||||
return None
|
||||
if any(marker in token for marker in ("*", "<", ">", "...", "${", "$LEDGER")):
|
||||
return None
|
||||
if token.startswith(("http://", "https://", "/", "~/")):
|
||||
return None
|
||||
if token.startswith(REPO_PREFIXES) or token in {"README.md", "CODEOWNERS"}:
|
||||
candidate = root / token
|
||||
elif token.endswith(PATH_SUFFIXES) and "/" in token:
|
||||
candidate = source.parent / token
|
||||
else:
|
||||
return None
|
||||
try:
|
||||
candidate.resolve().relative_to(root.resolve())
|
||||
except ValueError:
|
||||
return None
|
||||
return candidate
|
||||
|
||||
|
||||
def _inline_paths(root: Path, source: Path) -> set[Path]:
|
||||
text = source.read_text(encoding="utf-8")
|
||||
candidates: set[Path] = set()
|
||||
spans = CODE_SPAN.findall(text) + MARKDOWN_LINK.findall(text)
|
||||
for block in FENCED_SHELL.findall(text):
|
||||
spans.extend(line for line in block.splitlines() if line.strip())
|
||||
for span in spans:
|
||||
for token in _candidate_tokens(span):
|
||||
candidate = _resolve_candidate(root, source, token)
|
||||
if candidate is not None:
|
||||
candidates.add(candidate)
|
||||
return candidates
|
||||
|
||||
|
||||
def validate(root: Path, manifest_path: Path) -> dict[str, object]:
|
||||
problems: list[dict[str, str]] = []
|
||||
checked: set[Path] = set()
|
||||
manifest_file = root / manifest_path
|
||||
if not manifest_file.is_file():
|
||||
return {
|
||||
"artifact": "leo_teleo_skill_pack_path_validation",
|
||||
"status": "fail",
|
||||
"problems": [{"kind": "missing_manifest", "path": str(manifest_path)}],
|
||||
}
|
||||
|
||||
manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
|
||||
tracked = _tracked_paths(root)
|
||||
if not _is_tracked(root, manifest_file, tracked):
|
||||
problems.append({"kind": "untracked_manifest", "path": str(manifest_path)})
|
||||
skills = manifest.get("skills", [])
|
||||
skill_names = {entry.get("name") for entry in skills}
|
||||
|
||||
for entry in skills:
|
||||
relative = Path(entry["path"])
|
||||
path = root / relative
|
||||
checked.add(path)
|
||||
if not path.is_file():
|
||||
problems.append({"kind": "missing_skill", "path": str(relative)})
|
||||
continue
|
||||
if not _is_tracked(root, path, tracked):
|
||||
problems.append({"kind": "untracked_skill", "path": str(relative)})
|
||||
metadata = _frontmatter(path.read_text(encoding="utf-8"))
|
||||
if metadata.get("name") != entry.get("name"):
|
||||
problems.append({"kind": "skill_name_mismatch", "path": str(relative)})
|
||||
if not metadata.get("description"):
|
||||
problems.append({"kind": "missing_skill_description", "path": str(relative)})
|
||||
if set(metadata) != {"name", "description"}:
|
||||
problems.append({"kind": "invalid_skill_frontmatter_keys", "path": str(relative)})
|
||||
if not re.fullmatch(r"[a-z0-9-]{1,64}", metadata.get("name", "")):
|
||||
problems.append({"kind": "invalid_skill_name", "path": str(relative)})
|
||||
if relative.parent.name != metadata.get("name"):
|
||||
problems.append({"kind": "skill_folder_name_mismatch", "path": str(relative)})
|
||||
|
||||
required_coverage = manifest.get("required_coverage", {})
|
||||
for area, owners in required_coverage.items():
|
||||
for owner in owners:
|
||||
if owner not in skill_names:
|
||||
problems.append({"kind": "unknown_coverage_owner", "path": f"{area}:{owner}"})
|
||||
|
||||
reference_paths = list(manifest.get("reference_files", []))
|
||||
command_paths = list(manifest.get("command_files", []))
|
||||
scan_paths = list(manifest.get("scan_files", []))
|
||||
for relative_text in reference_paths + command_paths + scan_paths:
|
||||
relative = Path(relative_text)
|
||||
path = root / relative
|
||||
checked.add(path)
|
||||
if not path.is_file():
|
||||
problems.append({"kind": "missing_manifest_path", "path": str(relative)})
|
||||
elif not _is_tracked(root, path, tracked):
|
||||
problems.append({"kind": "untracked_manifest_path", "path": str(relative)})
|
||||
|
||||
for relative_text in manifest.get("executable_files", []):
|
||||
relative = Path(relative_text)
|
||||
path = root / relative
|
||||
checked.add(path)
|
||||
if not path.is_file():
|
||||
problems.append({"kind": "missing_executable", "path": str(relative)})
|
||||
elif not os.access(path, os.X_OK):
|
||||
problems.append({"kind": "not_executable", "path": str(relative)})
|
||||
|
||||
scan_sources = [root / entry["path"] for entry in skills]
|
||||
scan_sources += [root / relative for relative in manifest.get("scan_files", [])]
|
||||
for source in scan_sources:
|
||||
if not source.is_file():
|
||||
continue
|
||||
for candidate in _inline_paths(root, source):
|
||||
checked.add(candidate)
|
||||
if not candidate.exists():
|
||||
problems.append(
|
||||
{
|
||||
"kind": "missing_inline_path",
|
||||
"path": str(candidate.resolve().relative_to(root.resolve())),
|
||||
"source": str(source.relative_to(root)),
|
||||
}
|
||||
)
|
||||
elif not _is_tracked(root, candidate, tracked):
|
||||
problems.append(
|
||||
{
|
||||
"kind": "untracked_inline_path",
|
||||
"path": str(candidate.resolve().relative_to(root.resolve())),
|
||||
"source": str(source.relative_to(root)),
|
||||
}
|
||||
)
|
||||
|
||||
relative_paths = sorted(str(path.resolve().relative_to(root.resolve())) for path in checked)
|
||||
digest = hashlib.sha256("\n".join(relative_paths).encode("utf-8")).hexdigest()
|
||||
return {
|
||||
"artifact": "leo_teleo_skill_pack_path_validation",
|
||||
"required_tier": "T2_runtime",
|
||||
"status": "pass" if not problems else "fail",
|
||||
"manifest": str(manifest_path),
|
||||
"skill_count": len(skills),
|
||||
"coverage_area_count": len(required_coverage),
|
||||
"checked_path_count": len(relative_paths),
|
||||
"checked_paths_sha256": digest,
|
||||
"contains_secrets": False,
|
||||
"production_mutation_authorized": False,
|
||||
"problems": problems,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", type=Path, default=Path.cwd())
|
||||
parser.add_argument("--manifest", type=Path, default=DEFAULT_MANIFEST)
|
||||
parser.add_argument("--output", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
root = args.root.resolve()
|
||||
result = validate(root, args.manifest)
|
||||
payload = json.dumps(result, indent=2, sort_keys=True) + "\n"
|
||||
if args.output:
|
||||
output = args.output if args.output.is_absolute() else root / args.output
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(payload, encoding="utf-8")
|
||||
sys.stdout.write(payload)
|
||||
return 0 if result["status"] == "pass" else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
83
.agents/skills/teleo-reconstruction-recovery/SKILL.md
Normal file
83
.agents/skills/teleo-reconstruction-recovery/SKILL.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
name: teleo-reconstruction-recovery
|
||||
description: Use when restoring, rebuilding, recompiling, or incident-recovering Leo's Teleo knowledge database, including snapshot recovery, genesis-plus-ledger replay, source recompilation, parity verification, and honest recovery claim ceilings.
|
||||
---
|
||||
|
||||
# Teleo Reconstruction Recovery
|
||||
|
||||
## Job
|
||||
|
||||
Choose the smallest recovery path that restores a usable Leo knowledge system
|
||||
without confusing snapshot restoration, strict ledger replay, and semantic
|
||||
source recompilation.
|
||||
|
||||
## Authority
|
||||
|
||||
Read `docs/kb-rebuild-and-recompile.md` first. It is the canonical recovery
|
||||
contract. Use `docs/reports/leo-working-state-20260709/current-truth-index.md`
|
||||
only for the newest retained proof point, and refresh live state before making a
|
||||
current VPS or GCP claim.
|
||||
|
||||
PR #146 is merged and owns the deterministic genesis-plus-ledger reconstruction
|
||||
slice in `ops/run_local_genesis_ledger_rebuild.py`. PR #147 is merged and owns
|
||||
the current unseen-reasoning verifier in
|
||||
`scripts/verify_leo_unseen_reasoning_chain.py`. PR #148 is a separate, open GCP
|
||||
least-privilege candidate; do not treat its branch files or proposed live state
|
||||
as canonical `main` recovery instructions until it merges.
|
||||
|
||||
## Select The Recovery Mode
|
||||
|
||||
| Need | Path | Honest result |
|
||||
|---|---|---|
|
||||
| Restore the latest verified state quickly | `ops/run_local_canonical_postgres_rebuild.py` | Exact snapshot recovery |
|
||||
| Prove strict post-genesis applies replay exactly | `ops/run_local_genesis_ledger_rebuild.py` | Isolated insert-only ledger replay |
|
||||
| Turn one retained source into a reviewed packet | `scripts/compile_kb_source_packet.py` | Deterministic proposal packet, not canonical knowledge |
|
||||
| Rebuild every row from original sources | Follow the semantic recompilation contract in `docs/kb-rebuild-and-recompile.md` | Partial until every row and receipt is accounted for |
|
||||
| Diagnose a live incident before recovery | Use `.agents/skills/teleo-vps-runtime-ops/SKILL.md` and `.agents/skills/teleo-infra-provenance/SKILL.md` | Fresh read-only incident map |
|
||||
|
||||
## Safe Order
|
||||
|
||||
1. Identify the intended source database, snapshot, manifest, commit, and hashes.
|
||||
2. Keep private dumps, row payloads, source excerpts, and replay material outside
|
||||
the repository and mode `0600`.
|
||||
3. Run the selected path in an isolated, network-disabled local container.
|
||||
4. Verify schema, constraints, roles, counts, row hashes, key queries, and exact
|
||||
cleanup.
|
||||
5. Run the relevant focused tests, then the unseen-reasoning verifier when the
|
||||
restored state is meant to support Leo answers.
|
||||
6. Separate local reconstruction from VPS restore, GCP restore, service restart,
|
||||
Telegram delivery, promotion, and production apply. Each is its own proof row.
|
||||
|
||||
## Commands
|
||||
|
||||
Use the repository virtual environment documented in `README.md`:
|
||||
|
||||
```bash
|
||||
.venv/bin/python ops/run_local_canonical_postgres_rebuild.py --help
|
||||
.venv/bin/python ops/run_local_genesis_ledger_rebuild.py --help
|
||||
.venv/bin/python scripts/compile_kb_source_packet.py --help
|
||||
.venv/bin/python -m pytest -q \
|
||||
tests/test_run_local_canonical_postgres_rebuild.py \
|
||||
tests/test_run_local_genesis_ledger_rebuild.py \
|
||||
tests/test_verify_leo_unseen_reasoning_chain.py
|
||||
```
|
||||
|
||||
The help and focused-test commands are safe local canaries. A real recovery
|
||||
requires caller-supplied private material and must retain a sanitized receipt
|
||||
without the material itself.
|
||||
|
||||
## Stop Conditions
|
||||
|
||||
Stop before any live restore or restart when the source snapshot is not
|
||||
hash-bound, the source authority is ambiguous, parity fails, private material
|
||||
would enter Git or logs, cleanup cannot be proved, or the requested operation
|
||||
would promote GCP or mutate production without exact authorization.
|
||||
|
||||
## Claim Ceiling
|
||||
|
||||
- Exact snapshot recovery is working when its parity receipt passes.
|
||||
- The merged genesis-plus-ledger path proves an isolated insert-only slice.
|
||||
- Semantic source-to-blank-database recompilation remains incomplete until every
|
||||
canonical row traces to a genesis record or reviewed replay receipt.
|
||||
- No local receipt proves a live VPS/GCP restore, service restart, Telegram
|
||||
delivery, production apply, or GCP promotion.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
interface:
|
||||
display_name: "Teleo Reconstruction Recovery"
|
||||
short_description: "Recover and reconstruct Leo knowledge safely"
|
||||
default_prompt: "Use $teleo-reconstruction-recovery to choose and verify the safest Leo database recovery path."
|
||||
|
|
@ -62,11 +62,16 @@ A working Leo is a Telegram-facing agent that:
|
|||
|
||||
## Current Benchmark Cases
|
||||
|
||||
- Marker memory: `WL-LIVE-TG-CORY-20260709`.
|
||||
- Historical marker-memory receipt:
|
||||
`docs/reports/leo-working-state-20260709/telegram-live-canary-current.json`.
|
||||
- Truth correction: `approved != applied`.
|
||||
- Applied canary: proposal `00957f6c-9883-4015-95a4-6b09367efb0e` and edge `c167933e-d513-4f43-9335-d5d8aeb259f2`.
|
||||
- Staged write canary: proposal `8dfedb3f-3aa4-4200-970f-4c0016f6869f`, status `pending_review`, with no new public canonical rows after the test timestamp.
|
||||
- Open-ended triage canary: `WL-LIVE-TG-CORY-OPEN-20260709`, where Leo inferred the likely failure mode from "agents not working / same state as last night" and explained the proposed, approved, and applied split without exact IDs.
|
||||
- Open-ended triage receipt:
|
||||
`docs/reports/leo-working-state-20260709/telegram-live-open-ended-suite-current.md`,
|
||||
where Leo inferred the likely failure mode from "agents not working / same
|
||||
state as last night" and explained the proposed, approved, and applied split
|
||||
without exact IDs.
|
||||
- Old rich proposal packets: `14fa5ecc...`, `ac036c9d...`, and `a64df080...` are not to be silently production-applied.
|
||||
- Guarded apply proof: both generic and real Helmer v3 receipts pass `37/37` in
|
||||
disposable PostgreSQL; production Helmer remains unapplied.
|
||||
|
|
|
|||
208
.github/workflows/gcp-observatory-read-adapter.yml
vendored
Normal file
208
.github/workflows/gcp-observatory-read-adapter.yml
vendored
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
name: gcp-observatory-read-adapter
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: Build only, or deploy the protected staging service and run live receipts
|
||||
required: true
|
||||
default: build_only
|
||||
type: choice
|
||||
options:
|
||||
- build_only
|
||||
- deploy_staging
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: gcp-observatory-read-adapter-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROJECT_ID: teleo-501523
|
||||
REGION: europe-west6
|
||||
ZONE: europe-west6-a
|
||||
REPOSITORY: teleo
|
||||
IMAGE_NAME: observatory-read-adapter
|
||||
SERVICE_NAME: observatory-read-adapter-staging
|
||||
RUNTIME_SERVICE_ACCOUNT: sa-observatory-read-adapter@teleo-501523.iam.gserviceaccount.com
|
||||
DB_IAM_USER: sa-observatory-read-adapter@teleo-501523.iam
|
||||
API_KEY_SECRET: observatory-read-api-key-staging
|
||||
WORKLOAD_IDENTITY_PROVIDER: projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/living-ip-github
|
||||
DEPLOY_SERVICE_ACCOUNT: sa-artifact-builder@teleo-501523.iam.gserviceaccount.com
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test, build, and push adapter image
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
image_ref: ${{ steps.image.outputs.image_ref }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
|
||||
- name: Run focused adapter tests
|
||||
run: |
|
||||
python -m pip install -e '.[dev]'
|
||||
python -m pytest tests/test_observatory_read_adapter.py -q
|
||||
python -m ruff check observatory_read_adapter tests/test_observatory_read_adapter.py
|
||||
|
||||
- id: auth
|
||||
uses: google-github-actions/auth@v3
|
||||
with:
|
||||
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
|
||||
service_account: ${{ env.DEPLOY_SERVICE_ACCOUNT }}
|
||||
|
||||
- uses: google-github-actions/setup-gcloud@v3
|
||||
with:
|
||||
project_id: ${{ env.PROJECT_ID }}
|
||||
|
||||
- name: Configure Artifact Registry
|
||||
run: gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet
|
||||
|
||||
- id: image
|
||||
name: Build, smoke, and push immutable image
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tag="${GITHUB_SHA::12}"
|
||||
image="${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${IMAGE_NAME}:${tag}"
|
||||
digest="$(gcloud artifacts docker images describe "${image}" \
|
||||
--project="${PROJECT_ID}" \
|
||||
--format='value(image_summary.digest)' 2>/dev/null || true)"
|
||||
if [[ -n "${digest}" ]]; then
|
||||
docker pull "${image}"
|
||||
else
|
||||
docker build -f Dockerfile.observatory-read-adapter -t "${image}" .
|
||||
fi
|
||||
docker run --rm --env PYTHONPYCACHEPREFIX=/tmp/pycache \
|
||||
"${image}" python -m compileall -q /app/observatory_read_adapter
|
||||
docker run --rm "${image}" python -c 'import observatory_read_adapter; print("adapter-import-ok")'
|
||||
if [[ -z "${digest}" ]]; then
|
||||
docker push "${image}" | tee docker-push.log
|
||||
digest="$(awk '/digest: sha256:/ {print $3}' docker-push.log | tail -1)"
|
||||
fi
|
||||
test -n "${digest}"
|
||||
image_ref="${image}@${digest}"
|
||||
printf 'image_ref=%s\n' "${image_ref}" >> "${GITHUB_OUTPUT}"
|
||||
printf 'revision=%s\nimage_ref=%s\n' "${GITHUB_SHA}" "${image_ref}" > observatory-adapter-image.txt
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: observatory-adapter-image
|
||||
path: observatory-adapter-image.txt
|
||||
if-no-files-found: error
|
||||
|
||||
deploy-staging:
|
||||
name: Deploy and prove staging read path
|
||||
if: ${{ inputs.action == 'deploy_staging' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- id: auth
|
||||
uses: google-github-actions/auth@v3
|
||||
with:
|
||||
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
|
||||
service_account: ${{ env.DEPLOY_SERVICE_ACCOUNT }}
|
||||
|
||||
- uses: google-github-actions/setup-gcloud@v3
|
||||
with:
|
||||
project_id: ${{ env.PROJECT_ID }}
|
||||
|
||||
- name: Deploy bounded GCP staging service
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE_REF: ${{ needs.build.outputs.image_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gcloud run deploy "${SERVICE_NAME}" \
|
||||
--project="${PROJECT_ID}" \
|
||||
--region="${REGION}" \
|
||||
--image="${IMAGE_REF}" \
|
||||
--execution-environment=gen2 \
|
||||
--service-account="${RUNTIME_SERVICE_ACCOUNT}" \
|
||||
--network=teleo-staging-net \
|
||||
--subnet=teleo-staging-europe-west6 \
|
||||
--vpc-egress=private-ranges-only \
|
||||
--ingress=all \
|
||||
--allow-unauthenticated \
|
||||
--port=8080 \
|
||||
--cpu=1 \
|
||||
--memory=512Mi \
|
||||
--concurrency=8 \
|
||||
--max-instances=2 \
|
||||
--timeout=15 \
|
||||
--set-secrets="OBSERVATORY_API_KEY=${API_KEY_SECRET}:latest" \
|
||||
--set-env-vars="GCP_PROJECT_ID=${PROJECT_ID},CLOUD_SQL_INSTANCE=${PROJECT_ID}:${REGION}:teleo-pgvector-standby,DB_NAME=teleo_canonical,DB_IAM_USER=${DB_IAM_USER},DB_AUTHORIZATION_ROLE=kb_observatory_read,SERVICE_REVISION=${GITHUB_SHA}" \
|
||||
--labels="livingip-tier=gcp-staging,livingip-surface=observatory-read-adapter" \
|
||||
--quiet
|
||||
|
||||
- name: Capture positive and negative live receipts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
service_url="$(gcloud run services describe "${SERVICE_NAME}" --project="${PROJECT_ID}" --region="${REGION}" --format='value(status.url)')"
|
||||
revision="$(gcloud run services describe "${SERVICE_NAME}" --project="${PROJECT_ID}" --region="${REGION}" --format='value(status.latestReadyRevisionName)')"
|
||||
api_key="$(gcloud secrets versions access latest --secret="${API_KEY_SECRET}" --project="${PROJECT_ID}")"
|
||||
test -n "${service_url}"
|
||||
test -n "${revision}"
|
||||
test -n "${api_key}"
|
||||
|
||||
curl --fail --silent --show-error \
|
||||
--header "X-Api-Key: ${api_key}" \
|
||||
"${service_url}/v1/claims/sample" > positive.json
|
||||
jq -e '
|
||||
.schema == "livingip.observatory-canonical-claim.v1"
|
||||
and .read_only == true
|
||||
and .provenance.database == "teleo_canonical"
|
||||
and .provenance.authorization_role == "kb_observatory_read"
|
||||
and .provenance.transaction_read_only == true
|
||||
and .provenance.write_privileges_denied == true
|
||||
and (.canonical.evidence | length) > 0
|
||||
and .proposal_ledger.distinct_from_canonical == true
|
||||
' positive.json >/dev/null
|
||||
|
||||
anonymous_status="$(curl --silent --output anonymous.json --write-out '%{http_code}' "${service_url}/v1/claims/sample")"
|
||||
write_status="$(curl --silent --output write.json --write-out '%{http_code}' \
|
||||
--request POST --header "X-Api-Key: ${api_key}" --header 'Content-Type: application/json' \
|
||||
--data '{"status":"applied"}' "${service_url}/v1/claims/sample")"
|
||||
test "${anonymous_status}" = 401
|
||||
test "${write_status}" = 405
|
||||
|
||||
jq -n \
|
||||
--arg service_url "${service_url}" \
|
||||
--arg revision "${revision}" \
|
||||
--arg git_sha "${GITHUB_SHA}" \
|
||||
--argjson positive "$(cat positive.json)" \
|
||||
--arg anonymous_status "${anonymous_status}" \
|
||||
--arg write_status "${write_status}" \
|
||||
'{
|
||||
schema: "livingip.observatory-read-adapter-live-receipt.v1",
|
||||
required_tier: "T3_live_readonly",
|
||||
service_url: $service_url,
|
||||
revision: $revision,
|
||||
git_sha: $git_sha,
|
||||
positive: $positive,
|
||||
negative: {
|
||||
anonymous_http_status: ($anonymous_status | tonumber),
|
||||
authenticated_post_http_status: ($write_status | tonumber)
|
||||
},
|
||||
production_repoint_executed: false
|
||||
}' > observatory-read-adapter-live-receipt.json
|
||||
unset api_key
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: observatory-read-adapter-live-receipt
|
||||
path: observatory-read-adapter-live-receipt.json
|
||||
if-no-files-found: error
|
||||
20
Dockerfile.observatory-read-adapter
Normal file
20
Dockerfile.observatory-read-adapter
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
FROM python:3.11-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PORT=8080
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml README.md /app/
|
||||
COPY observatory_read_adapter /app/observatory_read_adapter
|
||||
COPY lib /app/lib
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade pip \
|
||||
&& pip install --no-cache-dir ".[observatory]" \
|
||||
&& addgroup --system --gid 10001 adapter \
|
||||
&& adduser --system --uid 10001 --ingroup adapter --no-create-home adapter
|
||||
|
||||
USER 10001:10001
|
||||
|
||||
CMD ["python", "-m", "observatory_read_adapter"]
|
||||
24
README.md
24
README.md
|
|
@ -84,8 +84,8 @@ A mirror sync (every 2 minutes) fast-forwards the PR onto Forgejo, where
|
|||
the pipeline picks it up. From there it's the same flow as agent-authored
|
||||
PRs — same tiers, same reviewers, same merge rules.
|
||||
|
||||
The contributor-facing guide lives in
|
||||
[`teleo-codex/CONTRIBUTING.md`](https://github.com/living-ip/teleo-codex/blob/main/CONTRIBUTING.md).
|
||||
The contributor-facing guide is the
|
||||
[Teleo Codex contributing guide](https://github.com/living-ip/teleo-codex/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## Repository layout
|
||||
|
||||
|
|
@ -119,10 +119,26 @@ status report in their Pentagon profile.
|
|||
## Development
|
||||
|
||||
```bash
|
||||
pip install -e ".[dev]"
|
||||
pytest
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -e ".[dev]"
|
||||
.venv/bin/python -m pytest
|
||||
```
|
||||
|
||||
## Leo / Teleo Operator Onboarding
|
||||
|
||||
Start with the repo-native skill router, not historical chat or a runtime mirror:
|
||||
|
||||
```bash
|
||||
.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .
|
||||
```
|
||||
|
||||
Then read `.agents/skills/teleo-leo-onboarding/SKILL.md` and
|
||||
`docs/reports/leo-working-state-20260709/current-truth-index.md`. Recovery work
|
||||
starts at `docs/kb-rebuild-and-recompile.md`. GitHub
|
||||
`living-ip/teleo-infrastructure` is canonical code/deployment truth; VPS/GCP
|
||||
runtime, canonical Postgres, proposal staging, Hermes memory, and retained
|
||||
proofs are separate state surfaces.
|
||||
|
||||
## Operations
|
||||
|
||||
Production deployment runs on a single VPS. Runbook, restart procedures,
|
||||
|
|
|
|||
|
|
@ -25,11 +25,12 @@ Run:
|
|||
--output /tmp/teleo-canonical-rebuild-receipt.json
|
||||
```
|
||||
|
||||
The newest retained 2026-07-14 post-V3 canary restored a fresh, network-isolated
|
||||
The retained 2026-07-14 post-V3 canary restored a fresh, network-isolated
|
||||
Postgres and then removed it. The restored target matched the source across all
|
||||
39 manifest tables and all 52,167 rows, with no schema, data, constraint, role,
|
||||
or performance mismatch. Total runtime was 10.56 seconds, including 2.71
|
||||
seconds for `pg_restore`. Key rows included:
|
||||
or performance mismatch. The same source snapshot was subsequently restored to
|
||||
a disposable private-TLS GCP Cloud SQL clone with exact parity, a bounded
|
||||
no-send reasoning turn, and verified cleanup. Key rows included:
|
||||
|
||||
- 1,837 claims;
|
||||
- 4,145 sources;
|
||||
|
|
@ -38,9 +39,10 @@ seconds for `pg_restore`. Key rows included:
|
|||
- 17 reasoning tools;
|
||||
- 29 proposals.
|
||||
|
||||
This snapshot includes the completed V3 source canary. It is ready as the
|
||||
current restore input, but it has not yet been restored to GCP; current GCP
|
||||
parity therefore remains unproven.
|
||||
This snapshot includes the completed V3 source canary. Disposable GCP restore
|
||||
parity is proven at that retained point. Persistent GCP `teleo_canonical`
|
||||
remains the older staging copy, so ongoing parity, promotion, and production
|
||||
cutover remain unproven.
|
||||
|
||||
This is the fastest disaster-recovery path. It does not require Leo to
|
||||
re-extract or relearn the corpus.
|
||||
|
|
@ -134,7 +136,7 @@ kept all database counts and the proposal payload hash unchanged, left the Leo
|
|||
gateway on the same PID with zero restarts, and removed the temporary private
|
||||
receipt. The full receipt is deliberately not committed because it can contain
|
||||
claim bodies or source excerpts; the sanitized proof is retained as
|
||||
`reports/leo-working-state-20260709/kb-apply-replay-receipt-current.json`.
|
||||
`docs/reports/leo-working-state-20260709/kb-apply-replay-receipt-current.json`.
|
||||
|
||||
Normal applies mark the SQL hash as `exact_executed_sql`. A later
|
||||
`--receipt-only` recovery marks it as `reconstructed_current_engine`; it never
|
||||
|
|
@ -146,7 +148,7 @@ not retain every column of the proposal ledger, so exact reconstruction also
|
|||
needs the full approved proposal row, immutable approval snapshot, and final
|
||||
applied proposal row.
|
||||
|
||||
## Genesis Plus Strict Ledger: Working Insert-Only Slice
|
||||
## Genesis Plus Strict Ledger: Working Deterministic Slice
|
||||
|
||||
`ops/run_local_genesis_ledger_rebuild.py` now executes the first exact
|
||||
genesis-plus-ledger slice in one command:
|
||||
|
|
@ -204,27 +206,67 @@ Each referenced material object has exact top-level keys
|
|||
must contain every current `kb_stage.kb_proposals` column; partial envelopes
|
||||
are rejected.
|
||||
|
||||
The command verifies every hash before starting Docker, restores a tmpfs-backed
|
||||
Postgres with `--network none`, reapplies the current guarded prerequisites,
|
||||
and proves genesis parity. For each entry it seeds the receipt's exact row IDs
|
||||
and timestamps in the disposable clone, executes the existing `kb_apply`
|
||||
payload-bound guard and ledger transition, checks exact proposal and canonical
|
||||
row readbacks, then verifies the complete final parity manifest. Its public
|
||||
The command verifies every hash before starting Docker, requires a
|
||||
SHA-256-pinned Postgres image (and defaults to a pinned PostgreSQL 16 Alpine
|
||||
multi-platform digest), restores it on tmpfs with `--network none`, reapplies
|
||||
the current guarded prerequisites, and proves genesis parity. Insert-only
|
||||
entries seed the receipt's exact canonical row IDs and timestamps before the
|
||||
existing `kb_apply` payload-bound guard executes. For `revise_strategy`, the
|
||||
runner seeds only the proposal and approval, captures the target agent's
|
||||
prestate, executes the real guarded transition, validates the generated delta,
|
||||
and then replaces only those generated rows with the receipt-pinned IDs and
|
||||
timestamps. Every path checks exact proposal and canonical row readbacks before
|
||||
verifying the complete final parity manifest. Its public
|
||||
mode-`0600` receipt contains hashes, IDs, types, counts, parity summaries, and
|
||||
cleanup proof, but no payloads, rows, SQL, source paths, or command errors.
|
||||
The legacy `seed_exact` summary is the insert-only aggregate of
|
||||
`proposal_seed_exact` and `canonical_seed_exact`; it is intentionally false for
|
||||
successful mutating entries, which instead report
|
||||
`mutating_delta_validated` and `mutating_poststate_normalized`.
|
||||
Fresh guard bootstrap rows use a fixed baseline timestamp rather than wall
|
||||
clock time, so repeated clean restores have identical row hashes.
|
||||
|
||||
The exact v1 claim ceiling is intentionally narrow:
|
||||
The exact v1 claim ceiling is intentionally bounded:
|
||||
|
||||
- `add_edge`, `attach_evidence`, and `approve_claim` strict receipts execute;
|
||||
- `add_edge`, `attach_evidence`, `approve_claim`, and `revise_strategy` strict
|
||||
receipts execute;
|
||||
- sequence gaps, hash drift, engine drift, duplicate proposals, and legacy or
|
||||
freeform payloads fail before container start;
|
||||
- `revise_strategy` fails closed because its current receipt omits the prior
|
||||
strategies and nodes that the apply engine deactivates or retires;
|
||||
- `revise_strategy` is accepted only when the receipt pins the exact SQL that
|
||||
matches the current guarded apply engine. Immediately before each entry, the
|
||||
runner captures the target agent's strategy/node IDs, active strategy,
|
||||
non-retired nodes, and maximum version. It then validates the generated
|
||||
post-minus-pre delta, requires `version = previous maximum + 1`, and replaces
|
||||
only the generated strategy/node rows with the exact receipt rows;
|
||||
- the original transaction timestamp is derived from the fresh strategy
|
||||
`created_at` and must equal every fresh node's `created_at` and `updated_at`.
|
||||
It must also fall within the immutable, timezone-aware interval
|
||||
`reviewed_at <= transaction timestamp <= applied_at`.
|
||||
Only node IDs observed as non-retired before apply receive that timestamp;
|
||||
already-retired, unrelated-agent, and shared NULL-agent rows stay untouched;
|
||||
- generated nodes must have no anchors before normalization, preventing a
|
||||
delete-and-reinsert step from silently cascading future trigger-created rows;
|
||||
- full proposal before/after rows are mandatory because the current receipt
|
||||
envelope does not retain proposal origin fields or exact `updated_at`;
|
||||
- this proves only an isolated local reconstruction. It does not touch or prove
|
||||
VPS, GCP, Telegram, a live database, or blank-schema source recompilation.
|
||||
|
||||
The transition contract for `revise_strategy` is final-state deterministic, not
|
||||
a claim that the v1 receipt independently contains a historical before-image:
|
||||
|
||||
```text
|
||||
exact genesis/pre-entry state
|
||||
+ exact current/original guarded SQL
|
||||
+ receipt-pinned fresh strategy and nodes
|
||||
-> prior active strategy inactive
|
||||
-> exactly the prior non-retired nodes retired at the original transaction time
|
||||
-> one receipt-exact active strategy and receipt-exact node set
|
||||
```
|
||||
|
||||
The genesis and final manifests remain mandatory oracles. Any incorrect
|
||||
prestate, unrelated-row mutation, missing/extra generated row, semantic drift,
|
||||
version drift, hash drift, or final rowset difference fails the reconstruction.
|
||||
|
||||
The source compiler now turns one raw artifact, its strict UTF-8 extraction,
|
||||
and a reviewed extraction manifest into a deterministic, hash-bound
|
||||
`pending_review` proposal bundle:
|
||||
|
|
@ -270,10 +312,9 @@ neither command applies canonical rows.
|
|||
|
||||
The existing full-data clone canary separately proves that a reviewed packet
|
||||
can create source, claim, evidence, and edge rows and affect later reasoning.
|
||||
The remaining reconstruction work is to retain complete deltas for mutating
|
||||
contracts such as `revise_strategy`, backfill or explicitly reject legacy
|
||||
freeform applies, and extend beyond genesis recovery to a blank-schema source
|
||||
compiler. The insert-only ledger runner does not prove that every historical
|
||||
The remaining reconstruction work is to backfill or explicitly reject legacy
|
||||
freeform applies and extend beyond genesis recovery to a blank-schema source
|
||||
compiler. The strict ledger runner does not prove that every historical
|
||||
canonical row can be rebuilt semantically from retained sources.
|
||||
|
||||
## Definition Of Working
|
||||
|
|
|
|||
102
docs/observatory-read-adapter-gcp-staging.md
Normal file
102
docs/observatory-read-adapter-gcp-staging.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# GCP Observatory Read Adapter
|
||||
|
||||
## Definition of Working
|
||||
|
||||
- Working target: an authenticated GCP staging API returns one canonical claim
|
||||
with source/evidence provenance and a separately labeled proposal ledger.
|
||||
- Operator path: `GET /v1/claims/sample` or `GET /v1/claims/{uuid}` with the
|
||||
server-only `X-Api-Key` value.
|
||||
- Done means: the response names `teleo_canonical`, the dedicated IAM database
|
||||
principal and `kb_observatory_read`, reports a read-only transaction and
|
||||
denied write privileges, and live anonymous/write HTTP attempts return
|
||||
`401`/`405`.
|
||||
- Not done: the VPS `teleo-pg`, a public Cloud SQL address, a browser-visible
|
||||
key, a mock-only receipt, or a production Vercel repoint.
|
||||
- Required tier: `T3_live_readonly`.
|
||||
|
||||
## Existing Connector Boundary
|
||||
|
||||
The current `living-ip/livingip-web` main branch reads claims through
|
||||
`src/lib/api.ts`. It selects `NEXT_PUBLIC_API_URL` and otherwise falls back to
|
||||
`http://77.42.65.182:8081`, then calls the legacy `/api/claims` routes. That is
|
||||
the VPS-local/public Observatory path. It is not safe to replace that public
|
||||
environment variable with this protected API: the response shape differs and
|
||||
the adapter key must never enter a `NEXT_PUBLIC_` variable or browser bundle.
|
||||
|
||||
The existing Teleo route `GET /api/kb/claims` is the compatibility reference
|
||||
for route-scoped authentication, but its default database/container settings
|
||||
still describe the VPS-local process. This service is a separate GCP runtime;
|
||||
it does not change Argus or PR #148's Leo runtime files.
|
||||
|
||||
## Runtime Contract
|
||||
|
||||
- Cloud Run service: `observatory-read-adapter-staging` in `europe-west6`.
|
||||
- Direct VPC egress: `teleo-staging-net` / `teleo-staging-europe-west6`.
|
||||
- Cloud SQL: `teleo-501523:europe-west6:teleo-pgvector-standby`, private IP.
|
||||
- Database: `teleo_canonical` only.
|
||||
- Runtime identity: `sa-observatory-read-adapter@teleo-501523.iam.gserviceaccount.com`.
|
||||
- IAM database user: `sa-observatory-read-adapter@teleo-501523.iam`.
|
||||
- Database authorization role: `kb_observatory_read` (`NOLOGIN`).
|
||||
- API authentication: route-scoped key from Secret Manager secret
|
||||
`observatory-read-api-key-staging`; the value is not in Git or deploy logs.
|
||||
- No mutation routes exist. Authenticated unsupported methods return `405`.
|
||||
|
||||
The Cloud SQL Python Connector uses Application Default Credentials from the
|
||||
Cloud Run service identity, automatic IAM database authentication, and private
|
||||
IP. Every request starts a read-only transaction and refuses the response if
|
||||
the database, IAM principal, authorization membership, required reads, or
|
||||
effective write-denial checks drift.
|
||||
|
||||
## One-Time Staging Bootstrap
|
||||
|
||||
These are staging mutations. Run them only with an authenticated GCP operator;
|
||||
they do not route production traffic.
|
||||
|
||||
1. Create the dedicated runtime service account and grant only
|
||||
`roles/cloudsql.client` and `roles/cloudsql.instanceUser` in project
|
||||
`teleo-501523`.
|
||||
2. Add the service account as a Cloud SQL IAM service-account user on
|
||||
`teleo-pgvector-standby`.
|
||||
3. From the existing private GCP VM database-admin path, run
|
||||
`ops/observatory_read_role.sql` against `teleo_canonical` with
|
||||
`OBSERVATORY_DB_IAM_USER=sa-observatory-read-adapter@teleo-501523.iam`.
|
||||
Do not read, copy, or retain the administrator password.
|
||||
4. Create `observatory-read-api-key-staging`, add a generated 32-byte-or-longer
|
||||
value through stdin, and grant that secret's accessor role only to the
|
||||
runtime service account and the bounded staging canary identity.
|
||||
5. Dispatch `.github/workflows/gcp-observatory-read-adapter.yml` with
|
||||
`action=deploy_staging` from the exact reviewed branch revision.
|
||||
|
||||
The workflow builds an immutable image, deploys at most two Cloud Run
|
||||
instances, and retains the positive response plus anonymous `401` and
|
||||
authenticated POST `405` receipts. It does not modify Vercel.
|
||||
|
||||
## Unexecuted Cutover Packet
|
||||
|
||||
Production cutover requires a separate exact authorization and a
|
||||
`living-ip/livingip-web` change. Do not execute these steps from this lane.
|
||||
|
||||
1. Add server-only Vercel secrets `OBSERVATORY_CANONICAL_API_URL` and
|
||||
`OBSERVATORY_CANONICAL_API_KEY` to a protected preview environment. The URL
|
||||
targets the GCP service; neither setting may use `NEXT_PUBLIC_`.
|
||||
2. Add a Next.js server route or server action that calls the GCP API, validates
|
||||
`livingip.observatory-canonical-claim.v1`, and maps it to the Observatory UI.
|
||||
The browser calls only the same-origin Next.js route.
|
||||
3. Prove preview Deployment Protection, anonymous denial, source/evidence
|
||||
rendering, proposal/live labels, and no browser key exposure.
|
||||
4. Obtain exact production-repoint authorization naming the reviewed
|
||||
`livingip-web` commit and Vercel project before changing production values.
|
||||
|
||||
## Unexecuted Rollback Packet
|
||||
|
||||
1. Revert the authorized `livingip-web` connector commit or disable its
|
||||
server-side feature flag.
|
||||
2. Restore the prior production Vercel values and redeploy the last accepted
|
||||
production revision.
|
||||
3. Verify `/claims` again uses the prior `/api/claims` contract.
|
||||
4. Leave the private Cloud SQL instance and adapter role intact while receipts
|
||||
are reviewed; scale the staging Cloud Run service to zero if isolation is
|
||||
required.
|
||||
5. Delete the staging service/role/secret only under a separate cleanup
|
||||
decision. Rollback never adds a Cloud SQL public IP and never redirects the
|
||||
adapter to VPS `teleo-pg`.
|
||||
|
|
@ -2,6 +2,30 @@
|
|||
|
||||
Use this file before making status claims. Prefer fresh VPS/GCP readbacks when cheap; this directory is the retained July 9 evidence snapshot committed to the Teleo infrastructure repo.
|
||||
|
||||
## Repository And Skill Routing Addendum - 2026-07-15
|
||||
|
||||
- PRs #146 and #147 are merged into canonical `main`. The reconstruction source
|
||||
of truth is `docs/kb-rebuild-and-recompile.md`; the merged deterministic paths
|
||||
are `ops/run_local_genesis_ledger_rebuild.py`,
|
||||
`scripts/leo_turn_execution_manifest.py`, and
|
||||
`scripts/verify_leo_unseen_reasoning_chain.py`.
|
||||
- PR #148 remains open candidate evidence for least-privilege GCP Cloud SQL
|
||||
runtime access. Do not use its branch-only runbook, deployment script, role
|
||||
migration, or proposed live outcome as canonical `main` until the PR is merged
|
||||
and its retained runtime receipt passes.
|
||||
- The repo-native onboarding entry point is
|
||||
`.agents/skills/teleo-leo-onboarding/SKILL.md`. Its standard-library validator
|
||||
is `.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py`; run it
|
||||
before relying on any skill route from a clean context.
|
||||
- The deterministic isolated install/routing/cleanup receipt is
|
||||
`docs/reports/leo-working-state-20260709/skill-pack-clean-context-canary-current.json`.
|
||||
- The separate no-history agent onboarding receipt is
|
||||
`docs/reports/leo-working-state-20260709/skill-pack-clean-context-agent-review-current.json`.
|
||||
- Exact snapshot restoration is working. The merged genesis-plus-ledger path is
|
||||
an isolated insert-only recovery slice. Full semantic recompilation from every
|
||||
original source remains incomplete. None of these local tiers proves a live
|
||||
VPS/GCP restore, Telegram delivery, production apply, or GCP promotion.
|
||||
|
||||
## GCP Database-First Addendum - 2026-07-14 10:07 UTC
|
||||
|
||||
This addendum supersedes the GCP-pending statements in the earlier July 14
|
||||
|
|
@ -135,8 +159,8 @@ sections below override this newer status.
|
|||
- VPS canonical KB: claims `1837`, sources `4145`, evidence `4670`, edges
|
||||
`4916`, proposals `26`; proposal states are applied `2`, approved `3`,
|
||||
pending review `14`, canceled `7`.
|
||||
- VPS direct Cory answers: no-send `DC-01` through `DC-06` strict-score `6/6`.
|
||||
- Telegram direct Cory answers: partial. `DC-01` was visibly sent and its live
|
||||
- VPS direct m3taversal-standard answers: no-send `DC-01` through `DC-06` strict-score `6/6`.
|
||||
- Telegram direct m3taversal-standard answers: partial. `DC-01` was visibly sent and its live
|
||||
gateway reply strict-passed server-side, but the reply itself is not yet
|
||||
captured in Telegram. `DC-02` through `DC-06` are not yet sent.
|
||||
- DB composition/application: deterministic source-to-proposal, full-data clone
|
||||
|
|
@ -146,7 +170,7 @@ sections below override this newer status.
|
|||
- GCP canonical DB: exact VPS parity is proven across `39` tables and `52,164`
|
||||
rows over private TLS, with zero row/catalog/role/extension/performance
|
||||
mismatches. Gateway PID `148735`, `NRestarts=0` stayed unchanged.
|
||||
- GCP Cory replay: six DB-read routes pass. One real no-send model replay
|
||||
- GCP m3taversal-standard replay: six DB-read routes pass. One real no-send model replay
|
||||
nominally scored `6/6`, but it was rejected because two replies printed false
|
||||
zero canonical counts. The harness now enables the required `status` read and
|
||||
rejects any printed count that differs from the canonical status receipt. A
|
||||
|
|
@ -170,16 +194,16 @@ Newest artifacts:
|
|||
- `docs/reports/leo-working-state-20260709/gcp-canonical-parity-live-20260712.json`
|
||||
- `docs/reports/leo-working-state-20260709/teleo_clone_cory_20260712t1940z-canonical-parity-receipt.json`
|
||||
- `docs/reports/leo-working-state-20260709/telegram-visible-direct-claim-dc01-partial-current.json`
|
||||
- `docs/reports/leo-working-state-20260709/gcp-cory-model-replay-current.json` (created only after the hardened rerun)
|
||||
- `docs/reports/leo-working-state-20260709/gcp-db-first-blind-claim-current.json`
|
||||
- `docs/reports/leo-working-state-20260709/gcp-operator-access-blocker-current.json`
|
||||
|
||||
## Definition And Outcomes
|
||||
|
||||
- Working Leo definition: `docs/reports/leo-working-state-20260709/working-leo-definition-20260709.md`
|
||||
- Cory expected outcomes: `docs/reports/leo-working-state-20260709/cory-expected-working-leo-outcomes-20260709.md`
|
||||
- Legacy-named expected-outcomes evidence: `docs/reports/leo-working-state-20260709/cory-expected-working-leo-outcomes-20260709.md`
|
||||
- Current state: `docs/reports/leo-working-state-20260709/working-leo-current-state-20260709.md`
|
||||
- Execution plan: `docs/reports/leo-working-state-20260709/working-leo-execution-plan-current.md`
|
||||
- Open-ended benchmark spec: `docs/reports/leo-working-state-20260709/working-leo-open-ended-benchmark-spec.json` (5 live-safe open prompts, 9 broader Cory-style outcome scenarios, and 6 no-context direct-claim follow-up prompts for disposable clone/sandbox first)
|
||||
- Open-ended benchmark spec: `docs/reports/leo-working-state-20260709/working-leo-open-ended-benchmark-spec.json` (5 live-safe open prompts, 9 broader m3taversal-standard outcome scenarios, and 6 no-context direct-claim follow-up prompts for disposable clone/sandbox first)
|
||||
- Open-ended live Telegram canary: `docs/reports/leo-working-state-20260709/telegram-live-open-ended-cory-style-20260709.md`
|
||||
- Open-ended live Telegram canary JSON: `docs/reports/leo-working-state-20260709/telegram-live-open-ended-cory-style-20260709.json`
|
||||
- Retained Telegram benchmark score for the open-ended canary: `docs/reports/leo-working-state-20260709/telegram-live-open-ended-benchmark-score-current.md`
|
||||
|
|
@ -187,9 +211,9 @@ Newest artifacts:
|
|||
- Full live-safe open-ended Telegram suite report: `docs/reports/leo-working-state-20260709/telegram-live-open-ended-suite-current.md`
|
||||
- Full live-safe open-ended Telegram suite score: `docs/reports/leo-working-state-20260709/telegram-live-open-ended-suite-score-current.md`
|
||||
- Full live-safe open-ended Telegram suite score JSON: `docs/reports/leo-working-state-20260709/telegram-live-open-ended-suite-score-current.json`
|
||||
- Full Cory-style outcome/direct-claim sandbox results: `docs/reports/leo-working-state-20260709/working-leo-cory-outcome-sandbox-results-current.md`
|
||||
- Full Cory-style outcome/direct-claim sandbox score: `docs/reports/leo-working-state-20260709/working-leo-cory-outcome-sandbox-score-current.md`
|
||||
- Full Cory-style outcome sandbox score JSON: `docs/reports/leo-working-state-20260709/working-leo-cory-outcome-sandbox-score-current.json`
|
||||
- Full m3taversal-standard outcome/direct-claim sandbox results: `docs/reports/leo-working-state-20260709/working-leo-cory-outcome-sandbox-results-current.md`
|
||||
- Full m3taversal-standard outcome/direct-claim sandbox score: `docs/reports/leo-working-state-20260709/working-leo-cory-outcome-sandbox-score-current.md`
|
||||
- Full m3taversal-standard outcome sandbox score JSON: `docs/reports/leo-working-state-20260709/working-leo-cory-outcome-sandbox-score-current.json`
|
||||
- Live VPS handler direct-claim suite report: `docs/reports/leo-working-state-20260709/telegram-handler-direct-claim-suite-current.json`
|
||||
- Live VPS handler direct-claim suite score: `docs/reports/leo-working-state-20260709/telegram-handler-direct-claim-suite-score-current.md`
|
||||
- Live VPS handler direct-claim suite review: `docs/reports/leo-working-state-20260709/telegram-handler-direct-claim-suite-review-current.md`
|
||||
|
|
@ -311,9 +335,9 @@ Newest artifacts:
|
|||
|
||||
- Skill-pack map: `docs/reports/leo-working-state-20260709/leo-db-state-13-skill-pack.svg`
|
||||
- Helmer packet proof: `docs/reports/leo-working-state-20260709/leo-db-state-14-helmer-packet.svg`
|
||||
- Open-ended Cory-style canary map: `docs/reports/leo-working-state-20260709/leo-db-state-15-open-ended-canary.svg`
|
||||
- Open-ended m3taversal-standard canary map: `docs/reports/leo-working-state-20260709/leo-db-state-15-open-ended-canary.svg`
|
||||
- Helmer ledger packet proof: `docs/reports/leo-working-state-20260709/leo-db-state-16-helmer-ledger.svg`
|
||||
- Cory-style outcome benchmark map: `docs/reports/leo-working-state-20260709/leo-db-state-17-cory-outcome-benchmark.svg`
|
||||
- m3taversal-standard outcome benchmark map: `docs/reports/leo-working-state-20260709/leo-db-state-17-cory-outcome-benchmark.svg`
|
||||
- Leoclean DB versus workspace map: `docs/reports/leo-working-state-20260709/leo-db-state-18-db-vs-workspace.svg`
|
||||
- Claim/body/metadata schema map: `docs/reports/leo-working-state-20260709/leo-db-state-19-claims-body-metadata.svg`
|
||||
- Cross-surface strategy anchor proof: `docs/reports/leo-working-state-20260709/leo-db-state-20-cross-surface-anchor.svg`
|
||||
|
|
@ -331,7 +355,7 @@ Newest artifacts:
|
|||
|
||||
- VPS Leo Telegram memory, KB audit, and staged-write canaries are live-proven.
|
||||
- VPS Leo live-safe open-ended Telegram suite is live-proven and retained-benchmark-scored for `OE-01` through `OE-05`: full selected coverage, `5/5` scored prompts passing, no overclaim, and no marker/staging/canonical KB rows created by the suite.
|
||||
- The broader sandbox-first Cory-style benchmark has a full-catalog mixed-evidence score: real retained live Telegram evidence for `OE-01` through `OE-05`, sandbox fixture replies for `CS-01` through `CS-09`, and no-context direct-claim expected-answer/follow-up fixtures for `DC-01` through `DC-06`, `20/20` passing. This is not a live Telegram run for the CS/DC prompts.
|
||||
- The broader sandbox-first m3taversal-standard benchmark has a full-catalog mixed-evidence score: real retained live Telegram evidence for `OE-01` through `OE-05`, sandbox fixture replies for `CS-01` through `CS-09`, and no-context direct-claim expected-answer/follow-up fixtures for `DC-01` through `DC-06`, `20/20` passing. This is not a live Telegram run for the CS/DC prompts.
|
||||
- After deploy commit `0183a5c805fa3b51d638267afd20f67a1bc3777f`, three fresh VPS GatewayRunner clean-session trials strict-scored `6/6` (`18/18` replies). Every trial posted nothing, made no live profile or production DB change, preserved counts (`1837/4145/4916/4670/26`), removed its temp profile, and left `leoclean-gateway.service` active/running with PID `2403328`, zero restarts, and the same start timestamp. Final cleanup found no matching temp directories.
|
||||
- Intentional restart survival is now live-proven as a separate required proof row, not implied by `NRestarts=0`: `systemctl restart leoclean-gateway.service` returned `0`, service moved from `MainPID=1908033` to `MainPID=2845730`, stayed active/running after restart and after the no-post handler smoke, canonical DB counts stayed unchanged (`1837/4145/4670/4916/26`), no Telegram post or production DB apply ran, no live profile change was recorded, temp profile cleanup succeeded, and the post-restart no-post `DC-01` through `DC-06` handler smoke returned six runtime replies. The retained pre-repair post-restart strict score was `5/6`; after deploying repair SHA `090becae1621436467610e529d6328d70964d11f`, the same no-post direct-claim suite strict-scored `6/6` with `DC-01` through `DC-06` all passing, DB counts unchanged, no Telegram post, no production DB apply, no live profile change, and cleanup confirmed. The raw handler JSON is retained on the VPS at `/tmp/leo-post-skill-repair-direct-claim-current.json` with SHA256 `6fffdbafc18913c4fc62feb00906988dee2e696d279f22dd284951a79451f39c`.
|
||||
- The Telegram-visible direct-claim benchmark was run in group `Leo` (`-5146042086`): all six prompts and replies are visible, and DB/service state is unchanged. The hardened semantic scorer gives `5/6`; `DC-05` incorrectly suggests applying the strict canary `add_edge` path to one of three legacy approved proposals whose payload types do not all match.
|
||||
|
|
@ -364,7 +388,7 @@ Newest artifacts:
|
|||
- Latest cleanup readback found no leftover rehearsal/clone/staging database names on the VPS Postgres container and did not mutate production DB or post to Telegram.
|
||||
- A live Telegram regression canary should be rerun after any production apply; no production apply has been authorized or executed.
|
||||
- GCP control-plane Chrome readback now proves project `teleo-501523`, running VM `teleo-prod-1` in `europe-west6-a`, and one available private-only PostgreSQL `16.14` Cloud SQL instance, `teleo-pgvector-standby`, in `europe-west6-c`. Cloud SQL Studio is reachable but exposes no enabled existing IAM database principal and requires built-in password authentication; no credential was submitted and no SQL ran. Database-row/runtime/DC-01-through-DC-06 parity therefore remains unproven. The exact next gate is an already-existing authenticated Studio principal or a separately authorized IAM/database-user enablement window.
|
||||
- GCP Console observability now separately proves `teleo-prod-1` is running and that serial output through `2026-07-10T15:57:54Z` contains recurring successful `leoclean-cloudsql-memory-sync.service` cycles. The latest exact `leoclean-gcp-prod-parallel.service` lifecycle event in the retained serial buffer is a Hermes start on July 9 with no later exact stop/failure. Cloud Logging has `92` guest/platform records over seven days but zero matches for `leoclean`, `hermes`, `gateway`, `postgres`, or `teleo-kb`, and Ops Agent is absent. This is bounded unit-activity evidence, not current PID/restart/SHA/profile/model/auth/Telegram/DB-row parity; no-post Cory execution remains unsafe and was not attempted. The agent-created Console tab was closed and no GCP/DB/service/IAM/deploy/credential/Telegram mutation occurred.
|
||||
- GCP Console observability now separately proves `teleo-prod-1` is running and that serial output through `2026-07-10T15:57:54Z` contains recurring successful `leoclean-cloudsql-memory-sync.service` cycles. The latest exact `leoclean-gcp-prod-parallel.service` lifecycle event in the retained serial buffer is a Hermes start on July 9 with no later exact stop/failure. Cloud Logging has `92` guest/platform records over seven days but zero matches for `leoclean`, `hermes`, `gateway`, `postgres`, or `teleo-kb`, and Ops Agent is absent. This is bounded unit-activity evidence, not current PID/restart/SHA/profile/model/auth/Telegram/DB-row parity; no-post m3taversal-standard execution remains unsafe and was not attempted. The agent-created Console tab was closed and no GCP/DB/service/IAM/deploy/credential/Telegram mutation occurred.
|
||||
- PR #77 auto-deployed to the VPS checkout at merge `1a0abd882d00c1b58fe203f42de42d4eeea61cbf` without restarting Leo: the deploy journal says `No Python changes - services not restarted`, the gateway remained PID `2999690` with `NRestarts=0`, and the apply worker/timer remained disabled and inactive. This proves source synchronization and runtime non-change, not a GCP replay.
|
||||
- A passwordless GCP operator is implemented and locally validated but is not yet bootstrapped in project `teleo-501523`. It uses GitHub OIDC/Workload Identity Federation, separate status and clone-operation service accounts, IAP TCP forwarding, OS Login, five-minute SSH keys, and a root-owned dispatcher limited to `status`, `direct-claim-replay`, and `cleanup-clone`. It stores no Google password, local refresh token, service-account JSON key, or API key. One final authenticated GCP admin session is still required to install and verify the IAM/IAP/OS Login/dispatcher path; the old public `/32` rule must remain enabled until the workflow status artifact passes.
|
||||
- The broader GCP working-Leo runner now has a complete `20`-prompt catalog and requires `32` GCP-executable non-tautological composition checks plus lifecycle, role-separation, immutable-source, and secret-redaction gates. Local tests and validation-only execution pass, and an independent VPS source baseline is retained with counts `1837/4145/4670/4916/17/26`; live GCP execution remains unproven because the SHA-bound target-identity receipt, four separated database roles/credentials, and passwordless operator bootstrap are not yet present. This does not inherit or claim the VPS-only `34/34` result.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
# Fable / Worker Onboarding Prompt - Leo / Teleo
|
||||
|
||||
current_canary: read the retained evidence index, then produce a repo/VPS/GCP/Telegram working-state map with exact claim ceilings and next executable action.
|
||||
current_canary: validate the repo-native skill routes, then produce a repo/VPS/GCP/Telegram working-state map with exact claim ceilings and next executable action.
|
||||
|
||||
Use the Teleo repo skill pack at:
|
||||
|
||||
`.agents/skills/`
|
||||
|
||||
Load these draft skills first:
|
||||
Run `.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .`,
|
||||
then load these manifest-indexed skills first:
|
||||
|
||||
1. `skills/teleo-leo-onboarding/SKILL.md`
|
||||
2. `skills/working-leo-m3taversal-outcomes/SKILL.md`
|
||||
3. `skills/teleo-kb-db-change-workflow/SKILL.md`
|
||||
4. `skills/teleo-vps-runtime-ops/SKILL.md`
|
||||
5. `skills/teleo-proof-handoff/SKILL.md`
|
||||
1. `.agents/skills/teleo-leo-onboarding/SKILL.md`
|
||||
2. `.agents/skills/working-leo-m3taversal-outcomes/SKILL.md`
|
||||
3. `.agents/skills/teleo-kb-db-change-workflow/SKILL.md`
|
||||
4. `.agents/skills/teleo-reconstruction-recovery/SKILL.md`
|
||||
5. `.agents/skills/teleo-vps-runtime-ops/SKILL.md`
|
||||
6. `.agents/skills/teleo-proof-handoff/SKILL.md`
|
||||
|
||||
Hard constraints:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# Leo Architecture, Work, And Outcomes
|
||||
|
||||
Current as of `2026-07-14 01:40 UTC`.
|
||||
Original delivery summary from `2026-07-14 01:40 UTC`, reconciled with merged
|
||||
repository truth on 2026-07-15.
|
||||
|
||||
This is the executive summary of what Leo is, how its knowledge system works,
|
||||
what changed during the July 10-14 delivery wave, what is genuinely working,
|
||||
|
|
@ -17,10 +18,16 @@ managed gateway restart. The current broad no-post suite passes `15/15`.
|
|||
|
||||
**The whole system is not complete.** The current broad suite has not yet been
|
||||
repeated visibly in the real Telegram group through authenticated Google Chrome.
|
||||
The newest post-V3 database copy has not yet been restored and replayed on GCP.
|
||||
No V3 production proposal has been canonically applied. A snapshot can rebuild
|
||||
the current database exactly, but the entire historical database cannot yet be
|
||||
recompiled from raw documents and review history alone.
|
||||
The newest post-V3 database copy has since been restored to a disposable private
|
||||
GCP Cloud SQL clone with exact parity and a bounded no-send reasoning replay,
|
||||
then cleaned up. Persistent GCP remains staging and older than that disposable
|
||||
proof point. No V3 production proposal has been canonically applied. A snapshot
|
||||
can rebuild the current database exactly, and merged PR #146 adds an exact
|
||||
insert-only genesis-plus-ledger slice; the entire historical database still
|
||||
cannot be recompiled from raw documents and review history alone.
|
||||
|
||||
Repository reconciliation: PRs #146 and #147 are merged `main` truth. PR #148
|
||||
remains an open least-privilege GCP candidate and is not canonical runtime proof.
|
||||
|
||||
## The Architecture
|
||||
|
||||
|
|
@ -255,7 +262,8 @@ left the gateway unchanged, and removed the private receipt afterward.
|
|||
| Grounded proposal staging | live VPS `pending_review` row | Achieved |
|
||||
| Exact snapshot rebuild | isolated local Postgres, `52,167` rows | Achieved |
|
||||
| New strict-apply row receipt | deployed VPS read-only recovery | Achieved |
|
||||
| Prior GCP database copy and six-question replay | private Cloud SQL and GCP compute | Achieved at the earlier `52,164`-row proof point |
|
||||
| Current disposable GCP copy and ID-free replay | private Cloud SQL and GCP compute, `52,167/52,167` rows | Achieved and cleaned up |
|
||||
| Genesis plus strict insert-only replay | isolated local Postgres through merged PR #146 | Achieved for supported strict contracts |
|
||||
|
||||
## What Is Not Achieved
|
||||
|
||||
|
|
@ -263,9 +271,9 @@ left the gateway unchanged, and removed the private receipt afterward.
|
|||
| --- | --- |
|
||||
| Current broad Telegram-visible proof | Telegram Web in the connected Google Chrome is not authenticated; handler proof is not visible-chat proof |
|
||||
| V3 production canonical apply | proposal remains `pending_review`; no review or apply was authorized |
|
||||
| Current GCP parity | the `52,167`-row post-V3 snapshot has not yet been restored and replayed on GCP |
|
||||
| Arbitrary automatic document/post/tweet intake | the current source command handles one bounded text-like artifact; general fetch/extract/stage automation is not complete |
|
||||
| Full source-to-blank-DB recompilation | historical provenance gaps remain and the genesis-plus-ledger replay runner is not finished |
|
||||
| Persistent GCP parity and promotion | the disposable `52,167`-row proof passed, but persistent `teleo_canonical` remains the older staging copy and is not cut over |
|
||||
| Full source-to-blank-DB recompilation | historical provenance gaps and unsupported mutating contracts remain beyond the merged insert-only genesis-plus-ledger slice |
|
||||
| General DB-to-identity rendering | no complete scheduled DB-to-`SOUL.md` renderer is proven |
|
||||
| Deployed decision matrix | matrix tables and voting flow are absent from the current live schema |
|
||||
|
||||
|
|
@ -282,13 +290,13 @@ left the gateway unchanged, and removed the private receipt afterward.
|
|||
|
||||
## How This Moves GCP Forward
|
||||
|
||||
- The prior GCP vertical slice proved the architecture can run against private
|
||||
Cloud SQL and real GCP compute.
|
||||
- The current post-V3 dump and manifest are already captured and locally
|
||||
verified, so GCP work starts from a known input rather than rebuilding one.
|
||||
- The same `15/15` suite and exact V3 question are ready for replay.
|
||||
- The remaining GCP work is operational: authenticate, restore the current copy,
|
||||
verify parity/private connectivity/performance, replay, and clean up.
|
||||
- The current post-V3 snapshot has been restored to a disposable private Cloud
|
||||
SQL clone with exact parity, bounded no-send replay, and cleanup.
|
||||
- Persistent `teleo_canonical` remains the older staging copy; promotion,
|
||||
continuous replication, Telegram delivery, and production cutover are
|
||||
separate decisions and proof rows.
|
||||
- Open PR #148 proposes least-privilege runtime access. Its branch and proposed
|
||||
live end state remain candidate evidence until merge and receipt verification.
|
||||
|
||||
## How This Moves On-Demand Rebuild Forward
|
||||
|
||||
|
|
@ -311,10 +319,11 @@ verified post-V3 snapshot = genesis epoch
|
|||
-> broad answer benchmark
|
||||
```
|
||||
|
||||
The new receipt path closes the future side of this model. The next repo-owned
|
||||
step is to replay one reviewed `approve_claim` receipt onto a clean genesis clone
|
||||
and prove exact post-apply parity. Historical import gaps can then be repaired
|
||||
without allowing new gaps to accumulate.
|
||||
The new receipt path closes the future side of this model. Merged PR #146 now
|
||||
replays supported strict insert-only receipts on a clean genesis clone and proves
|
||||
exact post-apply parity. The next reconstruction work is to retain complete
|
||||
deltas for mutating contracts, handle or reject legacy freeform applies, and
|
||||
repair historical import gaps without allowing new gaps to accumulate.
|
||||
|
||||
## Delivery Chain
|
||||
|
||||
|
|
@ -333,16 +342,21 @@ The latest coherent delivery sequence is merged:
|
|||
| `#135` | current post-V3 snapshot and exact rebuild proof |
|
||||
| `#136` | private replay receipts for guarded applies |
|
||||
| `#137` | deployed receipt runtime proof |
|
||||
| `#144` | GCP database-first restore, fail-closed helper, and live restart proof |
|
||||
| `#146` | unified turn manifest and deterministic genesis-plus-ledger replay |
|
||||
| `#147` | unseen-reasoning verifier hardened for valid reasoning variance |
|
||||
|
||||
PR #148 is open candidate work and is intentionally excluded from the merged
|
||||
delivery chain above.
|
||||
|
||||
## Immediate Next Outcomes
|
||||
|
||||
1. Authenticate Telegram Web in the connected Google Chrome and run visible,
|
||||
out-of-sample m3taversal-style questions.
|
||||
2. Authenticate Google Cloud CLI through the connected Chrome flow, restore the
|
||||
current post-V3 snapshot to disposable GCP staging, verify parity, replay the
|
||||
broad suite, and remove temporary resources.
|
||||
3. Complete the genesis-plus-receipt replayer for `approve_claim` bundles and
|
||||
prove exact clone-to-replay parity.
|
||||
2. Review and merge or reject PR #148, then verify the least-privilege runtime
|
||||
receipt before considering any persistent GCP promotion.
|
||||
3. Extend genesis-plus-receipt reconstruction beyond supported insert-only
|
||||
contracts while retaining exact before/after deltas.
|
||||
4. Run one explicitly reviewed source packet through guarded apply in a
|
||||
disposable clone before seeking any production apply authorization.
|
||||
5. Reconstruct the remaining historical source, evidence, and edge provenance.
|
||||
|
|
@ -363,7 +377,8 @@ The latest coherent delivery sequence is merged:
|
|||
|
||||
The last three days produced a stable, testable, database-grounded Leo on the
|
||||
VPS at the handler tier, a real source-to-review path, exact snapshot recovery,
|
||||
and durable replay evidence for future strict applies. They did not complete
|
||||
current Telegram-visible broad proof, current GCP parity, a V3 production apply,
|
||||
general automatic source ingestion, or full historical source-derived database
|
||||
recompilation.
|
||||
and durable replay evidence for future strict applies. Subsequent work added
|
||||
disposable current GCP parity plus no-send replay and an exact insert-only
|
||||
genesis-plus-ledger slice. The system still lacks current Telegram-visible broad
|
||||
proof, persistent GCP parity/promotion, a V3 production apply, general automatic
|
||||
source ingestion, and full historical source-derived database recompilation.
|
||||
|
|
|
|||
|
|
@ -32,10 +32,19 @@ Always refresh before claiming current state.
|
|||
## GCP
|
||||
|
||||
- Project: `teleo-501523`
|
||||
- Expected active account for project access: `billy@livingip.xyz`
|
||||
- Current retained probe: local gcloud config still selects `billy@livingip.xyz` and `teleo-501523`, but this runner cannot resolve `oauth2.googleapis.com`, so parity is blocked before token refresh/project access can be rechecked.
|
||||
- Current artifact: `docs/reports/leo-working-state-20260709/gcp-db-parity-current-probe-current.json`
|
||||
- Prior auth-capable artifact: `outputs/gcp-db-parity-current-blocker-20260709.json`
|
||||
- Compute: `teleo-prod-1` in `europe-west6-a`.
|
||||
- Private Cloud SQL: `teleo-pgvector-standby`, database `teleo_canonical`.
|
||||
- Direct passwordless alias `ssh teleo-gcp-staging` and `sudo -n` were retained as
|
||||
working on 2026-07-14; always re-run the batch-mode preflight before relying on
|
||||
them.
|
||||
- A disposable private-TLS clone matched the current VPS snapshot at `39` tables
|
||||
and `52,167` rows, then was deleted. Persistent `teleo_canonical` remains the
|
||||
older staging copy and is not promoted or cut over.
|
||||
- Current artifacts:
|
||||
`docs/reports/leo-working-state-20260709/gcp-db-first-working-leo-20260714.md`
|
||||
and `docs/reports/leo-working-state-20260709/gcp-db-first-parity-current.json`.
|
||||
- PR #148 is open candidate evidence for scoped runtime access; its branch and
|
||||
proposed live outcome are not canonical `main`.
|
||||
|
||||
## Telegram
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"artifact": "leo_teleo_skill_pack_fresh_agent_review",
|
||||
"status": "pass",
|
||||
"required_tier": "T2_runtime",
|
||||
"current_tier": "T2_runtime",
|
||||
"proof_scope": "fresh_context_agent_onboarding_and_stale_claim_rejection",
|
||||
"evidence_origin": {
|
||||
"agent": "/root/fresh_skill_canary",
|
||||
"fork_turns": "none",
|
||||
"onboarding_authority": "isolated installed skill pack only",
|
||||
"memory_used": false
|
||||
},
|
||||
"answers": {
|
||||
"canonical_code_deployment_repository_authority": {
|
||||
"repository": "GitHub living-ip/teleo-infrastructure",
|
||||
"canonical_branch": "main",
|
||||
"vps_deploy_source_mirror": "/opt/teleo-eval/workspaces/deploy-infra",
|
||||
"mirror_is_canonical_authority": false
|
||||
},
|
||||
"vps_canonical_database": {
|
||||
"container": "teleo-pg",
|
||||
"database": "teleo"
|
||||
},
|
||||
"live_vps_gateway_service": "leoclean-gateway.service",
|
||||
"persistent_gcp_database": {
|
||||
"instance": "teleo-pgvector-standby",
|
||||
"database": "teleo_canonical",
|
||||
"status": "older staging copy",
|
||||
"retained_measurement": {
|
||||
"tables": 39,
|
||||
"rows": 52164,
|
||||
"proposals": 26
|
||||
},
|
||||
"promoted": false,
|
||||
"cut_over": false
|
||||
},
|
||||
"pull_requests": {
|
||||
"146": "merged into canonical main",
|
||||
"147": "merged into canonical main",
|
||||
"148": "open least-privilege GCP candidate; not canonical main"
|
||||
},
|
||||
"approved_proposals_equal_applied_proposals": {
|
||||
"equal": false,
|
||||
"retained_vps_proposal_counts": {
|
||||
"total": 29,
|
||||
"applied": 2,
|
||||
"approved": 3,
|
||||
"pending_review": 16,
|
||||
"canceled": 8
|
||||
},
|
||||
"meaning": "Approved proposals remain staged until canonical application and row-level proof."
|
||||
},
|
||||
"first_safe_local_validation_command": ".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root ."
|
||||
},
|
||||
"stale_claims_rejected": {
|
||||
"pr_148_is_merged": true,
|
||||
"persistent_gcp_is_promoted": true,
|
||||
"all_approved_proposals_are_applied": true
|
||||
},
|
||||
"validator": {
|
||||
"command": "/private/tmp/teleo-clean-agent-final-20260715/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root /private/tmp/teleo-fleet-20260715/skills-docs-onboarding",
|
||||
"exit_code": 0,
|
||||
"status": "pass",
|
||||
"checked_path_count": 233,
|
||||
"checked_paths_sha256": "ae919c3e7d8cef19a1782a00c640eeeb66c49dfbce3ad9ceaf30eb96e77bda16",
|
||||
"coverage_area_count": 13,
|
||||
"skill_count": 16,
|
||||
"production_mutation_authorized": false,
|
||||
"contains_secrets": false,
|
||||
"problems": []
|
||||
},
|
||||
"repository_readback": {
|
||||
"status_before": [
|
||||
" M .agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py",
|
||||
" M .agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py",
|
||||
" M tests/test_repo_skill_pack.py",
|
||||
"?? goal.md"
|
||||
],
|
||||
"status_after": [
|
||||
" M .agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py",
|
||||
" M .agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py",
|
||||
" M tests/test_repo_skill_pack.py",
|
||||
"?? goal.md"
|
||||
],
|
||||
"unchanged": true
|
||||
},
|
||||
"files_read": [
|
||||
".agents/skills/teleo-leo-onboarding/SKILL.md",
|
||||
".agents/skills/teleo-infra-provenance/SKILL.md",
|
||||
"docs/reports/leo-working-state-20260709/current-truth-index.md",
|
||||
"docs/reports/leo-working-state-20260709/operator-surface-map.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-db-first-working-leo-20260714.md",
|
||||
"docs/reports/leo-working-state-20260709/working-leo-definition-20260709.md",
|
||||
"docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.md"
|
||||
],
|
||||
"cleanup_readback": {
|
||||
"temporary_skill_root_removed": true,
|
||||
"matching_temporary_roots_remaining": 0,
|
||||
"orphan_processes_started": false
|
||||
},
|
||||
"external_runtime_contacted": false,
|
||||
"production_mutation": false,
|
||||
"contains_secrets": false,
|
||||
"problems": [],
|
||||
"strongest_claim_allowed": "T2 fresh-context local onboarding and route validation only; this does not prove live VPS, GCP promotion, Telegram delivery, or production proposal application"
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"agent_reasoning_exercised": false,
|
||||
"ambient_skill_root_used": false,
|
||||
"artifact": "leo_teleo_skill_pack_isolated_install_canary",
|
||||
"canary_command": ".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .",
|
||||
"canary_exit_status": 0,
|
||||
"canary_receipt": {
|
||||
"artifact": "leo_teleo_skill_pack_path_validation",
|
||||
"checked_path_count": 234,
|
||||
"checked_paths_sha256": "19ed1813f9cb4c19489a180081007842e241a2dcae659a5d873582fbbd50cff6",
|
||||
"contains_secrets": false,
|
||||
"coverage_area_count": 13,
|
||||
"manifest": "docs/reports/leo-working-state-20260709/skill-pack-manifest.json",
|
||||
"problems": [],
|
||||
"production_mutation_authorized": false,
|
||||
"required_tier": "T2_runtime",
|
||||
"skill_count": 16,
|
||||
"status": "pass"
|
||||
},
|
||||
"cleanup_readback": {
|
||||
"orphan_processes_started": false,
|
||||
"temporary_skill_root_removed": true
|
||||
},
|
||||
"contains_secrets": false,
|
||||
"current_tier": "T2_runtime",
|
||||
"evidence_paths": {
|
||||
"authority": ".agents/skills/teleo-infra-provenance/SKILL.md",
|
||||
"candidate_only_gcp": ".agents/skills/teleo-reconstruction-recovery/SKILL.md",
|
||||
"gcp_database": ".agents/skills/teleo-leo-onboarding/SKILL.md",
|
||||
"live_service": ".agents/skills/teleo-infra-provenance/SKILL.md",
|
||||
"merged_reasoning_verifier": ".agents/skills/teleo-reconstruction-recovery/SKILL.md",
|
||||
"merged_reconstruction": ".agents/skills/teleo-reconstruction-recovery/SKILL.md",
|
||||
"next_safe_action": ".agents/skills/teleo-leo-onboarding/SKILL.md",
|
||||
"vps_database": ".agents/skills/teleo-leo-onboarding/SKILL.md"
|
||||
},
|
||||
"expected_answers": {
|
||||
"authority": "GitHub living-ip/teleo-infrastructure",
|
||||
"candidate_only_gcp": "PR #148 open candidate only; not canonical main",
|
||||
"gcp_database": "private Cloud SQL teleo_canonical; persistent copy remains staging",
|
||||
"live_service": "leoclean-gateway.service",
|
||||
"merged_reasoning_verifier": "PR #147 merged unseen-reasoning verifier hardening",
|
||||
"merged_reconstruction": "PR #146 merged deterministic genesis-plus-ledger reconstruction",
|
||||
"next_safe_action": "run the repo-local path validator before any operational action",
|
||||
"vps_database": "teleo-pg / teleo"
|
||||
},
|
||||
"external_runtime_contacted": false,
|
||||
"fresh_temporary_skill_root": true,
|
||||
"installed_skill_count": 16,
|
||||
"missing_to_reach_required_tier": [],
|
||||
"problems": [],
|
||||
"production_mutation_authorized": false,
|
||||
"proof_scope": "deterministic_isolated_install_and_route_validation",
|
||||
"required_tier": "T2_runtime",
|
||||
"selected_installed_skills": [
|
||||
"teleo-infra-provenance",
|
||||
"teleo-leo-onboarding",
|
||||
"teleo-reconstruction-recovery"
|
||||
],
|
||||
"semantic_checks": {
|
||||
"authority": true,
|
||||
"candidate_only_gcp": true,
|
||||
"gcp_database": true,
|
||||
"live_service": true,
|
||||
"merged_reasoning_verifier": true,
|
||||
"merged_reconstruction": true,
|
||||
"next_safe_action": true,
|
||||
"vps_database": true
|
||||
},
|
||||
"status": "pass",
|
||||
"strongest_claim_allowed": "T2 deterministic isolated local skill install, route-contract, and path validation only; this artifact does not claim an agent reasoning run, and retained product proofs keep their own VPS, GCP-staging, isolated-clone, Telegram, and production claim ceilings"
|
||||
}
|
||||
|
|
@ -1,39 +1,51 @@
|
|||
{
|
||||
"pack": "leo-teleo-skill-pack",
|
||||
"created_date": "2026-07-09",
|
||||
"last_updated_utc": "2026-07-13T06:45:00Z",
|
||||
"status": "repo_native_validated",
|
||||
"last_updated_utc": "2026-07-15T00:16:53Z",
|
||||
"status": "repo_native_path_validated",
|
||||
"contains_secrets": false,
|
||||
"production_mutation_authorized": false,
|
||||
"repo_skill_root": ".agents/skills",
|
||||
"optional_local_install_target": "/Users/user/.codex/skills",
|
||||
"claim_ceiling": "Repo-native onboarding and operations skills. Generic and Helmer guarded-apply canaries pass 37/37 in disposable PostgreSQL. PR #72 source auto-synchronized to the VPS checkout without a gateway restart or profile-source delta; the permission migration was not applied, the apply worker remained disabled/inactive, and Helmer remained unapplied. Current source-composition/apply lifecycle proof is stronger and remains isolated. GCP gateway and exact canonical DB parity are proven across 39 tables and 52,164 rows, and the hardened adapter-free model replay passes 6/6 with exact count consistency. Durable GCP operator identity and retained clone cleanup remain open. Broad blind VPS reasoning is not reliable yet: independent strict judges accepted only 1/12 and 2/12 outright. Current skills require exact m3taversal naming, neutral follow-up labels, and current-v1 schema truth.",
|
||||
"claim_ceiling": "Repo-native onboarding and operations skills at T2 clean-context local runtime. PRs #146 and #147 are merged and own deterministic reconstruction plus unseen-reasoning verification. PR #148 is open candidate evidence only. Generic and Helmer guarded-apply canaries pass 37/37 in disposable PostgreSQL, while production rich packets remain unapplied. The latest disposable GCP clone matched 39 tables and 52,167 rows and a no-send model turn passed 18/18 runtime checks plus 6/6 reasoning outcomes; persistent GCP teleo_canonical remains staging and no promotion, production apply, or current Telegram-visible end-to-end proof is claimed.",
|
||||
"repository_reconciliation": {
|
||||
"merged_pull_requests": [146, 147],
|
||||
"candidate_only_pull_requests": [148],
|
||||
"canonical_reconstruction_contract": "docs/kb-rebuild-and-recompile.md",
|
||||
"canonical_turn_manifest": "scripts/leo_turn_execution_manifest.py",
|
||||
"canonical_unseen_reasoning_verifier": "scripts/verify_leo_unseen_reasoning_chain.py"
|
||||
},
|
||||
"skills": [
|
||||
{
|
||||
"name": "teleo-leo-onboarding",
|
||||
"role": "Company/product/architecture orientation for Leo and Teleo work",
|
||||
"role": "Company, product, architecture, task routing, and clean-context entry point",
|
||||
"path": ".agents/skills/teleo-leo-onboarding/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "working-leo-m3taversal-outcomes",
|
||||
"role": "Definition of working Leo from m3taversal outcomes and tests",
|
||||
"role": "Definition of working Leo, identity discipline, behavior, and proof ceiling",
|
||||
"path": ".agents/skills/working-leo-m3taversal-outcomes/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "teleo-vps-runtime-ops",
|
||||
"role": "VPS runtime navigation, service health, Docker/Postgres checks, report sync",
|
||||
"role": "VPS runtime navigation, incidents, service health, Postgres, and cleanup",
|
||||
"path": ".agents/skills/teleo-vps-runtime-ops/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "teleo-gcp-parity-ops",
|
||||
"role": "GCP operator access, private Cloud SQL parity, m3taversal replay, rollback, and cleanup",
|
||||
"role": "GCP access, private Cloud SQL parity, no-send replay, rollback, and cleanup",
|
||||
"path": ".agents/skills/teleo-gcp-parity-ops/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "teleo-kb-db-change-workflow",
|
||||
"role": "Strict proposal normalization, separated review/apply authority, isolated canary, and row-level proof",
|
||||
"role": "Source ingestion, proposal normalization, review, guarded apply, and receipts",
|
||||
"path": ".agents/skills/teleo-kb-db-change-workflow/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "teleo-reconstruction-recovery",
|
||||
"role": "Snapshot recovery, genesis-ledger replay, source recompilation, and incident recovery",
|
||||
"path": ".agents/skills/teleo-reconstruction-recovery/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "leo-telegram-canary-ops",
|
||||
"role": "Live Telegram bot testing through authenticated Chrome and Computer Use",
|
||||
|
|
@ -41,29 +53,121 @@
|
|||
},
|
||||
{
|
||||
"name": "teleo-infra-provenance",
|
||||
"role": "Repo/deploy/runtime provenance and path confusion prevention",
|
||||
"role": "Repository, deploy, runtime, database, and retained-proof provenance",
|
||||
"path": ".agents/skills/teleo-infra-provenance/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "teleo-proof-handoff",
|
||||
"role": "Evidence-indexed handoff format for Fable/Codex/Opus workers",
|
||||
"role": "Capability tiers, retained evidence, cleanup, and worker handoff",
|
||||
"path": ".agents/skills/teleo-proof-handoff/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "nousresearch-hermes-agent",
|
||||
"role": "Hermes agent packaging, skills, prompts, memory, model routing, and no-secret policy",
|
||||
"path": ".agents/skills/nousresearch-hermes-agent/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "teleo-db-operator",
|
||||
"role": "Read-first Teleo pipeline SQLite discovery, audit, backup, and guarded writes",
|
||||
"path": ".agents/skills/teleo-db-operator/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "private-password-storage",
|
||||
"role": "Device-local credential presence and secure-input handling without chat disclosure",
|
||||
"path": ".agents/skills/private-password-storage/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "crabbox",
|
||||
"role": "Isolated remote Linux and CI proof without production deployment",
|
||||
"path": ".agents/skills/crabbox/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "decision-engine-refinement",
|
||||
"role": "Replayable model, evaluator, rubric, and decision-engine quality work",
|
||||
"path": ".agents/skills/decision-engine-refinement/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "living-ip-kb-interop",
|
||||
"role": "Safe external-agent knowledge read and propose-first writeback contracts",
|
||||
"path": ".agents/skills/living-ip-kb-interop/SKILL.md"
|
||||
},
|
||||
{
|
||||
"name": "openclaw-agent",
|
||||
"role": "No-secret Living IP agent packaging for OpenClaw workspaces",
|
||||
"path": ".agents/skills/openclaw-agent/SKILL.md"
|
||||
}
|
||||
],
|
||||
"required_coverage": {
|
||||
"company_product_context": ["teleo-leo-onboarding"],
|
||||
"vps": ["teleo-vps-runtime-ops"],
|
||||
"gcp": ["teleo-gcp-parity-ops"],
|
||||
"hermes_runtime": ["nousresearch-hermes-agent", "teleo-vps-runtime-ops"],
|
||||
"database_provenance": ["teleo-infra-provenance", "teleo-db-operator"],
|
||||
"ingestion": ["teleo-kb-db-change-workflow", "living-ip-kb-interop"],
|
||||
"proposal_apply": ["teleo-kb-db-change-workflow"],
|
||||
"reconstruction": ["teleo-reconstruction-recovery"],
|
||||
"identity": ["teleo-leo-onboarding", "working-leo-m3taversal-outcomes"],
|
||||
"testing": ["teleo-leo-onboarding", "teleo-proof-handoff", "crabbox", "decision-engine-refinement"],
|
||||
"security": ["teleo-vps-runtime-ops", "teleo-gcp-parity-ops", "private-password-storage"],
|
||||
"secrets": ["private-password-storage"],
|
||||
"incident_recovery": ["teleo-reconstruction-recovery", "teleo-vps-runtime-ops"]
|
||||
},
|
||||
"reference_files": [
|
||||
"docs/reports/leo-working-state-20260709/skill-pack-clean-context-agent-review-current.json",
|
||||
"docs/reports/leo-working-state-20260709/skill-pack-clean-context-canary-current.json",
|
||||
"docs/reports/leo-working-state-20260709/current-truth-index.md",
|
||||
"docs/reports/leo-working-state-20260709/operator-surface-map.md",
|
||||
"docs/reports/leo-working-state-20260709/fable-leo-teleo-onboarding.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-db-first-working-leo-20260714.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-db-first-parity-current.json",
|
||||
"docs/reports/leo-working-state-20260709/gcp-db-first-blind-claim-current.json",
|
||||
"docs/reports/leo-working-state-20260709/gcp-db-first-cleanup-current.json",
|
||||
"docs/reports/leo-working-state-20260709/gcp-db-first-live-deploy-restart-current.json",
|
||||
"docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-cloud-sql-t3-live-readonly-current.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-cloudsql-studio-parity-gate-current.md",
|
||||
"docs/reports/leo-working-state-20260709/pr72-vps-auto-deploy-runtime-nonchange-current.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-leo-runtime-observability-current.md",
|
||||
"docs/reports/leo-working-state-20260709/gcp-canonical-parity-live-20260712.json",
|
||||
"docs/reports/leo-working-state-20260709/gcp-operator-access-blocker-current.json",
|
||||
"docs/reports/leo-working-state-20260709/telegram-visible-direct-claim-dc01-partial-current.json",
|
||||
"docs/reports/leo-working-state-20260709/working-leo-current-proof-20260712.md",
|
||||
"docs/reports/leo-working-state-20260709/working-leo-current-proof-20260712.json"
|
||||
"docs/reports/leo-working-state-20260709/leo-unified-turn-manifest-canary-current.json",
|
||||
"docs/reports/leo-working-state-20260709/leo-architecture-work-and-outcomes-20260714.md",
|
||||
"docs/kb-rebuild-and-recompile.md"
|
||||
],
|
||||
"validation": "tests/test_repo_skill_pack.py"
|
||||
"command_files": [
|
||||
".agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py",
|
||||
".agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py",
|
||||
".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py",
|
||||
"ops/run_local_canonical_postgres_rebuild.py",
|
||||
"ops/run_local_genesis_ledger_rebuild.py",
|
||||
"ops/capture_vps_canonical_postgres_snapshot.py",
|
||||
"ops/restore_gcp_generated_postgres_snapshot.py",
|
||||
"ops/verify_postgres_parity_manifest.py",
|
||||
"scripts/compile_kb_source_packet.py",
|
||||
"scripts/approve_proposal.py",
|
||||
"scripts/apply_proposal.py",
|
||||
"scripts/leo_turn_execution_manifest.py",
|
||||
"scripts/verify_leo_unseen_reasoning_chain.py",
|
||||
"tests/test_repo_skill_pack.py"
|
||||
],
|
||||
"scan_files": [
|
||||
"docs/reports/leo-working-state-20260709/skill-pack-readme.md",
|
||||
"docs/reports/leo-working-state-20260709/current-truth-index.md",
|
||||
"docs/reports/leo-working-state-20260709/operator-surface-map.md",
|
||||
"docs/reports/leo-working-state-20260709/fable-leo-teleo-onboarding.md",
|
||||
"docs/reports/leo-working-state-20260709/leo-architecture-work-and-outcomes-20260714.md",
|
||||
"README.md",
|
||||
"docs/kb-rebuild-and-recompile.md"
|
||||
],
|
||||
"executable_files": [
|
||||
".agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py",
|
||||
".agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py",
|
||||
".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py",
|
||||
".agents/skills/private-password-storage/scripts/private-password-storage"
|
||||
],
|
||||
"validator": ".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py",
|
||||
"installer": ".agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py",
|
||||
"isolated_install_canary": ".agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py",
|
||||
"fresh_agent_receipt": "docs/reports/leo-working-state-20260709/skill-pack-clean-context-agent-review-current.json",
|
||||
"validation": "tests/test_repo_skill_pack.py",
|
||||
"validation_commands": [
|
||||
".agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .",
|
||||
".agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py --root .",
|
||||
".venv/bin/python -m pytest -q tests/test_repo_skill_pack.py"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,91 @@
|
|||
# Leo / Teleo Skill Pack - 2026-07-09
|
||||
# Leo / Teleo Skill Pack - 2026-07-15
|
||||
|
||||
Purpose: reusable onboarding and operating knowledge for future Codex/Fable workers working on Leo, Teleo, the VPS, GCP parity, Telegram canaries, and KB database changes.
|
||||
Purpose: give a new engineer or agent one repo-native route into Leo/Teleo
|
||||
architecture, operations, database lifecycle, proof tiers, secrets, and recovery
|
||||
without relying on historical chat or unmerged branch prose.
|
||||
|
||||
This is the repo-native skill set under `.agents/skills/`. It is evidence-linked and validated by `tests/test_repo_skill_pack.py`. It does not contain secrets, private key contents, bot tokens, or production mutation authorization.
|
||||
The pack lives under `.agents/skills/`, contains no credentials, and grants no
|
||||
production database mutation or GCP promotion authority.
|
||||
|
||||
## Clean-Context Entry Point
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
.agents/skills/teleo-leo-onboarding/scripts/validate_skill_pack.py --root .
|
||||
.agents/skills/teleo-leo-onboarding/scripts/install_skill_pack.py \
|
||||
--root . \
|
||||
--target /private/tmp/teleo-clean-agent/skills
|
||||
.agents/skills/teleo-leo-onboarding/scripts/run_clean_context_canary.py --root .
|
||||
```
|
||||
|
||||
The validator uses standard-library Python through its executable shebang. For
|
||||
pytest, create the repository virtual environment from `README.md` and use
|
||||
`.venv/bin/python`; do not assume a bare `python` executable exists.
|
||||
|
||||
## Skills In This Pack
|
||||
|
||||
1. `teleo-leo-onboarding`: company/product/architecture orientation before touching Leo or Teleo.
|
||||
2. `working-leo-m3taversal-outcomes`: m3taversal's expected Leo behavior and the current benchmark.
|
||||
3. `teleo-vps-runtime-ops`: VPS service, paths, Postgres, Docker, report sync, and stability checks.
|
||||
4. `teleo-gcp-parity-ops`: passwordless GCP access, Cloud SQL parity, no-send replay, rollback, and cleanup.
|
||||
5. `teleo-kb-db-change-workflow`: source composition plus approved proposal to canonical-row workflow with clone rehearsal and rollback.
|
||||
6. `leo-telegram-canary-ops`: live Telegram/Chrome/Computer Use canaries for Leo bot behavior.
|
||||
7. `teleo-infra-provenance`: repo, deploy, mirror, and runtime provenance map.
|
||||
8. `teleo-proof-handoff`: evidence bundle and Fable/Codex worker handoff protocol.
|
||||
1. `teleo-leo-onboarding`: product, architecture, task router, and first canary.
|
||||
2. `working-leo-m3taversal-outcomes`: exact identity discipline and working-Leo proof ceiling.
|
||||
3. `teleo-vps-runtime-ops`: VPS services, Postgres, incidents, and cleanup.
|
||||
4. `teleo-gcp-parity-ops`: GCP/Cloud SQL parity, no-send replay, and rollback.
|
||||
5. `teleo-kb-db-change-workflow`: source ingestion through guarded proposal/apply.
|
||||
6. `teleo-reconstruction-recovery`: snapshot, ledger, and semantic recovery modes.
|
||||
7. `leo-telegram-canary-ops`: authorized Leo-specific Telegram canaries.
|
||||
8. `teleo-infra-provenance`: repository, deploy, runtime, DB, and proof provenance.
|
||||
9. `teleo-proof-handoff`: tiered evidence and continuation handoffs.
|
||||
10. `nousresearch-hermes-agent`: Hermes packaging and runtime boundaries.
|
||||
11. `teleo-db-operator`: read-first pipeline SQLite operation.
|
||||
12. `private-password-storage`: device-local secure input and presence checks.
|
||||
13. `crabbox`: isolated remote Linux and CI proof.
|
||||
14. `decision-engine-refinement`: replayable evaluator and model-quality work.
|
||||
15. `living-ip-kb-interop`: propose-first external-agent KB access.
|
||||
16. `openclaw-agent`: no-secret OpenClaw workspace packaging.
|
||||
|
||||
The machine-readable topic coverage and exact paths are in
|
||||
`docs/reports/leo-working-state-20260709/skill-pack-manifest.json`.
|
||||
|
||||
## Repository Reconciliation
|
||||
|
||||
- PR #146 is merged: deterministic turn manifests and the first exact
|
||||
genesis-plus-ledger reconstruction slice are canonical `main` behavior.
|
||||
- PR #147 is merged: the unseen-reasoning verifier accepts valid reasoning
|
||||
variance while keeping evidence and receipt gates.
|
||||
- PR #148 is open candidate evidence only. Its proposed least-privilege GCP
|
||||
files and live end state are not canonical until merged and receipt-verified.
|
||||
|
||||
## Current Claim Ceiling
|
||||
|
||||
- VPS Leo Telegram memory, KB audit, and staged write canaries are live-proven.
|
||||
- A strict canonical `add_edge` apply canary is live-proven.
|
||||
- Deterministic source composition, full-data clone composition/reasoning, and guarded approved-bundle application are isolated-proven with exact source/evidence/claim links, row deltas, rollback, and cleanup. Production rich packets remain unapplied.
|
||||
- Narrow VPS DB truth and restart behavior are proven. Broad blind reasoning is not yet reliable; exact participant naming, neutral labels, and current-v1 schema guards are now required.
|
||||
- GCP gateway liveness, private Cloud SQL identity, exact VPS-to-GCP canonical parity, and hardened adapter-free model replay are live-proven. The database matches across `39` tables and `52,164` rows. Durable operator identity and retained replay-clone cleanup remain open because the intended target service accounts are absent.
|
||||
- T2 clean-context local skill install, route validation, and focused tests are
|
||||
the onboarding target for this pack.
|
||||
- VPS no-send behavior, isolated proposal/apply, snapshot recovery, disposable
|
||||
GCP parity, and bounded no-send GCP reasoning have retained evidence.
|
||||
- Persistent GCP `teleo_canonical` remains staging. No GCP promotion or cutover
|
||||
is authorized or claimed.
|
||||
- Production rich packets remain unapplied. A current Telegram-visible complete
|
||||
flow is not proven by this pack.
|
||||
|
||||
## Key References
|
||||
|
||||
- Current whole-system proof: `docs/reports/leo-working-state-20260709/working-leo-current-proof-20260712.md` and `.json`
|
||||
- Current truth index: `docs/reports/leo-working-state-20260709/current-truth-index.md`
|
||||
- Operator surface map: `docs/reports/leo-working-state-20260709/operator-surface-map.md`
|
||||
- Fable onboarding prompt: `docs/reports/leo-working-state-20260709/fable-leo-teleo-onboarding.md`
|
||||
- PR #72 VPS auto-deploy/runtime readback: `docs/reports/leo-working-state-20260709/pr72-vps-auto-deploy-runtime-nonchange-current.md`
|
||||
- GCP canonical parity: `docs/reports/leo-working-state-20260709/gcp-canonical-parity-live-20260712.json`
|
||||
- GCP model replay (legacy artifact filename): `docs/reports/leo-working-state-20260709/gcp-cory-model-replay-current.json`
|
||||
- Three-day delivery delta: `docs/reports/leo-working-state-20260709/working-leo-three-day-delivery-summary-20260713.md`
|
||||
- Blind out-of-sample audit: `docs/reports/leo-working-state-20260709/telegram-handler-blind-oos-audit-current.json`
|
||||
- GCP operator access gate: `docs/reports/leo-working-state-20260709/gcp-operator-access-blocker-current.json`
|
||||
- Skill manifest: `skill-pack-manifest.json`
|
||||
- Fresh-agent onboarding receipt: `docs/reports/leo-working-state-20260709/skill-pack-clean-context-agent-review-current.json`
|
||||
- Isolated-install acceptance receipt: `docs/reports/leo-working-state-20260709/skill-pack-clean-context-canary-current.json`
|
||||
- Current truth: `docs/reports/leo-working-state-20260709/current-truth-index.md`
|
||||
- Architecture/outcomes: `docs/reports/leo-working-state-20260709/leo-architecture-work-and-outcomes-20260714.md`
|
||||
- Recovery contract: `docs/kb-rebuild-and-recompile.md`
|
||||
- GCP database-first proof: `docs/reports/leo-working-state-20260709/gcp-db-first-working-leo-20260714.md`
|
||||
- GCP no-send reasoning receipt: `docs/reports/leo-working-state-20260709/gcp-db-first-blind-claim-current.json`
|
||||
- Unified turn receipt: `docs/reports/leo-working-state-20260709/leo-unified-turn-manifest-canary-current.json`
|
||||
- Operator map: `docs/reports/leo-working-state-20260709/operator-surface-map.md`
|
||||
|
||||
## Discovery
|
||||
## Team Change Notes
|
||||
|
||||
Codex-compatible workers should discover the committed `.agents/skills/` tree from this repository. The manifest is the index; each skill points to current retained evidence rather than embedding secrets or pretending a stale snapshot is live truth.
|
||||
- Replaced the missing legacy GCP replay route with the current database-first
|
||||
no-send receipt.
|
||||
- Added deterministic install and path-validation commands.
|
||||
- Added separate T2 receipts for deterministic isolated install/path validation
|
||||
and fresh-context agent reasoning, stale-claim rejection, and cleanup.
|
||||
- Added an explicit topic router and recovery skill.
|
||||
- Added merged-vs-open PR boundaries so branch candidates cannot silently become
|
||||
source-of-truth instructions.
|
||||
- Added tests for coverage, route existence, installer behavior, and exact
|
||||
m3taversal identity prose.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"schema": "livingip.observatoryReadAdapterCurrentGate.v1",
|
||||
"current_canary": "Authenticated GET /v1/claims/sample in GCP staging returns a provenance-bearing teleo_canonical claim, anonymous GET returns 401, and authenticated POST returns 405.",
|
||||
"permission_profile": "approval_policy_never_with_unrestricted_filesystem_and_network",
|
||||
"attempted_routes": [
|
||||
{
|
||||
"route": "merged GitHub Actions deployment with artifact-builder WIF",
|
||||
"result": "image build, smoke, and push passed; deploy stopped because run.googleapis.com is disabled"
|
||||
},
|
||||
{
|
||||
"route": "local billy@livingip.xyz gcloud user credential",
|
||||
"result": "token refresh and service enable both stop at noninteractive Google reauthentication"
|
||||
},
|
||||
{
|
||||
"route": "local application-default credential",
|
||||
"result": "token refresh stops at noninteractive Google reauthentication"
|
||||
},
|
||||
{
|
||||
"route": "other locally authenticated Google accounts",
|
||||
"result": "valid tokens but no access to project teleo-501523"
|
||||
},
|
||||
{
|
||||
"route": "fixed read-only GCP IAP status workflow",
|
||||
"result": "WIF token exchange failed because the teleo-iap-operator provider target is missing or disabled"
|
||||
},
|
||||
{
|
||||
"route": "local browser or computer-use session",
|
||||
"result": "no browser or computer-use tool is exposed to this task; no foreground takeover was attempted"
|
||||
}
|
||||
],
|
||||
"exact_gate": {
|
||||
"first_infrastructure_gate": "Cloud Run Admin API run.googleapis.com is disabled in teleo-501523.",
|
||||
"current_authorization_gate": "The only local account authorized for teleo-501523, billy@livingip.xyz, requires Google password or MFA reauthentication before project APIs and staging IAM resources can be changed.",
|
||||
"alternate_private_operator_gate": "The read-only teleo-iap-operator WIF provider returns invalid_target and cannot reach its status operation."
|
||||
},
|
||||
"why_autonomous_repair_stops": "Google password or MFA reauthentication is a human-only credential boundary, and no authenticated project-admin browser or computer-use surface is available in this task.",
|
||||
"clear_CTA": "Run `gcloud auth login billy@livingip.xyz --update-adc`, complete Google password or MFA, and then report `GCP reauth complete`.",
|
||||
"next_non_user_action": "Enable the required staging APIs; create or verify the runtime service account, API-key secret, Cloud SQL IAM database user, and exact read-only role grants; deploy the retained immutable image; capture authenticated GET, anonymous 401, authenticated POST 405, SQL write-denial, exact service/database identity, and cleanup receipts.",
|
||||
"production_boundary": "Do not change Vercel, production routing, Cloud SQL network exposure, or any write endpoint."
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"schema": "livingip.observatoryReadAdapterStagingReceipt.v1",
|
||||
"status": "blocked_before_runtime_creation",
|
||||
"required_runtime_tier": "T3_live_gcp_staging_api",
|
||||
"current_runtime_tier": "below_T3_build_artifact_only",
|
||||
"project_id": "teleo-501523",
|
||||
"region": "europe-west6",
|
||||
"service_name": "observatory-read-adapter-staging",
|
||||
"database_name": "teleo_canonical",
|
||||
"cloud_sql_instance": "teleo-501523:europe-west6:teleo-pgvector-standby",
|
||||
"authorization_role": "kb_observatory_read",
|
||||
"runtime_service_account": "sa-observatory-read-adapter@teleo-501523.iam.gserviceaccount.com",
|
||||
"image": {
|
||||
"revision": "53c8de0a1959a744e17888df1a70e935dfef0143",
|
||||
"digest": "sha256:b5c9b2051559730cbb9003d74e5d8e577fa2f43f49b72577f251f2843a0650c5",
|
||||
"artifact_registry_push": "pass",
|
||||
"container_smoke": "pass",
|
||||
"intentional_retained_resource": true
|
||||
},
|
||||
"workflow": {
|
||||
"run_id": 29382683335,
|
||||
"url": "https://github.com/living-ip/teleo-infrastructure/actions/runs/29382683335",
|
||||
"build_job": "success",
|
||||
"deploy_job": "failure",
|
||||
"failure_class": "SERVICE_DISABLED",
|
||||
"failure_detail": "run.googleapis.com is disabled in teleo-501523",
|
||||
"authenticated_as": "sa-artifact-builder@teleo-501523.iam.gserviceaccount.com"
|
||||
},
|
||||
"live_receipts": {
|
||||
"service_url": null,
|
||||
"authenticated_get_sample": null,
|
||||
"anonymous_get_401": null,
|
||||
"authenticated_post_405": null,
|
||||
"database_write_denial": null,
|
||||
"database_identity": null
|
||||
},
|
||||
"negative_claims": {
|
||||
"cloud_run_service_created": false,
|
||||
"cloud_sql_public_exposure_changed": false,
|
||||
"production_or_vercel_routing_changed": false,
|
||||
"credentials_logged": false
|
||||
},
|
||||
"cleanup": {
|
||||
"github_jobs_completed": true,
|
||||
"local_observatory_test_containers": 0,
|
||||
"local_observatory_test_images": 0,
|
||||
"orphan_local_build_processes": 0,
|
||||
"artifact_registry_image_retained_for_next_deploy": true
|
||||
},
|
||||
"proof_paths": [
|
||||
"/private/tmp/observatory-read-adapter-run-29382683335/observatory-adapter-image/observatory-adapter-image.txt",
|
||||
"/private/tmp/observatory-iap-status-29382844567/gcp-iap-operator-29382844567/result.json"
|
||||
],
|
||||
"claim_ceiling": "The immutable staging image exists, but no live API or database query receipt exists. T3 is not complete."
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"fallback_goal_path": "goal.md",
|
||||
"platform_goal_after": {
|
||||
"objective": "Complete and verify the private GCP Observatory read adapter defined in this goal file, stopping before production repoint without exact authorization.",
|
||||
"status": "active",
|
||||
"thread_id": "019f6350-69fa-71f3-944b-3fc0065f4fec"
|
||||
},
|
||||
"platform_goal_before": null,
|
||||
"platform_goal_repair_action": "create_goal",
|
||||
"platform_goal_replaced": false,
|
||||
"why_not_replaced": "No prior platform Goal existed; a new active Goal was created from goal.md."
|
||||
}
|
||||
52
fixtures/working-leo/document-ingestion-v2.scenario.json
Normal file
52
fixtures/working-leo/document-ingestion-v2.scenario.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"schema": "livingip.workingLeoSourceIngestionScenario.v2",
|
||||
"artifact": {
|
||||
"path": "document-ingestion-v2.txt",
|
||||
"format": "plain_text"
|
||||
},
|
||||
"source": {
|
||||
"identity": "document:working-leo-review-gated-composition-v2",
|
||||
"source_key": "working_leo_document_ingestion_v2",
|
||||
"source_type": "article",
|
||||
"title": "Working Leo review-gated composition note",
|
||||
"locator": "fixture://working-leo/document-ingestion-v2",
|
||||
"metadata": {
|
||||
"author": "Working Leo synthetic canary fixture",
|
||||
"captured_at": "2026-07-15T00:00:00Z",
|
||||
"document_kind": "operating_note",
|
||||
"language": "en",
|
||||
"synthetic": true
|
||||
}
|
||||
},
|
||||
"extractor": {
|
||||
"name": "deterministic_fixture_replay",
|
||||
"version": "2"
|
||||
},
|
||||
"extraction": {
|
||||
"claims": [
|
||||
{
|
||||
"claim_key": "staged_proposal_remains_noncanonical",
|
||||
"type": "structural",
|
||||
"confidence": 0.75,
|
||||
"text": "A staged knowledge proposal remains non-canonical until review and guarded apply succeed.",
|
||||
"body": "A staged proposal remains non-canonical until an operator reviews it and a separate guarded apply succeeds.",
|
||||
"metadata": {
|
||||
"needs_research": false
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"segment_id": "paragraph-2",
|
||||
"role": "grounds",
|
||||
"excerpt": "A staged proposal remains non-canonical until an operator reviews it and a separate guarded apply succeeds.",
|
||||
"metadata": {
|
||||
"evidence_kind": "exact_quote"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
],
|
||||
"duplicates": [],
|
||||
"conflicts": []
|
||||
}
|
||||
}
|
||||
5
fixtures/working-leo/document-ingestion-v2.txt
Normal file
5
fixtures/working-leo/document-ingestion-v2.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Working Leo composition note. A newly captured document may produce claim and evidence candidates, but those candidates are not canonical knowledge.
|
||||
|
||||
A staged proposal remains non-canonical until an operator reviews it and a separate guarded apply succeeds. The proposal must retain the source content hash, exact evidence excerpt, and source identity so reviewers can trace every planned row back to the captured artifact.
|
||||
|
||||
For a staging-only rehearsal, pending_review is the terminal state. No public knowledge-base row should be written.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{"ts":"2026-07-15T09:00:01Z","chat_id":900001,"chat_title":"Synthetic review gate thread","message_id":7301,"type":"message","username":"fixture_analyst","display_name":"Fixture Analyst","user_id":910001,"message":"A pending review does not change canonical knowledge; only a separate guarded apply can do that.","reply_to":null,"synthetic":true}
|
||||
{"ts":"2026-07-15T09:00:12Z","chat_id":900001,"chat_title":"Synthetic review gate thread","message_id":7302,"type":"message","username":"fixture_operator","display_name":"Fixture Operator","user_id":910002,"message":"Reviewer approval alone makes the proposal canonical immediately; no apply step is needed.","reply_to":7301,"synthetic":true}
|
||||
{"ts":"2026-07-15T09:00:24Z","chat_id":900001,"chat_title":"Synthetic review gate thread","message_id":7303,"type":"message","username":"fixture_reviewer","display_name":"Fixture Reviewer","user_id":910003,"message":"A pending review does not change canonical knowledge; only a separate guarded apply can do that.","reply_to":7302,"synthetic":true}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"schema": "livingip.workingLeoSourceIngestionScenario.v2",
|
||||
"artifact": {
|
||||
"path": "telegram-transcript-ingestion-v1.jsonl",
|
||||
"format": "telegram_jsonl"
|
||||
},
|
||||
"source": {
|
||||
"identity": "fixture:telegram-chat-900001-export-20260715",
|
||||
"source_key": "telegram_review_gate_exchange_20260715",
|
||||
"source_type": "transcript",
|
||||
"title": "Synthetic Telegram review-gate exchange",
|
||||
"locator": "fixture://working-leo/telegram-transcript-ingestion-v1",
|
||||
"metadata": {
|
||||
"captured_at": "2026-07-15T09:01:00Z",
|
||||
"export_format": "teleo_append_only_telegram_jsonl",
|
||||
"language": "en",
|
||||
"synthetic": true
|
||||
}
|
||||
},
|
||||
"extractor": {
|
||||
"name": "deterministic_telegram_jsonl_replay",
|
||||
"version": "1"
|
||||
},
|
||||
"extraction": {
|
||||
"claims": [
|
||||
{
|
||||
"claim_key": "pending_review_does_not_change_canonical",
|
||||
"type": "structural",
|
||||
"confidence": 0.7,
|
||||
"text": "Pending review alone does not change canonical knowledge.",
|
||||
"body": "A pending review does not change canonical knowledge; only a separate guarded apply can do that.",
|
||||
"metadata": {
|
||||
"needs_research": false
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"segment_id": "message-900001-7301",
|
||||
"role": "grounds",
|
||||
"excerpt": "A pending review does not change canonical knowledge; only a separate guarded apply can do that.",
|
||||
"metadata": {
|
||||
"evidence_kind": "exact_message"
|
||||
}
|
||||
},
|
||||
{
|
||||
"segment_id": "message-900001-7303",
|
||||
"role": "illustrates",
|
||||
"excerpt": "A pending review does not change canonical knowledge; only a separate guarded apply can do that.",
|
||||
"metadata": {
|
||||
"evidence_kind": "duplicate_exact_message"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"edge_type": "contradicts",
|
||||
"target": "approval_alone_makes_canonical"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"claim_key": "approval_alone_makes_canonical",
|
||||
"type": "empirical",
|
||||
"confidence": 0.4,
|
||||
"text": "Reviewer approval alone makes a proposal canonical without apply.",
|
||||
"body": "Reviewer approval alone makes the proposal canonical immediately; no apply step is needed.",
|
||||
"metadata": {
|
||||
"needs_research": true
|
||||
},
|
||||
"evidence": [
|
||||
{
|
||||
"segment_id": "message-900001-7302",
|
||||
"role": "grounds",
|
||||
"excerpt": "Reviewer approval alone makes the proposal canonical immediately; no apply step is needed.",
|
||||
"metadata": {
|
||||
"evidence_kind": "exact_message"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": []
|
||||
}
|
||||
],
|
||||
"duplicates": [
|
||||
{
|
||||
"segment_id": "message-900001-7303",
|
||||
"duplicate_of_claim_key": "pending_review_does_not_change_canonical",
|
||||
"excerpt": "A pending review does not change canonical knowledge; only a separate guarded apply can do that.",
|
||||
"reason": "Message 7303 repeats message 7301 exactly and is retained as explicit duplicate evidence."
|
||||
}
|
||||
],
|
||||
"conflicts": [
|
||||
{
|
||||
"from_claim_key": "pending_review_does_not_change_canonical",
|
||||
"to_claim_key": "approval_alone_makes_canonical",
|
||||
"relationship": "contradicts",
|
||||
"reason": "The two candidate propositions assert incompatible canonical-state transitions."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
6
observatory_read_adapter/__init__.py
Normal file
6
observatory_read_adapter/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"""Authenticated, read-only Observatory adapter for canonical Teleo claims."""
|
||||
|
||||
from .app import create_app
|
||||
from .config import Settings
|
||||
|
||||
__all__ = ["Settings", "create_app"]
|
||||
18
observatory_read_adapter/__main__.py
Normal file
18
observatory_read_adapter/__main__.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from .app import create_app
|
||||
from .config import Settings
|
||||
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
settings = Settings.from_env()
|
||||
web.run_app(create_app(settings), host="0.0.0.0", port=settings.port)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
111
observatory_read_adapter/app.py
Normal file
111
observatory_read_adapter/app.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import hmac
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from .config import Settings
|
||||
from .db import CloudSqlClaimReader
|
||||
|
||||
logger = logging.getLogger("teleo.observatory_read_adapter")
|
||||
|
||||
SETTINGS_KEY = web.AppKey("observatory_settings", Settings)
|
||||
READER_KEY = web.AppKey("observatory_reader", object)
|
||||
PUBLIC_PATHS = frozenset({"/healthz", "/readyz"})
|
||||
|
||||
|
||||
def _private_json(payload: dict[str, Any], *, status: int = 200) -> web.Response:
|
||||
response = web.json_response(payload, status=status)
|
||||
response.headers["Cache-Control"] = "private, no-store, max-age=0"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
response.headers["Vary"] = "X-Api-Key"
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
return response
|
||||
|
||||
|
||||
@web.middleware
|
||||
async def api_key_auth(request: web.Request, handler: Any) -> web.StreamResponse:
|
||||
if request.path in PUBLIC_PATHS:
|
||||
return await handler(request)
|
||||
settings = request.app[SETTINGS_KEY]
|
||||
provided = request.headers.get("X-Api-Key", "")
|
||||
try:
|
||||
authenticated = hmac.compare_digest(
|
||||
provided.encode("ascii"),
|
||||
settings.api_key.encode("ascii"),
|
||||
)
|
||||
except UnicodeEncodeError:
|
||||
authenticated = False
|
||||
if not authenticated:
|
||||
return _private_json({"error": "unauthorized"}, status=401)
|
||||
return await handler(request)
|
||||
|
||||
|
||||
async def healthz(_request: web.Request) -> web.Response:
|
||||
return web.json_response({"status": "ok"})
|
||||
|
||||
|
||||
async def readyz(request: web.Request) -> web.Response:
|
||||
reader = request.app[READER_KEY]
|
||||
try:
|
||||
await asyncio.wait_for(asyncio.to_thread(reader.check_ready), timeout=12.0)
|
||||
except Exception as exc:
|
||||
logger.warning("readiness check failed (%s)", type(exc).__name__)
|
||||
return web.json_response({"status": "unavailable"}, status=503)
|
||||
return web.json_response({"status": "ready"})
|
||||
|
||||
|
||||
async def get_sample_claim(request: web.Request) -> web.Response:
|
||||
return await _claim_response(request, None)
|
||||
|
||||
|
||||
async def get_claim(request: web.Request) -> web.Response:
|
||||
raw_claim_id = request.match_info["claim_id"]
|
||||
try:
|
||||
claim_id = str(uuid.UUID(raw_claim_id))
|
||||
except ValueError:
|
||||
return _private_json({"error": "invalid_claim_id"}, status=400)
|
||||
return await _claim_response(request, claim_id)
|
||||
|
||||
|
||||
async def _claim_response(request: web.Request, claim_id: str | None) -> web.Response:
|
||||
reader = request.app[READER_KEY]
|
||||
try:
|
||||
payload = await asyncio.wait_for(
|
||||
asyncio.to_thread(reader.read_claim, claim_id),
|
||||
timeout=12.0,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.error("canonical claim read failed (%s)", type(exc).__name__)
|
||||
return _private_json({"error": "canonical_claim_unavailable"}, status=503)
|
||||
if payload is None:
|
||||
return _private_json({"error": "claim_not_found"}, status=404)
|
||||
return _private_json(payload)
|
||||
|
||||
|
||||
async def _cleanup(app: web.Application) -> None:
|
||||
reader = app[READER_KEY]
|
||||
await asyncio.to_thread(reader.close)
|
||||
|
||||
|
||||
def create_app(
|
||||
settings: Settings | None = None,
|
||||
*,
|
||||
reader: Any | None = None,
|
||||
) -> web.Application:
|
||||
settings = settings or Settings.from_env()
|
||||
settings.validate()
|
||||
reader = reader or CloudSqlClaimReader(settings)
|
||||
app = web.Application(middlewares=[api_key_auth], client_max_size=16 * 1024)
|
||||
app[SETTINGS_KEY] = settings
|
||||
app[READER_KEY] = reader
|
||||
app.router.add_get("/healthz", healthz)
|
||||
app.router.add_get("/readyz", readyz)
|
||||
app.router.add_get("/v1/claims/sample", get_sample_claim)
|
||||
app.router.add_get("/v1/claims/{claim_id}", get_claim)
|
||||
app.on_cleanup.append(_cleanup)
|
||||
return app
|
||||
61
observatory_read_adapter/config.py
Normal file
61
observatory_read_adapter/config.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
EXPECTED_PROJECT = "teleo-501523"
|
||||
EXPECTED_REGION = "europe-west6"
|
||||
EXPECTED_INSTANCE = "teleo-pgvector-standby"
|
||||
EXPECTED_CONNECTION_NAME = f"{EXPECTED_PROJECT}:{EXPECTED_REGION}:{EXPECTED_INSTANCE}"
|
||||
EXPECTED_DATABASE = "teleo_canonical"
|
||||
EXPECTED_AUTHORIZATION_ROLE = "kb_observatory_read"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Settings:
|
||||
project_id: str
|
||||
instance_connection_name: str
|
||||
database: str
|
||||
iam_database_user: str
|
||||
authorization_role: str
|
||||
api_key: str = field(repr=False)
|
||||
service_revision: str = "unknown"
|
||||
port: int = 8080
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> Settings:
|
||||
settings = cls(
|
||||
project_id=os.environ.get("GCP_PROJECT_ID", ""),
|
||||
instance_connection_name=os.environ.get("CLOUD_SQL_INSTANCE", ""),
|
||||
database=os.environ.get("DB_NAME", ""),
|
||||
iam_database_user=os.environ.get("DB_IAM_USER", ""),
|
||||
authorization_role=os.environ.get("DB_AUTHORIZATION_ROLE", ""),
|
||||
api_key=os.environ.get("OBSERVATORY_API_KEY", ""),
|
||||
service_revision=os.environ.get("SERVICE_REVISION", "unknown"),
|
||||
port=int(os.environ.get("PORT", "8080")),
|
||||
)
|
||||
settings.validate()
|
||||
return settings
|
||||
|
||||
def validate(self) -> None:
|
||||
if self.project_id != EXPECTED_PROJECT:
|
||||
raise ValueError(f"GCP_PROJECT_ID must be {EXPECTED_PROJECT}")
|
||||
if self.instance_connection_name != EXPECTED_CONNECTION_NAME:
|
||||
raise ValueError(f"CLOUD_SQL_INSTANCE must be {EXPECTED_CONNECTION_NAME}")
|
||||
if self.database != EXPECTED_DATABASE:
|
||||
raise ValueError(f"DB_NAME must be {EXPECTED_DATABASE}")
|
||||
if self.authorization_role != EXPECTED_AUTHORIZATION_ROLE:
|
||||
raise ValueError(f"DB_AUTHORIZATION_ROLE must be {EXPECTED_AUTHORIZATION_ROLE}")
|
||||
if not self.iam_database_user.endswith(f"@{EXPECTED_PROJECT}.iam"):
|
||||
raise ValueError("DB_IAM_USER must be the scoped Cloud SQL service-account user")
|
||||
if any(character.isspace() for character in self.iam_database_user):
|
||||
raise ValueError("DB_IAM_USER must not contain whitespace")
|
||||
if (
|
||||
len(self.api_key) < 32
|
||||
or len(self.api_key) > 512
|
||||
or not self.api_key.isascii()
|
||||
or any(character.isspace() for character in self.api_key)
|
||||
):
|
||||
raise ValueError("OBSERVATORY_API_KEY must be 32-512 non-whitespace ASCII characters")
|
||||
if not 1 <= self.port <= 65535:
|
||||
raise ValueError("PORT is outside the valid TCP port range")
|
||||
315
observatory_read_adapter/db.py
Normal file
315
observatory_read_adapter/db.py
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import date, datetime, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from .config import Settings
|
||||
|
||||
ConnectionFactory = Callable[[], Any]
|
||||
|
||||
|
||||
class ReaderContractError(RuntimeError):
|
||||
"""The live database session does not match the adapter's read-only contract."""
|
||||
|
||||
|
||||
def _json_value(value: Any) -> Any:
|
||||
if isinstance(value, Decimal):
|
||||
return float(value)
|
||||
if isinstance(value, (date, datetime)):
|
||||
return value.isoformat()
|
||||
if isinstance(value, tuple):
|
||||
return list(value)
|
||||
return value
|
||||
|
||||
|
||||
def _column_name(column: Any) -> str:
|
||||
return str(column.name) if hasattr(column, "name") else str(column[0])
|
||||
|
||||
|
||||
def _row_dict(cursor: Any, row: Any) -> dict[str, Any] | None:
|
||||
if row is None:
|
||||
return None
|
||||
names = [_column_name(column) for column in cursor.description]
|
||||
return {name: _json_value(value) for name, value in zip(names, row, strict=True)}
|
||||
|
||||
|
||||
def _rows(cursor: Any) -> list[dict[str, Any]]:
|
||||
names = [_column_name(column) for column in cursor.description]
|
||||
return [
|
||||
{name: _json_value(value) for name, value in zip(names, row, strict=True)}
|
||||
for row in cursor.fetchall()
|
||||
]
|
||||
|
||||
|
||||
class CloudSqlClaimReader:
|
||||
def __init__(self, settings: Settings, *, connection_factory: ConnectionFactory | None = None):
|
||||
self.settings = settings
|
||||
self._connector: Any | None = None
|
||||
if connection_factory is not None:
|
||||
self._connection_factory = connection_factory
|
||||
return
|
||||
|
||||
from google.cloud.sql.connector import Connector, IPTypes
|
||||
|
||||
self._connector = Connector(refresh_strategy="LAZY")
|
||||
|
||||
def connect() -> Any:
|
||||
return self._connector.connect(
|
||||
settings.instance_connection_name,
|
||||
"pg8000",
|
||||
user=settings.iam_database_user,
|
||||
db=settings.database,
|
||||
enable_iam_auth=True,
|
||||
ip_type=IPTypes.PRIVATE,
|
||||
)
|
||||
|
||||
self._connection_factory = connect
|
||||
|
||||
def close(self) -> None:
|
||||
if self._connector is not None:
|
||||
self._connector.close()
|
||||
|
||||
def check_ready(self) -> dict[str, Any]:
|
||||
connection = self._connection_factory()
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
self._begin_read_only(cursor)
|
||||
session = self._load_session_contract(cursor)
|
||||
connection.rollback()
|
||||
return {
|
||||
"ready": True,
|
||||
"database": session["database"],
|
||||
"authorization_role": self.settings.authorization_role,
|
||||
"transaction_read_only": True,
|
||||
}
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
def read_claim(self, claim_id: str | None = None) -> dict[str, Any] | None:
|
||||
connection = self._connection_factory()
|
||||
try:
|
||||
cursor = connection.cursor()
|
||||
self._begin_read_only(cursor)
|
||||
session = self._load_session_contract(cursor)
|
||||
claim = self._load_claim(cursor, claim_id)
|
||||
if claim is None:
|
||||
connection.rollback()
|
||||
return None
|
||||
|
||||
evidence = self._load_evidence(cursor, claim["id"])
|
||||
edges = self._load_edges(cursor, claim["id"])
|
||||
proposal_counts = self._load_proposal_counts(cursor)
|
||||
related_proposals = self._load_related_proposals(cursor, claim["id"])
|
||||
recent_proposals = self._load_recent_proposals(cursor)
|
||||
connection.rollback()
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
generated_at = datetime.now(timezone.utc).isoformat()
|
||||
return {
|
||||
"schema": "livingip.observatory-canonical-claim.v1",
|
||||
"generated_at": generated_at,
|
||||
"read_only": True,
|
||||
"provenance": {
|
||||
"gcp_project": self.settings.project_id,
|
||||
"cloud_sql_instance": self.settings.instance_connection_name,
|
||||
"database": session["database"],
|
||||
"database_principal": session["database_principal"],
|
||||
"authorization_role": self.settings.authorization_role,
|
||||
"transaction_read_only": True,
|
||||
"write_privileges_denied": bool(session["required_writes_denied"]),
|
||||
"service_revision": self.settings.service_revision,
|
||||
},
|
||||
"canonical": {
|
||||
"relation": "public.claims",
|
||||
"claim": claim,
|
||||
"evidence_relation": "public.claim_evidence -> public.sources",
|
||||
"evidence": evidence,
|
||||
"edge_relation": "public.claim_edges",
|
||||
"edges": edges,
|
||||
},
|
||||
"proposal_ledger": {
|
||||
"relation": "kb_stage.kb_proposals",
|
||||
"distinct_from_canonical": True,
|
||||
"status_counts": proposal_counts,
|
||||
"related": related_proposals,
|
||||
"recent": recent_proposals,
|
||||
"semantics": {
|
||||
"pending_review": "staged only; not canonical",
|
||||
"approved": "reviewed intent; not canonical until applied_at is set",
|
||||
"applied": "proposal receipt only; canonical rows remain authoritative",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _begin_read_only(cursor: Any) -> None:
|
||||
cursor.execute("set transaction read only")
|
||||
cursor.execute("set local statement_timeout = '5000ms'")
|
||||
cursor.execute("set local lock_timeout = '1000ms'")
|
||||
|
||||
def _load_session_contract(self, cursor: Any) -> dict[str, Any]:
|
||||
cursor.execute(
|
||||
"""
|
||||
select current_database() as database,
|
||||
current_user as database_principal,
|
||||
current_setting('transaction_read_only') as transaction_read_only,
|
||||
pg_has_role(current_user, %s, 'member') as has_authorization_role,
|
||||
has_table_privilege(current_user, 'public.claims', 'select')
|
||||
and has_table_privilege(current_user, 'public.sources', 'select')
|
||||
and has_table_privilege(current_user, 'public.claim_evidence', 'select')
|
||||
and has_table_privilege(current_user, 'public.claim_edges', 'select')
|
||||
and has_table_privilege(current_user, 'kb_stage.kb_proposals', 'select')
|
||||
as has_required_reads,
|
||||
not (
|
||||
has_table_privilege(current_user, 'public.claims', 'insert,update,delete,truncate')
|
||||
or has_table_privilege(current_user, 'public.sources', 'insert,update,delete,truncate')
|
||||
or has_table_privilege(current_user, 'public.claim_evidence', 'insert,update,delete,truncate')
|
||||
or has_table_privilege(current_user, 'public.claim_edges', 'insert,update,delete,truncate')
|
||||
or has_table_privilege(current_user, 'kb_stage.kb_proposals', 'insert,update,delete,truncate')
|
||||
) as required_writes_denied
|
||||
""",
|
||||
(self.settings.authorization_role,),
|
||||
)
|
||||
session = _row_dict(cursor, cursor.fetchone())
|
||||
if session is None:
|
||||
raise ReaderContractError("database session contract returned no row")
|
||||
if session["database"] != self.settings.database:
|
||||
raise ReaderContractError("database session selected an unexpected database")
|
||||
if session["database_principal"] != self.settings.iam_database_user:
|
||||
raise ReaderContractError("database session selected an unexpected principal")
|
||||
if session["transaction_read_only"] != "on":
|
||||
raise ReaderContractError("database session is not read-only")
|
||||
if not session["has_authorization_role"] or not session["has_required_reads"]:
|
||||
raise ReaderContractError("database principal lacks the exact read authorization")
|
||||
if not session["required_writes_denied"]:
|
||||
raise ReaderContractError("database principal has an unexpected write privilege")
|
||||
return session
|
||||
|
||||
@staticmethod
|
||||
def _load_claim(cursor: Any, claim_id: str | None) -> dict[str, Any] | None:
|
||||
columns = """
|
||||
select c.id::text as id,
|
||||
c.type::text as type,
|
||||
left(c.text, 12000) as text,
|
||||
c.status::text as status,
|
||||
c.confidence,
|
||||
coalesce(c.tags, '{}'::text[]) as tags,
|
||||
c.superseded_by::text as superseded_by,
|
||||
c.created_at,
|
||||
c.updated_at
|
||||
from public.claims c
|
||||
"""
|
||||
if claim_id is not None:
|
||||
cursor.execute(columns + " where c.id = %s::uuid", (claim_id,))
|
||||
return _row_dict(cursor, cursor.fetchone())
|
||||
|
||||
cursor.execute(
|
||||
columns
|
||||
+ """
|
||||
where exists (
|
||||
select 1 from public.claim_evidence ce where ce.claim_id = c.id
|
||||
)
|
||||
order by coalesce(c.updated_at, c.created_at) desc, c.id desc
|
||||
limit 1
|
||||
"""
|
||||
)
|
||||
claim = _row_dict(cursor, cursor.fetchone())
|
||||
if claim is not None:
|
||||
return claim
|
||||
cursor.execute(columns + " order by coalesce(c.updated_at, c.created_at) desc, c.id desc limit 1")
|
||||
return _row_dict(cursor, cursor.fetchone())
|
||||
|
||||
@staticmethod
|
||||
def _load_evidence(cursor: Any, claim_id: str) -> list[dict[str, Any]]:
|
||||
cursor.execute(
|
||||
"""
|
||||
select ce.id::text as evidence_id,
|
||||
ce.role::text as role,
|
||||
ce.weight,
|
||||
ce.created_at as linked_at,
|
||||
s.id::text as source_id,
|
||||
s.source_type::text as source_type,
|
||||
s.url,
|
||||
s.storage_path,
|
||||
left(coalesce(s.excerpt, ''), 2400) as excerpt,
|
||||
s.hash as content_hash,
|
||||
s.captured_at,
|
||||
s.created_at as source_created_at
|
||||
from public.claim_evidence ce
|
||||
join public.sources s on s.id = ce.source_id
|
||||
where ce.claim_id = %s::uuid
|
||||
order by ce.weight desc nulls last, ce.created_at, ce.id
|
||||
limit 30
|
||||
""",
|
||||
(claim_id,),
|
||||
)
|
||||
return _rows(cursor)
|
||||
|
||||
@staticmethod
|
||||
def _load_edges(cursor: Any, claim_id: str) -> list[dict[str, Any]]:
|
||||
cursor.execute(
|
||||
"""
|
||||
select e.id::text as edge_id,
|
||||
case when e.from_claim = %s::uuid then 'outgoing' else 'incoming' end as direction,
|
||||
e.edge_type::text as edge_type,
|
||||
e.weight,
|
||||
case when e.from_claim = %s::uuid then e.to_claim::text else e.from_claim::text end
|
||||
as connected_claim_id,
|
||||
e.created_at
|
||||
from public.claim_edges e
|
||||
where e.from_claim = %s::uuid or e.to_claim = %s::uuid
|
||||
order by e.created_at, e.id
|
||||
limit 40
|
||||
""",
|
||||
(claim_id, claim_id, claim_id, claim_id),
|
||||
)
|
||||
return _rows(cursor)
|
||||
|
||||
@staticmethod
|
||||
def _load_proposal_counts(cursor: Any) -> dict[str, int]:
|
||||
cursor.execute(
|
||||
"select status::text as status, count(*)::int as count "
|
||||
"from kb_stage.kb_proposals group by status order by status"
|
||||
)
|
||||
return {str(status): int(count) for status, count in cursor.fetchall()}
|
||||
|
||||
@staticmethod
|
||||
def _load_related_proposals(cursor: Any, claim_id: str) -> list[dict[str, Any]]:
|
||||
cursor.execute(
|
||||
"""
|
||||
select p.id::text as id,
|
||||
p.proposal_type::text as proposal_type,
|
||||
p.status::text as status,
|
||||
to_jsonb(p)->>'source_ref' as source_ref,
|
||||
p.reviewed_at,
|
||||
p.applied_at,
|
||||
p.updated_at
|
||||
from kb_stage.kb_proposals p
|
||||
where p.payload::text like %s
|
||||
order by p.updated_at desc, p.id desc
|
||||
limit 20
|
||||
""",
|
||||
(f"%{claim_id}%",),
|
||||
)
|
||||
return _rows(cursor)
|
||||
|
||||
@staticmethod
|
||||
def _load_recent_proposals(cursor: Any) -> list[dict[str, Any]]:
|
||||
cursor.execute(
|
||||
"""
|
||||
select p.id::text as id,
|
||||
p.proposal_type::text as proposal_type,
|
||||
p.status::text as status,
|
||||
to_jsonb(p)->>'source_ref' as source_ref,
|
||||
p.reviewed_at,
|
||||
p.applied_at,
|
||||
p.updated_at
|
||||
from kb_stage.kb_proposals p
|
||||
order by p.updated_at desc, p.id desc
|
||||
limit 10
|
||||
"""
|
||||
)
|
||||
return _rows(cursor)
|
||||
183
ops/observatory_read_role.sql
Normal file
183
ops/observatory_read_role.sql
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
\set ON_ERROR_STOP on
|
||||
\getenv observatory_iam_user OBSERVATORY_DB_IAM_USER
|
||||
|
||||
-- Provision only after Cloud SQL has created the dedicated IAM database user.
|
||||
-- The service-account username omits the .gserviceaccount.com suffix.
|
||||
select set_config('observatory.iam_user', :'observatory_iam_user', false);
|
||||
|
||||
do $preflight$
|
||||
declare
|
||||
principal text := current_setting('observatory.iam_user');
|
||||
begin
|
||||
if current_database() <> 'teleo_canonical' then
|
||||
raise exception 'observatory_read_role must run only in teleo_canonical';
|
||||
end if;
|
||||
if principal !~ '^[a-z0-9-]+@teleo-501523[.]iam$' then
|
||||
raise exception 'OBSERVATORY_DB_IAM_USER is not the scoped GCP service-account database user';
|
||||
end if;
|
||||
if not exists (select 1 from pg_catalog.pg_roles where rolname = principal) then
|
||||
raise exception 'Cloud SQL IAM database user does not exist: %', principal;
|
||||
end if;
|
||||
end
|
||||
$preflight$;
|
||||
|
||||
select 'create role kb_observatory_read nologin nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit -1'
|
||||
where not exists (select 1 from pg_catalog.pg_roles where rolname = 'kb_observatory_read')
|
||||
\gexec
|
||||
|
||||
alter role kb_observatory_read
|
||||
with nologin nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit -1
|
||||
password null;
|
||||
alter role kb_observatory_read reset all;
|
||||
|
||||
revoke all privileges on database teleo_canonical from kb_observatory_read;
|
||||
grant connect on database teleo_canonical to kb_observatory_read;
|
||||
|
||||
revoke all on schema public, kb_stage from kb_observatory_read;
|
||||
revoke all privileges on all tables in schema public, kb_stage from kb_observatory_read;
|
||||
revoke all privileges on all sequences in schema public, kb_stage from kb_observatory_read;
|
||||
revoke all privileges on all functions in schema public, kb_stage from kb_observatory_read;
|
||||
revoke all privileges on all procedures in schema public, kb_stage from kb_observatory_read;
|
||||
|
||||
select format(
|
||||
'revoke all privileges (%s) on table %I.%I from kb_observatory_read',
|
||||
pg_catalog.string_agg(pg_catalog.quote_ident(attribute.attname), ', ' order by attribute.attnum),
|
||||
namespace.nspname,
|
||||
relation.relname
|
||||
)
|
||||
from pg_catalog.pg_class relation
|
||||
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
|
||||
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
|
||||
where namespace.nspname in ('public', 'kb_stage')
|
||||
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
|
||||
and attribute.attnum > 0
|
||||
and not attribute.attisdropped
|
||||
group by namespace.nspname, relation.relname
|
||||
\gexec
|
||||
|
||||
grant usage on schema public, kb_stage to kb_observatory_read;
|
||||
grant select on
|
||||
public.claims,
|
||||
public.sources,
|
||||
public.claim_evidence,
|
||||
public.claim_edges,
|
||||
kb_stage.kb_proposals
|
||||
to kb_observatory_read;
|
||||
|
||||
do $membership$
|
||||
declare
|
||||
principal text := current_setting('observatory.iam_user');
|
||||
unexpected text;
|
||||
begin
|
||||
select pg_catalog.string_agg(granted.rolname, ', ' order by granted.rolname)
|
||||
into unexpected
|
||||
from pg_catalog.pg_auth_members membership
|
||||
join pg_catalog.pg_roles granted on granted.oid = membership.roleid
|
||||
join pg_catalog.pg_roles member on member.oid = membership.member
|
||||
where member.rolname = principal
|
||||
and granted.rolname <> 'kb_observatory_read';
|
||||
if unexpected is not null then
|
||||
raise exception 'dedicated Observatory principal has unexpected role memberships: %', unexpected;
|
||||
end if;
|
||||
|
||||
select pg_catalog.string_agg(member.rolname, ', ' order by member.rolname)
|
||||
into unexpected
|
||||
from pg_catalog.pg_auth_members membership
|
||||
join pg_catalog.pg_roles granted on granted.oid = membership.roleid
|
||||
join pg_catalog.pg_roles member on member.oid = membership.member
|
||||
where granted.rolname = 'kb_observatory_read'
|
||||
and member.rolname <> principal;
|
||||
if unexpected is not null then
|
||||
raise exception 'kb_observatory_read has unexpected members: %', unexpected;
|
||||
end if;
|
||||
|
||||
execute pg_catalog.format('grant kb_observatory_read to %I', principal);
|
||||
execute pg_catalog.format(
|
||||
'alter role %I in database teleo_canonical set default_transaction_read_only = on',
|
||||
principal
|
||||
);
|
||||
execute pg_catalog.format(
|
||||
'alter role %I in database teleo_canonical set statement_timeout = %L',
|
||||
principal,
|
||||
'5s'
|
||||
);
|
||||
execute pg_catalog.format(
|
||||
'alter role %I in database teleo_canonical set lock_timeout = %L',
|
||||
principal,
|
||||
'1s'
|
||||
);
|
||||
end
|
||||
$membership$;
|
||||
|
||||
do $verify$
|
||||
declare
|
||||
principal text := current_setting('observatory.iam_user');
|
||||
unexpected text;
|
||||
begin
|
||||
if not pg_catalog.pg_has_role(principal, 'kb_observatory_read', 'member') then
|
||||
raise exception 'Observatory principal is not a member of kb_observatory_read';
|
||||
end if;
|
||||
if not exists (
|
||||
select 1 from pg_catalog.pg_roles
|
||||
where rolname = principal and rolinherit and not rolsuper and not rolcreatedb
|
||||
and not rolcreaterole and not rolreplication and not rolbypassrls
|
||||
) then
|
||||
raise exception 'Observatory principal has unsafe role attributes or cannot inherit read grants';
|
||||
end if;
|
||||
|
||||
select pg_catalog.string_agg(
|
||||
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
|
||||
', ' order by namespace.nspname, relation.relname, privilege.name
|
||||
)
|
||||
into unexpected
|
||||
from pg_catalog.pg_class relation
|
||||
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
|
||||
cross join (values ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')) privilege(name)
|
||||
where namespace.nspname in ('public', 'kb_stage')
|
||||
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
|
||||
and pg_catalog.has_table_privilege(principal, relation.oid, privilege.name);
|
||||
if unexpected is not null then
|
||||
raise exception 'Observatory principal has effective table writes: %', unexpected;
|
||||
end if;
|
||||
|
||||
select pg_catalog.string_agg(
|
||||
pg_catalog.format('%I.%I', namespace.nspname, relation.relname),
|
||||
', ' order by namespace.nspname, relation.relname
|
||||
)
|
||||
into unexpected
|
||||
from pg_catalog.pg_class relation
|
||||
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
|
||||
where namespace.nspname in ('public', 'kb_stage')
|
||||
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
|
||||
and pg_catalog.has_table_privilege(principal, relation.oid, 'SELECT')
|
||||
and (namespace.nspname, relation.relname) not in (
|
||||
('public', 'claims'),
|
||||
('public', 'sources'),
|
||||
('public', 'claim_evidence'),
|
||||
('public', 'claim_edges'),
|
||||
('kb_stage', 'kb_proposals')
|
||||
);
|
||||
if unexpected is not null then
|
||||
raise exception 'Observatory principal can SELECT outside its allowlist: %', unexpected;
|
||||
end if;
|
||||
|
||||
if not (
|
||||
pg_catalog.has_table_privilege(principal, 'public.claims', 'SELECT')
|
||||
and pg_catalog.has_table_privilege(principal, 'public.sources', 'SELECT')
|
||||
and pg_catalog.has_table_privilege(principal, 'public.claim_evidence', 'SELECT')
|
||||
and pg_catalog.has_table_privilege(principal, 'public.claim_edges', 'SELECT')
|
||||
and pg_catalog.has_table_privilege(principal, 'kb_stage.kb_proposals', 'SELECT')
|
||||
) then
|
||||
raise exception 'Observatory principal is missing a required read grant';
|
||||
end if;
|
||||
end
|
||||
$verify$;
|
||||
|
||||
select jsonb_build_object(
|
||||
'database', current_database(),
|
||||
'authorization_role', 'kb_observatory_read',
|
||||
'database_principal', current_setting('observatory.iam_user'),
|
||||
'required_reads', true,
|
||||
'effective_table_writes_denied', true,
|
||||
'default_transaction_read_only', true
|
||||
)::text;
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Deterministically rebuild a disposable Postgres from genesis plus a strict ledger.
|
||||
|
||||
The v1 replay boundary is intentionally narrow. It supports strict, insert-only
|
||||
``add_edge``, ``attach_evidence``, and ``approve_claim`` receipts. Each ledger
|
||||
entry must also retain the exact approved and applied proposal rows plus the
|
||||
immutable approval snapshot. Legacy/freeform entries and ``revise_strategy``
|
||||
fail before Docker starts because their retained material is not a complete
|
||||
database delta.
|
||||
The v1 replay boundary supports strict ``add_edge``, ``attach_evidence``,
|
||||
``approve_claim``, and ``revise_strategy`` receipts. Insert-only actions seed
|
||||
their receipt-pinned rows before the guarded transition. ``revise_strategy``
|
||||
instead captures the exact prestate identities, executes the exact hash-matched
|
||||
guarded SQL, validates the generated delta, and normalizes only that delta to the
|
||||
receipt-pinned transaction timestamp, IDs, and rows. Every entry must retain the
|
||||
full approved and applied proposal rows plus its immutable approval snapshot.
|
||||
Legacy and freeform entries still fail before Docker starts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -46,15 +48,18 @@ LEDGER_ARTIFACT = "teleo_genesis_plus_ledger"
|
|||
MATERIAL_ARTIFACT = "teleo_strict_proposal_replay_material"
|
||||
LEDGER_CONTRACT_VERSION = 1
|
||||
MATERIAL_CONTRACT_VERSION = 1
|
||||
SUPPORTED_PROPOSAL_TYPES = frozenset({"add_edge", "attach_evidence", "approve_claim"})
|
||||
DEFAULT_REBUILD_IMAGE = (
|
||||
"docker.io/library/postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777"
|
||||
)
|
||||
SUPPORTED_PROPOSAL_TYPES = frozenset({"add_edge", "attach_evidence", "approve_claim", "revise_strategy"})
|
||||
CLAIM_CEILING = (
|
||||
"exact genesis plus ordered strict insert-only proposal replay; supported types are "
|
||||
"add_edge, attach_evidence, and approve_claim; full approved/applied proposal rows "
|
||||
"and immutable approval snapshots are required"
|
||||
"exact genesis plus ordered strict proposal replay; supported types are add_edge, "
|
||||
"attach_evidence, approve_claim, and revise_strategy; mutating strategy replay "
|
||||
"captures prestate identities and normalizes the exact receipt-pinned poststate; "
|
||||
"full approved/applied proposal rows and immutable approval snapshots are required"
|
||||
)
|
||||
NOT_PROVEN = (
|
||||
"legacy or freeform proposal replay",
|
||||
"revise_strategy replay because prior-row mutations are absent from v1 receipts",
|
||||
"source-corpus recompilation from a blank schema",
|
||||
"VPS, GCP, Telegram, or any live database mutation",
|
||||
)
|
||||
|
|
@ -77,6 +82,7 @@ FIXED_ENGINE_PATHS = frozenset(
|
|||
)
|
||||
|
||||
SHA256_RE = re.compile(r"[0-9a-f]{64}\Z")
|
||||
IMAGE_DIGEST_RE = re.compile(r"\S+@sha256:[0-9a-f]{64}\Z")
|
||||
PROPOSAL_TRANSITION_FIELDS = frozenset(
|
||||
{"status", "applied_by_handle", "applied_by_agent_id", "applied_at", "updated_at"}
|
||||
)
|
||||
|
|
@ -121,6 +127,35 @@ CANONICAL_TABLE_ORDER = (
|
|||
"claim_evidence",
|
||||
"claim_edges",
|
||||
)
|
||||
STRATEGY_REQUIRED_FIELDS = frozenset(
|
||||
{
|
||||
"id",
|
||||
"agent_id",
|
||||
"diagnosis",
|
||||
"guiding_policy",
|
||||
"proximate_objectives",
|
||||
"version",
|
||||
"active",
|
||||
"created_at",
|
||||
}
|
||||
)
|
||||
STRATEGY_NODE_REQUIRED_FIELDS = frozenset(
|
||||
{
|
||||
"id",
|
||||
"agent_id",
|
||||
"node_type",
|
||||
"title",
|
||||
"body",
|
||||
"rank",
|
||||
"status",
|
||||
"horizon",
|
||||
"measure",
|
||||
"source_ref",
|
||||
"metadata",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ReconstructionError(RuntimeError):
|
||||
|
|
@ -308,6 +343,116 @@ def _validate_proposal_rows(
|
|||
)
|
||||
|
||||
|
||||
def _parse_aware_timestamp(value: Any, field: str, *, code: str = "invalid_mutating_receipt") -> datetime:
|
||||
if not isinstance(value, str) or not value:
|
||||
raise ReconstructionError(code, f"{field} must be an ISO-8601 timestamp")
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
except ValueError as exc:
|
||||
raise ReconstructionError(code, f"{field} must be an ISO-8601 timestamp") from exc
|
||||
if parsed.tzinfo is None:
|
||||
raise ReconstructionError(code, f"{field} must include a timezone")
|
||||
return parsed
|
||||
|
||||
|
||||
def _validated_uuid_list(value: Any, field: str, *, code: str = "invalid_mutating_state") -> list[str]:
|
||||
if not isinstance(value, list):
|
||||
raise ReconstructionError(code, f"{field} must be a UUID list")
|
||||
normalized: list[str] = []
|
||||
for item in value:
|
||||
try:
|
||||
normalized.append(str(uuid.UUID(str(item))))
|
||||
except (TypeError, ValueError, AttributeError) as exc:
|
||||
raise ReconstructionError(code, f"{field} must contain only UUIDs") from exc
|
||||
if len(normalized) != len(set(normalized)):
|
||||
raise ReconstructionError(code, f"{field} contains duplicate UUIDs")
|
||||
return normalized
|
||||
|
||||
|
||||
def _validate_revise_strategy_receipt_rows(
|
||||
proposal: dict[str, Any], canonical_rows: dict[str, Any]
|
||||
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
||||
payload = proposal.get("payload")
|
||||
apply_payload = payload.get("apply_payload") if isinstance(payload, dict) else None
|
||||
if not isinstance(apply_payload, dict):
|
||||
raise ReconstructionError("invalid_mutating_receipt", "revise_strategy receipt requires a strict apply payload")
|
||||
try:
|
||||
agent_id = str(uuid.UUID(str(apply_payload.get("agent_id"))))
|
||||
except (TypeError, ValueError, AttributeError) as exc:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt requires one agent UUID"
|
||||
) from exc
|
||||
|
||||
strategies = canonical_rows.get("strategies")
|
||||
nodes = canonical_rows.get("strategy_nodes")
|
||||
if not isinstance(strategies, list) or len(strategies) != 1 or not isinstance(strategies[0], dict):
|
||||
raise ReconstructionError("invalid_mutating_receipt", "revise_strategy receipt must pin one fresh strategy row")
|
||||
expected_nodes = apply_payload.get("strategy_nodes")
|
||||
if (
|
||||
not isinstance(expected_nodes, list)
|
||||
or not isinstance(nodes, list)
|
||||
or len(nodes) != len(expected_nodes)
|
||||
or any(not isinstance(row, dict) for row in nodes)
|
||||
):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt must pin every fresh strategy node row"
|
||||
)
|
||||
|
||||
strategy = strategies[0]
|
||||
if frozenset(strategy) != STRATEGY_REQUIRED_FIELDS:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt strategy row fields are not canonical"
|
||||
)
|
||||
if any(frozenset(row) != STRATEGY_NODE_REQUIRED_FIELDS for row in nodes):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt strategy node row fields are not canonical"
|
||||
)
|
||||
strategy_id = _validated_uuid_list([strategy.get("id")], "receipt strategy id", code="invalid_mutating_receipt")[0]
|
||||
node_ids = _validated_uuid_list(
|
||||
[row.get("id") for row in nodes],
|
||||
"receipt strategy node ids",
|
||||
code="invalid_mutating_receipt",
|
||||
)
|
||||
if strategy.get("agent_id") != agent_id or any(row.get("agent_id") != agent_id for row in nodes):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt rows do not belong to the payload agent"
|
||||
)
|
||||
if strategy.get("active") is not True or any(row.get("status") != "active" for row in nodes):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt must pin the fresh active poststate"
|
||||
)
|
||||
version = strategy.get("version")
|
||||
if isinstance(version, bool) or not isinstance(version, int) or version < 1:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy receipt strategy version must be positive"
|
||||
)
|
||||
|
||||
transaction_timestamp = strategy.get("created_at")
|
||||
transaction_time = _parse_aware_timestamp(transaction_timestamp, "receipt strategy created_at")
|
||||
for row in nodes:
|
||||
if row.get("created_at") != transaction_timestamp or row.get("updated_at") != transaction_timestamp:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt",
|
||||
"revise_strategy fresh strategy and node timestamps must share one transaction timestamp",
|
||||
)
|
||||
reviewed_time = _parse_aware_timestamp(proposal.get("reviewed_at"), "receipt proposal reviewed_at")
|
||||
applied_time = _parse_aware_timestamp(proposal.get("applied_at"), "receipt proposal applied_at")
|
||||
if reviewed_time > applied_time:
|
||||
raise ReconstructionError("invalid_mutating_receipt", "revise_strategy proposal approval is after apply time")
|
||||
if transaction_time < reviewed_time:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy transaction timestamp is before proposal approval"
|
||||
)
|
||||
if transaction_time > applied_time:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_receipt", "revise_strategy transaction timestamp is after proposal apply time"
|
||||
)
|
||||
|
||||
return {**strategy, "id": strategy_id}, [
|
||||
{**row, "id": normalized_id} for row, normalized_id in zip(nodes, node_ids, strict=True)
|
||||
]
|
||||
|
||||
|
||||
def _validate_entry_material(
|
||||
material: dict[str, Any],
|
||||
*,
|
||||
|
|
@ -340,11 +485,6 @@ def _validate_entry_material(
|
|||
if not isinstance(proposal, dict):
|
||||
raise ReconstructionError("invalid_material", "replay receipt has no proposal object")
|
||||
proposal_type = proposal.get("proposal_type")
|
||||
if proposal_type == "revise_strategy":
|
||||
raise ReconstructionError(
|
||||
"unsupported_mutating_receipt",
|
||||
"revise_strategy receipts omit prior strategy and node mutations and cannot replay exactly",
|
||||
)
|
||||
if proposal_type not in SUPPORTED_PROPOSAL_TYPES:
|
||||
raise ReconstructionError(
|
||||
"unsupported_proposal_type", "ledger entry proposal type is not supported by v1 reconstruction"
|
||||
|
|
@ -358,6 +498,8 @@ def _validate_entry_material(
|
|||
canonical_rows = receipt.get("canonical_rows")
|
||||
if not isinstance(apply_metadata, dict) or not isinstance(canonical_rows, dict):
|
||||
raise ReconstructionError("invalid_replay_receipt", "replay receipt is missing apply or row material")
|
||||
if proposal_type == "revise_strategy":
|
||||
_validate_revise_strategy_receipt_rows(proposal, canonical_rows)
|
||||
try:
|
||||
rebuilt = replay_receipt.build_replay_receipt(
|
||||
proposal,
|
||||
|
|
@ -382,6 +524,7 @@ def _validate_entry_material(
|
|||
raise ReconstructionError(
|
||||
"replay_material_hash_mismatch", "ledger replay-material pin does not match the private receipt"
|
||||
)
|
||||
receipt = {**receipt, "canonical_rows": rebuilt["canonical_rows"]}
|
||||
|
||||
approved = material["approved_proposal"]
|
||||
approval = material["approval_snapshot"]
|
||||
|
|
@ -394,6 +537,15 @@ def _validate_entry_material(
|
|||
"current_apply_engine_rejected_material",
|
||||
"current guarded apply engine rejected the strict replay material",
|
||||
) from exc
|
||||
current_apply_sql_sha256 = _sha256_text(current_apply_sql)
|
||||
if proposal_type == "revise_strategy" and (
|
||||
apply_metadata.get("source") != "exact_executed_sql"
|
||||
or apply_metadata.get("apply_sql_sha256") != current_apply_sql_sha256
|
||||
):
|
||||
raise ReconstructionError(
|
||||
"mutating_apply_engine_mismatch",
|
||||
"revise_strategy replay requires the exact executed SQL to match the current guarded apply engine",
|
||||
)
|
||||
|
||||
return LedgerEntry(
|
||||
sequence=expected_sequence,
|
||||
|
|
@ -405,7 +557,7 @@ def _validate_entry_material(
|
|||
applied_proposal=applied,
|
||||
receipt=receipt,
|
||||
current_apply_sql=current_apply_sql,
|
||||
current_apply_sql_sha256=_sha256_text(current_apply_sql),
|
||||
current_apply_sql_sha256=current_apply_sql_sha256,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -550,6 +702,266 @@ def build_seed_sql(entry: LedgerEntry) -> str:
|
|||
return "\n".join(statements) + "\n"
|
||||
|
||||
|
||||
def _uuid_membership_sql(column: str, values: list[str], *, negate: bool = False) -> str:
|
||||
if not values:
|
||||
return "true" if negate else "false"
|
||||
rendered = ", ".join(f"{apply_engine.sql_literal(value)}::uuid" for value in values)
|
||||
operator = "not in" if negate else "in"
|
||||
return f"{column} {operator} ({rendered})"
|
||||
|
||||
|
||||
def _uuid_array_sql(values: list[str]) -> str:
|
||||
if not values:
|
||||
return "array[]::uuid[]"
|
||||
rendered = ", ".join(f"{apply_engine.sql_literal(value)}::uuid" for value in values)
|
||||
return f"array[{rendered}]::uuid[]"
|
||||
|
||||
|
||||
def build_revise_strategy_prestate_sql(entry: LedgerEntry) -> str:
|
||||
apply_payload = entry.receipt["proposal"]["payload"]["apply_payload"]
|
||||
agent = apply_engine.sql_literal(apply_payload["agent_id"])
|
||||
return f"""with strategy_state as (
|
||||
select coalesce(jsonb_agg(s.id::text order by s.id::text), '[]'::jsonb) as strategy_ids,
|
||||
coalesce(
|
||||
jsonb_agg(s.id::text order by s.id::text) filter (where s.active),
|
||||
'[]'::jsonb
|
||||
) as active_strategy_ids,
|
||||
coalesce(max(s.version), 0) as max_strategy_version
|
||||
from public.strategies s
|
||||
where s.agent_id = {agent}::uuid
|
||||
), node_state as (
|
||||
select coalesce(jsonb_agg(n.id::text order by n.id::text), '[]'::jsonb) as strategy_node_ids,
|
||||
coalesce(
|
||||
jsonb_agg(n.id::text order by n.id::text) filter (where n.status <> 'retired'),
|
||||
'[]'::jsonb
|
||||
) as non_retired_strategy_node_ids
|
||||
from public.strategy_nodes n
|
||||
where n.agent_id = {agent}::uuid
|
||||
)
|
||||
select jsonb_build_object(
|
||||
'strategy_ids', s.strategy_ids,
|
||||
'active_strategy_ids', s.active_strategy_ids,
|
||||
'max_strategy_version', s.max_strategy_version,
|
||||
'strategy_node_ids', n.strategy_node_ids,
|
||||
'non_retired_strategy_node_ids', n.non_retired_strategy_node_ids
|
||||
)::text
|
||||
from strategy_state s cross join node_state n;
|
||||
"""
|
||||
|
||||
|
||||
def _validate_revise_strategy_prestate(value: Any) -> dict[str, Any]:
|
||||
expected = frozenset(
|
||||
{
|
||||
"strategy_ids",
|
||||
"active_strategy_ids",
|
||||
"max_strategy_version",
|
||||
"strategy_node_ids",
|
||||
"non_retired_strategy_node_ids",
|
||||
}
|
||||
)
|
||||
state = _require_exact_keys(value, expected, "revise_strategy prestate")
|
||||
strategy_ids = _validated_uuid_list(state["strategy_ids"], "prestate strategy ids")
|
||||
active_strategy_ids = _validated_uuid_list(state["active_strategy_ids"], "prestate active strategy ids")
|
||||
strategy_node_ids = _validated_uuid_list(state["strategy_node_ids"], "prestate strategy node ids")
|
||||
non_retired_node_ids = _validated_uuid_list(
|
||||
state["non_retired_strategy_node_ids"], "prestate non-retired strategy node ids"
|
||||
)
|
||||
max_version = state["max_strategy_version"]
|
||||
if isinstance(max_version, bool) or not isinstance(max_version, int) or max_version < 0:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_state", "prestate maximum strategy version must be a non-negative integer"
|
||||
)
|
||||
if len(active_strategy_ids) > 1 or not set(active_strategy_ids).issubset(strategy_ids):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_state", "prestate active strategies violate the one-active invariant"
|
||||
)
|
||||
if not set(non_retired_node_ids).issubset(strategy_node_ids):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_state", "prestate non-retired strategy nodes are not a subset of all nodes"
|
||||
)
|
||||
return {
|
||||
"strategy_ids": strategy_ids,
|
||||
"active_strategy_ids": active_strategy_ids,
|
||||
"max_strategy_version": max_version,
|
||||
"strategy_node_ids": strategy_node_ids,
|
||||
"non_retired_strategy_node_ids": non_retired_node_ids,
|
||||
}
|
||||
|
||||
|
||||
def build_revise_strategy_generated_delta_sql(entry: LedgerEntry, prestate: dict[str, Any]) -> str:
|
||||
state = _validate_revise_strategy_prestate(prestate)
|
||||
apply_payload = entry.receipt["proposal"]["payload"]["apply_payload"]
|
||||
agent = apply_engine.sql_literal(apply_payload["agent_id"])
|
||||
strategy_filter = _uuid_membership_sql("s.id", state["strategy_ids"], negate=True)
|
||||
node_filter = _uuid_membership_sql("n.id", state["strategy_node_ids"], negate=True)
|
||||
return f"""select jsonb_build_object(
|
||||
'strategies', coalesce((
|
||||
select jsonb_agg(to_jsonb(s) order by s.id::text)
|
||||
from public.strategies s
|
||||
where s.agent_id = {agent}::uuid and {strategy_filter}
|
||||
), '[]'::jsonb),
|
||||
'strategy_nodes', coalesce((
|
||||
select jsonb_agg(to_jsonb(n) order by n.id::text)
|
||||
from public.strategy_nodes n
|
||||
where n.agent_id = {agent}::uuid and {node_filter}
|
||||
), '[]'::jsonb)
|
||||
)::text;
|
||||
"""
|
||||
|
||||
|
||||
def _validate_revise_strategy_generated_delta(
|
||||
entry: LedgerEntry,
|
||||
prestate: dict[str, Any],
|
||||
generated_rows: Any,
|
||||
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
||||
state = _validate_revise_strategy_prestate(prestate)
|
||||
if not isinstance(generated_rows, dict):
|
||||
raise ReconstructionError("invalid_mutating_delta", "revise_strategy generated delta must be a row object")
|
||||
try:
|
||||
replay_receipt.build_replay_receipt(
|
||||
entry.receipt["proposal"],
|
||||
generated_rows,
|
||||
apply_sql_sha256=entry.current_apply_sql_sha256,
|
||||
apply_sql_source="exact_executed_sql",
|
||||
exported_at_utc=entry.receipt.get("exported_at_utc"),
|
||||
)
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_delta",
|
||||
"revise_strategy generated rows do not match the strict payload",
|
||||
) from exc
|
||||
|
||||
strategies = generated_rows.get("strategies")
|
||||
nodes = generated_rows.get("strategy_nodes")
|
||||
if not isinstance(strategies, list) or len(strategies) != 1 or not isinstance(strategies[0], dict):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_delta", "revise_strategy apply did not generate exactly one strategy"
|
||||
)
|
||||
if not isinstance(nodes, list) or any(not isinstance(row, dict) for row in nodes):
|
||||
raise ReconstructionError("invalid_mutating_delta", "revise_strategy apply generated invalid strategy nodes")
|
||||
strategy = strategies[0]
|
||||
if strategy.get("version") != state["max_strategy_version"] + 1:
|
||||
raise ReconstructionError("invalid_mutating_delta", "revise_strategy generated an unexpected strategy version")
|
||||
transaction_timestamp = strategy.get("created_at")
|
||||
_parse_aware_timestamp(transaction_timestamp, "generated strategy created_at", code="invalid_mutating_delta")
|
||||
if any(
|
||||
row.get("created_at") != transaction_timestamp or row.get("updated_at") != transaction_timestamp
|
||||
for row in nodes
|
||||
):
|
||||
raise ReconstructionError(
|
||||
"invalid_mutating_delta",
|
||||
"revise_strategy generated rows do not share one transaction timestamp",
|
||||
)
|
||||
_validated_uuid_list([strategy.get("id")], "generated strategy id", code="invalid_mutating_delta")
|
||||
_validated_uuid_list(
|
||||
[row.get("id") for row in nodes],
|
||||
"generated strategy node ids",
|
||||
code="invalid_mutating_delta",
|
||||
)
|
||||
return strategy, nodes
|
||||
|
||||
|
||||
def build_revise_strategy_normalization_sql(
|
||||
entry: LedgerEntry,
|
||||
prestate: dict[str, Any],
|
||||
generated_rows: dict[str, Any],
|
||||
) -> str:
|
||||
state = _validate_revise_strategy_prestate(prestate)
|
||||
generated_strategy, generated_nodes = _validate_revise_strategy_generated_delta(entry, state, generated_rows)
|
||||
receipt_strategy, receipt_nodes = _validate_revise_strategy_receipt_rows(
|
||||
entry.receipt["proposal"], entry.receipt["canonical_rows"]
|
||||
)
|
||||
if receipt_strategy["version"] != state["max_strategy_version"] + 1:
|
||||
raise ReconstructionError(
|
||||
"mutating_receipt_version_mismatch",
|
||||
"revise_strategy receipt version does not follow the observed prestate",
|
||||
)
|
||||
if receipt_strategy["id"] in state["strategy_ids"] or set(row["id"] for row in receipt_nodes).intersection(
|
||||
state["strategy_node_ids"]
|
||||
):
|
||||
raise ReconstructionError(
|
||||
"mutating_receipt_id_collision", "revise_strategy receipt IDs collide with the observed prestate"
|
||||
)
|
||||
|
||||
apply_payload = entry.receipt["proposal"]["payload"]["apply_payload"]
|
||||
agent = apply_engine.sql_literal(apply_payload["agent_id"])
|
||||
generated_strategy_id = str(uuid.UUID(str(generated_strategy["id"])))
|
||||
generated_node_ids = [str(uuid.UUID(str(row["id"]))) for row in generated_nodes]
|
||||
previous_active = state["active_strategy_ids"]
|
||||
changed_node_ids = state["non_retired_strategy_node_ids"]
|
||||
transaction_timestamp = apply_engine.sql_literal(receipt_strategy["created_at"])
|
||||
generated_node_array = _uuid_array_sql(generated_node_ids)
|
||||
previous_active_array = _uuid_array_sql(previous_active)
|
||||
changed_node_array = _uuid_array_sql(changed_node_ids)
|
||||
|
||||
prior_strategy_assertion = ""
|
||||
if previous_active:
|
||||
prior_strategy_assertion = f"""
|
||||
if (select count(*) from public.strategies
|
||||
where agent_id = {agent}::uuid and id = any(previous_active_ids) and not active) <> {len(previous_active)} then
|
||||
raise exception 'revise_strategy prior active strategy transition mismatch';
|
||||
end if;"""
|
||||
prior_node_normalization = ""
|
||||
if changed_node_ids:
|
||||
prior_node_normalization = f"""
|
||||
if (select count(*) from public.strategy_nodes
|
||||
where agent_id = {agent}::uuid and id = any(changed_node_ids) and status = 'retired') <> {len(changed_node_ids)} then
|
||||
raise exception 'revise_strategy prior node transition mismatch';
|
||||
end if;
|
||||
update public.strategy_nodes
|
||||
set status = 'retired', updated_at = {transaction_timestamp}::timestamptz
|
||||
where agent_id = {agent}::uuid and id = any(changed_node_ids);
|
||||
get diagnostics changed_rows = row_count;
|
||||
if changed_rows <> {len(changed_node_ids)} then
|
||||
raise exception 'revise_strategy prior node normalization mismatch';
|
||||
end if;"""
|
||||
|
||||
return f"""begin;
|
||||
set local standard_conforming_strings = on;
|
||||
do $reconstruction$
|
||||
declare
|
||||
changed_rows integer;
|
||||
generated_strategy_id constant uuid := {apply_engine.sql_literal(generated_strategy_id)}::uuid;
|
||||
generated_node_ids constant uuid[] := {generated_node_array};
|
||||
previous_active_ids constant uuid[] := {previous_active_array};
|
||||
changed_node_ids constant uuid[] := {changed_node_array};
|
||||
begin{prior_strategy_assertion}
|
||||
if exists (
|
||||
select 1 from public.strategy_node_anchors
|
||||
where from_node_id = any(generated_node_ids) or to_node_id = any(generated_node_ids)
|
||||
) then
|
||||
raise exception 'revise_strategy generated nodes unexpectedly have anchors';
|
||||
end if;
|
||||
delete from public.strategy_nodes
|
||||
where agent_id = {agent}::uuid and id = any(generated_node_ids);
|
||||
get diagnostics changed_rows = row_count;
|
||||
if changed_rows <> {len(generated_node_ids)} then
|
||||
raise exception 'revise_strategy generated node delta mismatch';
|
||||
end if;
|
||||
delete from public.strategies
|
||||
where agent_id = {agent}::uuid and id = generated_strategy_id;
|
||||
get diagnostics changed_rows = row_count;
|
||||
if changed_rows <> 1 then
|
||||
raise exception 'revise_strategy generated strategy delta mismatch';
|
||||
end if;{prior_node_normalization}
|
||||
insert into public.strategies
|
||||
select seeded.* from jsonb_populate_record(
|
||||
null::public.strategies, {_jsonb_literal(receipt_strategy)}
|
||||
) seeded;
|
||||
insert into public.strategy_nodes
|
||||
select seeded.* from jsonb_populate_recordset(
|
||||
null::public.strategy_nodes, {_jsonb_literal(receipt_nodes)}
|
||||
) seeded;
|
||||
if (select count(*) from public.strategies
|
||||
where agent_id = {agent}::uuid and active) <> 1 then
|
||||
raise exception 'revise_strategy normalized one-active invariant mismatch';
|
||||
end if;
|
||||
end
|
||||
$reconstruction$;
|
||||
commit;
|
||||
"""
|
||||
|
||||
|
||||
def build_proposal_readback_sql(proposal_id: str) -> str:
|
||||
literal = apply_engine.sql_literal(proposal_id)
|
||||
return f"""select jsonb_build_object(
|
||||
|
|
@ -694,7 +1106,12 @@ def _entry_summary(entry: LedgerEntry) -> dict[str, Any]:
|
|||
"current_apply_sql_sha256": entry.current_apply_sql_sha256,
|
||||
"expected_row_counts": entry.receipt["expected_row_counts"],
|
||||
"seed_exact": False,
|
||||
"proposal_seed_exact": False,
|
||||
"canonical_seed_exact": False,
|
||||
"guarded_apply_executed": False,
|
||||
"mutating_prestate_captured": False,
|
||||
"mutating_delta_validated": False,
|
||||
"mutating_poststate_normalized": False,
|
||||
"proposal_exact": False,
|
||||
"canonical_rows_exact": False,
|
||||
"status": "pending",
|
||||
|
|
@ -707,6 +1124,7 @@ def apply_entry(
|
|||
entry: LedgerEntry,
|
||||
summary: dict[str, Any],
|
||||
) -> None:
|
||||
mutating_prestate: dict[str, Any] | None = None
|
||||
try:
|
||||
_psql(
|
||||
args,
|
||||
|
|
@ -733,20 +1151,34 @@ def apply_entry(
|
|||
code="entry_seed_mismatch",
|
||||
action=f"ledger entry {entry.sequence} proposal seed",
|
||||
)
|
||||
seeded_rows = _read_json(
|
||||
args,
|
||||
container,
|
||||
replay_receipt.build_postflight_sql(entry.receipt["proposal"]),
|
||||
code="entry_seed_readback_failed",
|
||||
action=f"ledger entry {entry.sequence} canonical seed readback",
|
||||
)
|
||||
_assert_exact_json(
|
||||
seeded_rows,
|
||||
entry.receipt["canonical_rows"],
|
||||
code="entry_seed_mismatch",
|
||||
action=f"ledger entry {entry.sequence} canonical seed",
|
||||
)
|
||||
summary["seed_exact"] = True
|
||||
summary["proposal_seed_exact"] = True
|
||||
if entry.applied_proposal["proposal_type"] == "revise_strategy":
|
||||
mutating_prestate = _validate_revise_strategy_prestate(
|
||||
_read_json(
|
||||
args,
|
||||
container,
|
||||
build_revise_strategy_prestate_sql(entry),
|
||||
code="mutating_prestate_readback_failed",
|
||||
action=f"ledger entry {entry.sequence} revise_strategy prestate readback",
|
||||
)
|
||||
)
|
||||
summary["mutating_prestate_captured"] = True
|
||||
else:
|
||||
seeded_rows = _read_json(
|
||||
args,
|
||||
container,
|
||||
replay_receipt.build_postflight_sql(entry.receipt["proposal"]),
|
||||
code="entry_seed_readback_failed",
|
||||
action=f"ledger entry {entry.sequence} canonical seed readback",
|
||||
)
|
||||
_assert_exact_json(
|
||||
seeded_rows,
|
||||
entry.receipt["canonical_rows"],
|
||||
code="entry_seed_mismatch",
|
||||
action=f"ledger entry {entry.sequence} canonical seed",
|
||||
)
|
||||
summary["canonical_seed_exact"] = True
|
||||
summary["seed_exact"] = summary["proposal_seed_exact"] and summary["canonical_seed_exact"]
|
||||
|
||||
_psql(
|
||||
args,
|
||||
|
|
@ -759,6 +1191,27 @@ def apply_entry(
|
|||
)
|
||||
summary["guarded_apply_executed"] = True
|
||||
|
||||
if mutating_prestate is not None:
|
||||
generated_rows = _read_json(
|
||||
args,
|
||||
container,
|
||||
build_revise_strategy_generated_delta_sql(entry, mutating_prestate),
|
||||
code="mutating_delta_readback_failed",
|
||||
action=f"ledger entry {entry.sequence} revise_strategy generated delta readback",
|
||||
)
|
||||
normalization_sql = build_revise_strategy_normalization_sql(entry, mutating_prestate, generated_rows)
|
||||
summary["mutating_delta_validated"] = True
|
||||
_psql(
|
||||
args,
|
||||
container,
|
||||
normalization_sql,
|
||||
role="postgres",
|
||||
timeout=args.apply_timeout,
|
||||
code="mutating_poststate_normalization_failed",
|
||||
action=f"ledger entry {entry.sequence} revise_strategy exact poststate normalization",
|
||||
)
|
||||
summary["mutating_poststate_normalized"] = True
|
||||
|
||||
_psql(
|
||||
args,
|
||||
container,
|
||||
|
|
@ -1199,7 +1652,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||
parser.add_argument("--ledger", required=True, type=Path)
|
||||
parser.add_argument("--ledger-sha256", required=True)
|
||||
parser.add_argument("--database", default="teleo")
|
||||
parser.add_argument("--image", default=canonical_rebuild.DEFAULT_IMAGE)
|
||||
parser.add_argument("--image", default=DEFAULT_REBUILD_IMAGE)
|
||||
parser.add_argument("--container-prefix", default="teleo-genesis-ledger-rebuild")
|
||||
parser.add_argument("--tmpfs-mb", default=1024, type=int)
|
||||
parser.add_argument("--startup-timeout", default=60.0, type=float)
|
||||
|
|
@ -1218,8 +1671,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||
parser.error("--database must be a safe PostgreSQL identifier up to 63 characters")
|
||||
if not canonical_rebuild.CONTAINER_PREFIX_RE.fullmatch(args.container_prefix):
|
||||
parser.error("--container-prefix must be 1-40 Docker-safe characters")
|
||||
if not args.image or any(character.isspace() for character in args.image):
|
||||
parser.error("--image must be a non-empty Docker image reference without whitespace")
|
||||
if not IMAGE_DIGEST_RE.fullmatch(args.image):
|
||||
parser.error("--image must be pinned by a lowercase sha256 digest")
|
||||
if not args.docker_bin or any(character.isspace() for character in args.docker_bin):
|
||||
parser.error("--docker-bin must be one executable name or path without whitespace")
|
||||
if not 64 <= args.tmpfs_mb <= 65536:
|
||||
|
|
|
|||
|
|
@ -18,9 +18,12 @@ dev = [
|
|||
"pytest-asyncio>=0.23",
|
||||
"ruff>=0.3",
|
||||
]
|
||||
observatory = [
|
||||
"cloud-sql-python-connector[pg8000]>=1.20.4,<1.21",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["lib"]
|
||||
packages = ["lib", "observatory_read_adapter"]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py311"
|
||||
|
|
|
|||
|
|
@ -315,8 +315,9 @@ alter table kb_stage.kb_review_principals owner to kb_gate_owner;
|
|||
alter table kb_stage.kb_proposal_approvals owner to kb_gate_owner;
|
||||
|
||||
insert into kb_stage.kb_review_principals
|
||||
(db_role, reviewed_by_handle, reviewed_by_agent_id, active)
|
||||
select 'kb_review'::name, a.handle, a.id, true
|
||||
(db_role, reviewed_by_handle, reviewed_by_agent_id, active, created_at)
|
||||
select 'kb_review'::name, a.handle, a.id, true,
|
||||
'1970-01-01 00:00:00+00'::timestamptz
|
||||
from public.agents a
|
||||
where a.handle = 'm3ta'
|
||||
on conflict (db_role) do nothing;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ DEFAULT_MODEL = "anthropic/claude-sonnet-4.5"
|
|||
EXTRACTOR_VERSION = "2"
|
||||
DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
||||
DEFAULT_KEY_FILE = Path("/opt/teleo-eval/secrets/openrouter-key")
|
||||
TEXT_SUFFIXES = {".csv", ".htm", ".html", ".json", ".md", ".rst", ".txt", ".xml"}
|
||||
TEXT_SUFFIXES = {".csv", ".htm", ".html", ".json", ".jsonl", ".md", ".rst", ".txt", ".xml"}
|
||||
MAX_SOURCE_CHARS = 120_000
|
||||
MAX_CANDIDATES = 20
|
||||
MAX_CLAIMS = 3
|
||||
|
|
@ -124,20 +124,37 @@ def _load_context(path: Path) -> dict[str, Any]:
|
|||
return {"database_search_query": query.strip(), "candidate_claims": normalized}
|
||||
|
||||
|
||||
def build_source_fragments(text: str) -> dict[str, str]:
|
||||
"""Label non-empty source lines so the model selects text instead of copying it."""
|
||||
def build_source_fragment_index(text: str) -> tuple[dict[str, str], dict[str, dict[str, Any]]]:
|
||||
"""Return selectable lines plus exact line/character provenance."""
|
||||
fragments: dict[str, str] = {}
|
||||
for line in text.splitlines():
|
||||
if line.strip():
|
||||
fragments[f"S{len(fragments) + 1:05d}"] = line
|
||||
locations: dict[str, dict[str, Any]] = {}
|
||||
offset = 0
|
||||
for line_number, raw_line in enumerate(text.splitlines(keepends=True), start=1):
|
||||
line = raw_line.rstrip("\r\n")
|
||||
quote = line.strip()
|
||||
if quote:
|
||||
reference = f"S{len(fragments) + 1:05d}"
|
||||
start = offset + line.find(quote)
|
||||
fragments[reference] = quote
|
||||
locations[reference] = {
|
||||
"reference": reference,
|
||||
"line": line_number,
|
||||
"char_start": start,
|
||||
"char_end": start + len(quote),
|
||||
"quote_sha256": sha256_bytes(quote.encode("utf-8")),
|
||||
}
|
||||
offset += len(raw_line)
|
||||
if not fragments:
|
||||
raise PreparationError("extracted text has no selectable source lines")
|
||||
return fragments
|
||||
return fragments, locations
|
||||
|
||||
|
||||
def build_prompt(
|
||||
fragments: dict[str, str], context: dict[str, Any], repair_error: str | None = None
|
||||
) -> str:
|
||||
def build_source_fragments(text: str) -> dict[str, str]:
|
||||
"""Label non-empty source lines so the model selects text instead of copying it."""
|
||||
return build_source_fragment_index(text)[0]
|
||||
|
||||
|
||||
def build_prompt(fragments: dict[str, str], context: dict[str, Any], repair_error: str | None = None) -> str:
|
||||
candidates = json.dumps(context["candidate_claims"], indent=2, ensure_ascii=True)
|
||||
source = "\n".join(f"[{reference}] {line}" for reference, line in fragments.items())
|
||||
repair = ""
|
||||
|
|
@ -273,9 +290,7 @@ def parse_model_output(content: str) -> dict[str, Any]:
|
|||
if not isinstance(claim, dict):
|
||||
raise PreparationError(f"model extraction claims[{index}] must be an object")
|
||||
if set(claim) != MODEL_CLAIM_KEYS:
|
||||
raise PreparationError(
|
||||
f"model extraction claims[{index}] keys must equal {sorted(MODEL_CLAIM_KEYS)}"
|
||||
)
|
||||
raise PreparationError(f"model extraction claims[{index}] keys must equal {sorted(MODEL_CLAIM_KEYS)}")
|
||||
confidence = claim.get("confidence")
|
||||
if confidence is not None and (
|
||||
isinstance(confidence, bool) or not isinstance(confidence, (int, float)) or not 0 <= confidence <= 0.75
|
||||
|
|
@ -304,27 +319,47 @@ def _resolve_reference(reference: Any, fragments: dict[str, str], label: str) ->
|
|||
return fragments[reference]
|
||||
|
||||
|
||||
def resolve_model_output(selection: dict[str, Any], fragments: dict[str, str]) -> dict[str, Any]:
|
||||
def resolve_model_output(
|
||||
selection: dict[str, Any],
|
||||
fragments: dict[str, str],
|
||||
fragment_locations: dict[str, dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
def selected(kind: str, reference: str, **identity: Any) -> dict[str, Any]:
|
||||
result = {"kind": kind, "reference": reference, **identity}
|
||||
if fragment_locations is not None:
|
||||
result.update(fragment_locations[reference])
|
||||
return result
|
||||
|
||||
claims: list[dict[str, Any]] = []
|
||||
selected_references: list[dict[str, str]] = []
|
||||
selected_references: list[dict[str, Any]] = []
|
||||
for claim_index, claim in enumerate(selection["claims"]):
|
||||
quote_ref = claim["quote_ref"]
|
||||
quote = _resolve_reference(quote_ref, fragments, f"claims[{claim_index}].quote_ref")
|
||||
selected_references.append({"kind": "claim", "reference": quote_ref})
|
||||
selected_references.append(selected("claim", quote_ref, claim_key=claim["claim_key"], quote=quote))
|
||||
evidence: list[dict[str, str]] = []
|
||||
for evidence_index, row in enumerate(claim["evidence"]):
|
||||
evidence_ref = row["quote_ref"]
|
||||
evidence_quote = _resolve_reference(
|
||||
evidence_ref,
|
||||
fragments,
|
||||
f"claims[{claim_index}].evidence[{evidence_index}].quote_ref",
|
||||
)
|
||||
evidence.append(
|
||||
{
|
||||
"quote": _resolve_reference(
|
||||
evidence_ref,
|
||||
fragments,
|
||||
f"claims[{claim_index}].evidence[{evidence_index}].quote_ref",
|
||||
),
|
||||
"quote": evidence_quote,
|
||||
"role": row["role"],
|
||||
}
|
||||
)
|
||||
selected_references.append({"kind": "evidence", "reference": evidence_ref})
|
||||
selected_references.append(
|
||||
selected(
|
||||
"evidence",
|
||||
evidence_ref,
|
||||
claim_key=claim["claim_key"],
|
||||
evidence_index=evidence_index,
|
||||
evidence_role=row["role"],
|
||||
quote=evidence_quote,
|
||||
)
|
||||
)
|
||||
claims.append(
|
||||
{
|
||||
"claim_key": claim["claim_key"],
|
||||
|
|
@ -351,7 +386,14 @@ def resolve_model_output(selection: dict[str, Any], fragments: dict[str, str]) -
|
|||
"reason": duplicate["reason"],
|
||||
}
|
||||
)
|
||||
selected_references.append({"kind": "duplicate", "reference": source_quote_ref})
|
||||
selected_references.append(
|
||||
selected(
|
||||
"duplicate",
|
||||
source_quote_ref,
|
||||
claim_id=duplicate["claim_id"],
|
||||
quote=duplicates[-1]["source_quote"],
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"schema": RESOLVED_OUTPUT_SCHEMA,
|
||||
|
|
@ -362,6 +404,37 @@ def resolve_model_output(selection: dict[str, Any], fragments: dict[str, str]) -
|
|||
}
|
||||
|
||||
|
||||
def validate_bundle_source_locations(bundle: dict[str, Any], selected_references: list[dict[str, Any]]) -> None:
|
||||
"""Ensure compiler quote offsets match the exact selected fragment occurrence."""
|
||||
available = list(bundle.get("quote_bindings") or [])
|
||||
for selected in selected_references:
|
||||
if selected.get("kind") not in {"claim", "evidence"}:
|
||||
continue
|
||||
match_index = next(
|
||||
(
|
||||
index
|
||||
for index, binding in enumerate(available)
|
||||
if binding.get("kind") == selected.get("kind")
|
||||
and binding.get("claim_key") == selected.get("claim_key")
|
||||
and binding.get("quote") == selected.get("quote")
|
||||
and (
|
||||
selected.get("kind") != "evidence" or binding.get("evidence_role") == selected.get("evidence_role")
|
||||
)
|
||||
),
|
||||
None,
|
||||
)
|
||||
if match_index is None:
|
||||
raise PreparationError("compiler dropped a selected claim/evidence source binding")
|
||||
binding = available.pop(match_index)
|
||||
if binding.get("first_start") != selected.get("char_start") or binding.get("first_end") != selected.get(
|
||||
"char_end"
|
||||
):
|
||||
raise PreparationError(
|
||||
f"selected {selected['reference']} is an ambiguous repeated quote; "
|
||||
"the current compiler cannot preserve that exact occurrence"
|
||||
)
|
||||
|
||||
|
||||
def build_manifest(
|
||||
*,
|
||||
args: argparse.Namespace,
|
||||
|
|
@ -427,7 +500,7 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
|||
artifact_bytes = _read_nonempty(args.artifact, "artifact")
|
||||
text_bytes = extract_utf8_text(args.artifact, args.text)
|
||||
text = text_bytes.decode("utf-8", errors="strict")
|
||||
fragments = build_source_fragments(text)
|
||||
fragments, fragment_locations = build_source_fragment_index(text)
|
||||
context = _load_context(args.kb_context)
|
||||
|
||||
requested_output_dir = args.output_dir.expanduser()
|
||||
|
|
@ -464,7 +537,7 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
|||
attempts.append({"attempt": attempt, "response_sha256": sha256_bytes(raw.encode("utf-8")), "usage": usage})
|
||||
try:
|
||||
selection = parse_model_output(raw)
|
||||
extraction = resolve_model_output(selection, fragments)
|
||||
extraction = resolve_model_output(selection, fragments, fragment_locations)
|
||||
compiler._validate_dedupe(
|
||||
{
|
||||
"database_search_query": context["database_search_query"],
|
||||
|
|
@ -494,6 +567,7 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
|||
with os.fdopen(fd, "wb") as handle:
|
||||
handle.write(_json_bytes(manifest))
|
||||
bundle = compiler.compile_source_packet(args.artifact, temp_text, temp_manifest)
|
||||
validate_bundle_source_locations(bundle, extraction["selected_source_references"])
|
||||
finally:
|
||||
if temp_text.exists():
|
||||
temp_text.unlink()
|
||||
|
|
@ -518,6 +592,7 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
|||
raise PreparationError("prepared extraction has no manifest")
|
||||
_write_private(manifest_path, _json_bytes(manifest))
|
||||
bundle = compiler.compile_source_packet(args.artifact, text_path, manifest_path)
|
||||
validate_bundle_source_locations(bundle, extraction["selected_source_references"])
|
||||
|
||||
receipt = {
|
||||
"schema": RECEIPT_SCHEMA,
|
||||
|
|
@ -545,10 +620,20 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
|||
"model": args.model,
|
||||
"attempts": attempts,
|
||||
"claim_count": len(extraction["claims"]),
|
||||
"evidence_count": sum(len(claim["evidence"]) for claim in extraction["claims"]),
|
||||
"duplicate_judgment_count": len(extraction["duplicates"]),
|
||||
"selectable_source_line_count": len(fragments),
|
||||
"selected_source_references": extraction["selected_source_references"],
|
||||
"notes": extraction["extraction_notes"],
|
||||
},
|
||||
"replay": {
|
||||
"artifact_sha256": artifact_sha256,
|
||||
"extracted_text_sha256": text_sha256,
|
||||
"kb_context_sha256": sha256_bytes(_json_bytes(context)),
|
||||
"fragment_index_sha256": sha256_bytes(_json_bytes(fragment_locations)),
|
||||
"extractor": {"name": f"openrouter:{args.model}", "version": EXTRACTOR_VERSION},
|
||||
"exact_selected_locations_validated": bundle is not None,
|
||||
},
|
||||
"outputs": {
|
||||
"output_dir": str(output_dir),
|
||||
"extracted_text": str(text_path),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -57,6 +57,12 @@ def prepare_normalized_child(parent: dict[str, Any]) -> dict[str, Any]:
|
|||
manifest = apply_payload.setdefault("normalization_manifest", {})
|
||||
if not isinstance(manifest, dict):
|
||||
raise bound.CheckpointError("normalized child normalization_manifest must be an object")
|
||||
parent_payload = parent.get("payload")
|
||||
ingestion_manifest = parent_payload.get("ingestion_manifest") if isinstance(parent_payload, dict) else None
|
||||
if ingestion_manifest is not None:
|
||||
if not isinstance(ingestion_manifest, dict):
|
||||
raise bound.CheckpointError("parent payload.ingestion_manifest must be an object")
|
||||
manifest["ingestion_manifest"] = json.loads(json.dumps(ingestion_manifest, sort_keys=True))
|
||||
parent_packet_sha256 = canonical_sha256(parent)
|
||||
manifest["parent_packet_sha256"] = parent_packet_sha256
|
||||
child_payload_sha256 = canonical_sha256(payload)
|
||||
|
|
|
|||
|
|
@ -203,6 +203,15 @@ def _lines(completed: subprocess.CompletedProcess[str]) -> set[str]:
|
|||
return {line for line in completed.stdout.splitlines() if line}
|
||||
|
||||
|
||||
def test_reviewer_principal_seed_timestamp_is_deterministic(migrated_postgres: str) -> None:
|
||||
created_at = _psql(
|
||||
migrated_postgres,
|
||||
"select created_at::text from kb_stage.kb_review_principals where db_role = 'kb_review';",
|
||||
).stdout.strip()
|
||||
|
||||
assert created_at == "1970-01-01 00:00:00+00"
|
||||
|
||||
|
||||
def _catalog_snapshot(container: str) -> str:
|
||||
return _psql(
|
||||
container,
|
||||
|
|
|
|||
346
tests/test_observatory_read_adapter.py
Normal file
346
tests/test_observatory_read_adapter.py
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from aiohttp.test_utils import TestClient, TestServer
|
||||
|
||||
from observatory_read_adapter.app import create_app
|
||||
from observatory_read_adapter.config import EXPECTED_CONNECTION_NAME, Settings
|
||||
from observatory_read_adapter.db import CloudSqlClaimReader
|
||||
|
||||
CLAIM_ID = "d0000000-0000-0000-0000-000000000005"
|
||||
API_KEY = "observatory-test-api-key-000000000000000000000000"
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def settings(**overrides: object) -> Settings:
|
||||
values = {
|
||||
"project_id": "teleo-501523",
|
||||
"instance_connection_name": EXPECTED_CONNECTION_NAME,
|
||||
"database": "teleo_canonical",
|
||||
"iam_database_user": "sa-observatory-read-adapter@teleo-501523.iam",
|
||||
"authorization_role": "kb_observatory_read",
|
||||
"api_key": API_KEY,
|
||||
"service_revision": "test-revision",
|
||||
}
|
||||
values.update(overrides)
|
||||
return Settings(**values)
|
||||
|
||||
|
||||
class FakeReader:
|
||||
def __init__(self) -> None:
|
||||
self.closed = False
|
||||
|
||||
def check_ready(self) -> dict[str, object]:
|
||||
return {
|
||||
"ready": True,
|
||||
"database": "teleo_canonical",
|
||||
"authorization_role": "kb_observatory_read",
|
||||
"transaction_read_only": True,
|
||||
}
|
||||
|
||||
def read_claim(self, claim_id: str | None = None) -> dict[str, object] | None:
|
||||
if claim_id not in (None, CLAIM_ID):
|
||||
return None
|
||||
return {
|
||||
"schema": "livingip.observatory-canonical-claim.v1",
|
||||
"read_only": True,
|
||||
"provenance": {
|
||||
"database": "teleo_canonical",
|
||||
"database_principal": "sa-observatory-read-adapter@teleo-501523.iam",
|
||||
"authorization_role": "kb_observatory_read",
|
||||
"transaction_read_only": True,
|
||||
"write_privileges_denied": True,
|
||||
},
|
||||
"canonical": {
|
||||
"claim": {"id": CLAIM_ID, "status": "open", "text": "Canonical claim"},
|
||||
"evidence": [{"source_id": "e0000000-0000-0000-0000-000000000006"}],
|
||||
},
|
||||
"proposal_ledger": {
|
||||
"distinct_from_canonical": True,
|
||||
"status_counts": {"pending_review": 2, "approved": 1, "applied": 1},
|
||||
"related": [{"status": "approved", "applied_at": None}],
|
||||
},
|
||||
}
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class FakeCursor:
|
||||
def __init__(self) -> None:
|
||||
self.description: list[tuple[str]] = []
|
||||
self.result: list[tuple[object, ...]] = []
|
||||
self.executed: list[tuple[str, tuple[object, ...]]] = []
|
||||
|
||||
def execute(self, sql: str, parameters: tuple[object, ...] = ()) -> None:
|
||||
normalized = " ".join(sql.split()).lower()
|
||||
self.executed.append((normalized, parameters))
|
||||
self.description = []
|
||||
self.result = []
|
||||
if "current_database() as database" in normalized:
|
||||
self.description = [
|
||||
("database",),
|
||||
("database_principal",),
|
||||
("transaction_read_only",),
|
||||
("has_authorization_role",),
|
||||
("has_required_reads",),
|
||||
("required_writes_denied",),
|
||||
]
|
||||
self.result = [
|
||||
(
|
||||
"teleo_canonical",
|
||||
"sa-observatory-read-adapter@teleo-501523.iam",
|
||||
"on",
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
)
|
||||
]
|
||||
elif "from public.claims c" in normalized:
|
||||
self.description = [
|
||||
("id",),
|
||||
("type",),
|
||||
("text",),
|
||||
("status",),
|
||||
("confidence",),
|
||||
("tags",),
|
||||
("superseded_by",),
|
||||
("created_at",),
|
||||
("updated_at",),
|
||||
]
|
||||
self.result = [
|
||||
(
|
||||
CLAIM_ID,
|
||||
"strategy",
|
||||
"Canonical claim",
|
||||
"open",
|
||||
0.8,
|
||||
["test"],
|
||||
None,
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
)
|
||||
]
|
||||
elif "from public.claim_evidence ce" in normalized:
|
||||
self.description = [
|
||||
("evidence_id",),
|
||||
("role",),
|
||||
("weight",),
|
||||
("linked_at",),
|
||||
("source_id",),
|
||||
("source_type",),
|
||||
("url",),
|
||||
("storage_path",),
|
||||
("excerpt",),
|
||||
("content_hash",),
|
||||
("captured_at",),
|
||||
("source_created_at",),
|
||||
]
|
||||
self.result = [
|
||||
(
|
||||
"e0000000-0000-0000-0000-000000000006",
|
||||
"grounds",
|
||||
1.0,
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
"f0000000-0000-0000-0000-000000000007",
|
||||
"document",
|
||||
"https://example.test/source",
|
||||
None,
|
||||
"Source excerpt",
|
||||
"sha256:test",
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
)
|
||||
]
|
||||
elif "from public.claim_edges e" in normalized:
|
||||
self.description = [
|
||||
("edge_id",),
|
||||
("direction",),
|
||||
("edge_type",),
|
||||
("weight",),
|
||||
("connected_claim_id",),
|
||||
("created_at",),
|
||||
]
|
||||
self.result = []
|
||||
elif "group by status" in normalized:
|
||||
self.description = [("status",), ("count",)]
|
||||
self.result = [("approved", 1), ("applied", 1), ("pending_review", 2)]
|
||||
elif "where p.payload::text like" in normalized:
|
||||
self.description = [
|
||||
("id",),
|
||||
("proposal_type",),
|
||||
("status",),
|
||||
("source_ref",),
|
||||
("reviewed_at",),
|
||||
("applied_at",),
|
||||
("updated_at",),
|
||||
]
|
||||
self.result = [
|
||||
(
|
||||
"a0000000-0000-0000-0000-000000000001",
|
||||
"revise_claim",
|
||||
"approved",
|
||||
"source:test",
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
None,
|
||||
"2026-07-15T00:00:00+00:00",
|
||||
)
|
||||
]
|
||||
elif "from kb_stage.kb_proposals p" in normalized:
|
||||
self.description = [
|
||||
("id",),
|
||||
("proposal_type",),
|
||||
("status",),
|
||||
("source_ref",),
|
||||
("reviewed_at",),
|
||||
("applied_at",),
|
||||
("updated_at",),
|
||||
]
|
||||
self.result = []
|
||||
|
||||
def fetchone(self) -> tuple[object, ...] | None:
|
||||
return self.result.pop(0) if self.result else None
|
||||
|
||||
def fetchall(self) -> list[tuple[object, ...]]:
|
||||
result, self.result = self.result, []
|
||||
return result
|
||||
|
||||
|
||||
class FakeConnection:
|
||||
def __init__(self) -> None:
|
||||
self.cursor_value = FakeCursor()
|
||||
self.rolled_back = False
|
||||
self.closed = False
|
||||
|
||||
def cursor(self) -> FakeCursor:
|
||||
return self.cursor_value
|
||||
|
||||
def rollback(self) -> None:
|
||||
self.rolled_back = True
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
def test_settings_fail_closed_on_wrong_database_role_or_short_key() -> None:
|
||||
for overrides in (
|
||||
{"database": "teleo"},
|
||||
{"authorization_role": "leoclean_kb_runtime"},
|
||||
{"api_key": "too-short"},
|
||||
{"instance_connection_name": "other:region:instance"},
|
||||
):
|
||||
candidate = settings(**overrides)
|
||||
try:
|
||||
candidate.validate()
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError(f"unsafe settings accepted: {overrides}")
|
||||
|
||||
|
||||
def test_authenticated_claim_response_and_negative_http_paths() -> None:
|
||||
async def exercise() -> None:
|
||||
reader = FakeReader()
|
||||
client = TestClient(TestServer(create_app(settings(), reader=reader)))
|
||||
await client.start_server()
|
||||
try:
|
||||
anonymous = await client.get("/v1/claims/sample")
|
||||
assert anonymous.status == 401
|
||||
assert await anonymous.json() == {"error": "unauthorized"}
|
||||
|
||||
response = await client.get(
|
||||
f"/v1/claims/{CLAIM_ID}",
|
||||
headers={"X-Api-Key": API_KEY},
|
||||
)
|
||||
assert response.status == 200
|
||||
assert response.headers["Cache-Control"] == "private, no-store, max-age=0"
|
||||
payload = await response.json()
|
||||
assert payload["schema"] == "livingip.observatory-canonical-claim.v1"
|
||||
assert payload["provenance"]["database"] == "teleo_canonical"
|
||||
assert payload["provenance"]["write_privileges_denied"] is True
|
||||
assert payload["canonical"]["evidence"]
|
||||
assert payload["proposal_ledger"]["distinct_from_canonical"] is True
|
||||
assert payload["proposal_ledger"]["related"][0]["applied_at"] is None
|
||||
assert API_KEY not in json.dumps(payload)
|
||||
|
||||
write_attempt = await client.post(
|
||||
f"/v1/claims/{CLAIM_ID}",
|
||||
headers={"X-Api-Key": API_KEY},
|
||||
json={"status": "applied"},
|
||||
)
|
||||
assert write_attempt.status == 405
|
||||
|
||||
invalid = await client.get(
|
||||
"/v1/claims/not-a-uuid",
|
||||
headers={"X-Api-Key": API_KEY},
|
||||
)
|
||||
assert invalid.status == 400
|
||||
finally:
|
||||
await client.close()
|
||||
assert reader.closed is True
|
||||
|
||||
asyncio.run(exercise())
|
||||
|
||||
|
||||
def test_readiness_checks_database_contract_without_exposing_claims() -> None:
|
||||
async def exercise() -> None:
|
||||
client = TestClient(TestServer(create_app(settings(), reader=FakeReader())))
|
||||
await client.start_server()
|
||||
try:
|
||||
response = await client.get("/readyz")
|
||||
payload = await response.json()
|
||||
assert response.status == 200
|
||||
assert payload == {"status": "ready"}
|
||||
finally:
|
||||
await client.close()
|
||||
|
||||
asyncio.run(exercise())
|
||||
|
||||
|
||||
def test_database_reader_returns_provenance_and_keeps_proposals_distinct() -> None:
|
||||
connection = FakeConnection()
|
||||
reader = CloudSqlClaimReader(settings(), connection_factory=lambda: connection)
|
||||
|
||||
payload = reader.read_claim(CLAIM_ID)
|
||||
|
||||
assert payload is not None
|
||||
assert payload["provenance"]["database"] == "teleo_canonical"
|
||||
assert payload["provenance"]["database_principal"] == settings().iam_database_user
|
||||
assert payload["provenance"]["write_privileges_denied"] is True
|
||||
assert payload["canonical"]["claim"]["id"] == CLAIM_ID
|
||||
assert payload["canonical"]["evidence"][0]["content_hash"] == "sha256:test"
|
||||
assert payload["proposal_ledger"]["distinct_from_canonical"] is True
|
||||
assert payload["proposal_ledger"]["related"][0]["status"] == "approved"
|
||||
assert payload["proposal_ledger"]["related"][0]["applied_at"] is None
|
||||
assert connection.rolled_back is True
|
||||
assert connection.closed is True
|
||||
assert any("set transaction read only" in sql for sql, _parameters in connection.cursor_value.executed)
|
||||
|
||||
|
||||
def test_database_role_contract_is_select_only_and_iam_scoped() -> None:
|
||||
sql = (ROOT / "ops" / "observatory_read_role.sql").read_text(encoding="utf-8")
|
||||
lowered = sql.lower()
|
||||
|
||||
assert "kb_observatory_read nologin" in lowered
|
||||
assert "default_transaction_read_only = on" in lowered
|
||||
assert "public.claims" in lowered
|
||||
assert "public.sources" in lowered
|
||||
assert "public.claim_evidence" in lowered
|
||||
assert "public.claim_edges" in lowered
|
||||
assert "kb_stage.kb_proposals" in lowered
|
||||
assert "grant select on" in lowered
|
||||
assert "grant insert" not in lowered
|
||||
assert "grant update" not in lowered
|
||||
assert "grant delete" not in lowered
|
||||
assert "effective_table_writes_denied" in lowered
|
||||
|
||||
|
||||
def test_container_runs_as_non_root_and_contains_no_password_contract() -> None:
|
||||
dockerfile = (ROOT / "Dockerfile.observatory-read-adapter").read_text(encoding="utf-8")
|
||||
|
||||
assert "USER 10001:10001" in dockerfile
|
||||
assert "PGPASSWORD" not in dockerfile
|
||||
assert "DB_PASS" not in dockerfile
|
||||
182
tests/test_observatory_read_role_postgres.py
Normal file
182
tests/test_observatory_read_role_postgres.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
ROLE_SQL = ROOT / "ops" / "observatory_read_role.sql"
|
||||
POSTGRES_IMAGE = "postgres:16-alpine"
|
||||
DATABASE = "teleo_canonical"
|
||||
IAM_USER = "sa-observatory-read-adapter@teleo-501523.iam"
|
||||
TEST_PASSWORD = "generated-test-only-password"
|
||||
|
||||
BOOTSTRAP_SQL = f"""
|
||||
begin;
|
||||
create schema kb_stage;
|
||||
create role "{IAM_USER}" login inherit password '{TEST_PASSWORD}';
|
||||
create table public.claims (id uuid primary key);
|
||||
create table public.sources (id uuid primary key);
|
||||
create table public.claim_evidence (id uuid primary key);
|
||||
create table public.claim_edges (id uuid primary key);
|
||||
create table public.private_notes (id uuid primary key);
|
||||
create table kb_stage.kb_proposals (id uuid primary key);
|
||||
insert into public.claims values ('11111111-1111-1111-1111-111111111111');
|
||||
commit;
|
||||
"""
|
||||
|
||||
|
||||
def run(command: list[str], *, input_text: str | None = None, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
command,
|
||||
input=input_text,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=check,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("docker") is None, reason="Docker is required")
|
||||
def test_observatory_role_is_read_only_and_exactly_allowlisted() -> None:
|
||||
container = f"teleo-observatory-role-{uuid.uuid4().hex[:10]}"
|
||||
run(
|
||||
[
|
||||
"docker",
|
||||
"run",
|
||||
"--detach",
|
||||
"--rm",
|
||||
"--name",
|
||||
container,
|
||||
"--env",
|
||||
f"POSTGRES_PASSWORD={TEST_PASSWORD}",
|
||||
"--env",
|
||||
f"POSTGRES_DB={DATABASE}",
|
||||
POSTGRES_IMAGE,
|
||||
]
|
||||
)
|
||||
try:
|
||||
deadline = time.monotonic() + 25
|
||||
while time.monotonic() < deadline:
|
||||
ready = run(
|
||||
["docker", "exec", container, "pg_isready", "-U", "postgres", "-d", DATABASE],
|
||||
check=False,
|
||||
)
|
||||
if ready.returncode == 0:
|
||||
break
|
||||
time.sleep(0.25)
|
||||
else:
|
||||
raise AssertionError("ephemeral Postgres did not become ready")
|
||||
|
||||
bootstrap: subprocess.CompletedProcess[str] | None = None
|
||||
deadline = time.monotonic() + 15
|
||||
while time.monotonic() < deadline:
|
||||
bootstrap = run(
|
||||
[
|
||||
"docker",
|
||||
"exec",
|
||||
"-i",
|
||||
container,
|
||||
"psql",
|
||||
"--set",
|
||||
"ON_ERROR_STOP=1",
|
||||
"-U",
|
||||
"postgres",
|
||||
"-d",
|
||||
DATABASE,
|
||||
],
|
||||
input_text=BOOTSTRAP_SQL,
|
||||
check=False,
|
||||
)
|
||||
if bootstrap.returncode == 0:
|
||||
break
|
||||
time.sleep(0.25)
|
||||
else:
|
||||
assert bootstrap is not None
|
||||
raise AssertionError(f"ephemeral Postgres bootstrap failed: {bootstrap.stderr[-1000:]}")
|
||||
migration = run(
|
||||
[
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
f"OBSERVATORY_DB_IAM_USER={IAM_USER}",
|
||||
"-i",
|
||||
container,
|
||||
"psql",
|
||||
"-U",
|
||||
"postgres",
|
||||
"-d",
|
||||
DATABASE,
|
||||
],
|
||||
input_text=ROLE_SQL.read_text(encoding="utf-8"),
|
||||
)
|
||||
assert '"effective_table_writes_denied": true' in migration.stdout
|
||||
|
||||
readback = run(
|
||||
[
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
f"PGPASSWORD={TEST_PASSWORD}",
|
||||
container,
|
||||
"psql",
|
||||
"-h",
|
||||
"127.0.0.1",
|
||||
"-U",
|
||||
IAM_USER,
|
||||
"-d",
|
||||
DATABASE,
|
||||
"-At",
|
||||
"-c",
|
||||
"show default_transaction_read_only; select count(*) from public.claims;",
|
||||
]
|
||||
)
|
||||
assert readback.stdout.splitlines() == ["on", "1"]
|
||||
|
||||
write_attempt = run(
|
||||
[
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
f"PGPASSWORD={TEST_PASSWORD}",
|
||||
container,
|
||||
"psql",
|
||||
"-h",
|
||||
"127.0.0.1",
|
||||
"-U",
|
||||
IAM_USER,
|
||||
"-d",
|
||||
DATABASE,
|
||||
"-c",
|
||||
"insert into public.claims values ('22222222-2222-2222-2222-222222222222');",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
assert write_attempt.returncode != 0
|
||||
|
||||
outside_allowlist = run(
|
||||
[
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
f"PGPASSWORD={TEST_PASSWORD}",
|
||||
container,
|
||||
"psql",
|
||||
"-h",
|
||||
"127.0.0.1",
|
||||
"-U",
|
||||
IAM_USER,
|
||||
"-d",
|
||||
DATABASE,
|
||||
"-c",
|
||||
"select * from public.private_notes;",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
assert outside_allowlist.returncode != 0
|
||||
finally:
|
||||
run(["docker", "rm", "--force", container], check=False)
|
||||
|
|
@ -123,11 +123,17 @@ def test_prepare_builds_private_compiler_validated_manifest_without_db_write(
|
|||
assert manifest["dedupe"]["duplicates"][0]["claim_id"] == CANDIDATE_ID
|
||||
assert manifest["claims"][0]["quote"] == CLAIM_QUOTE
|
||||
assert manifest["dedupe"]["duplicates"][0]["source_quote"] == DUPLICATE_QUOTE
|
||||
assert receipt["extraction"]["selected_source_references"] == [
|
||||
{"kind": "claim", "reference": "S00003"},
|
||||
{"kind": "evidence", "reference": "S00003"},
|
||||
{"kind": "duplicate", "reference": "S00002"},
|
||||
selected = receipt["extraction"]["selected_source_references"]
|
||||
assert [(row["kind"], row["reference"]) for row in selected] == [
|
||||
("claim", "S00003"),
|
||||
("evidence", "S00003"),
|
||||
("duplicate", "S00002"),
|
||||
]
|
||||
assert all(row["char_end"] > row["char_start"] for row in selected)
|
||||
assert all(len(row["quote_sha256"]) == 64 for row in selected)
|
||||
assert receipt["replay"]["exact_selected_locations_validated"] is True
|
||||
assert receipt["extraction"]["evidence_count"] == 1
|
||||
assert receipt["extraction"]["duplicate_judgment_count"] == 1
|
||||
assert stat.S_IMODE(output_dir.stat().st_mode) == 0o700
|
||||
for path in output_dir.iterdir():
|
||||
assert stat.S_IMODE(path.stat().st_mode) == 0o600
|
||||
|
|
@ -173,6 +179,63 @@ def test_source_fragment_ids_are_dense_across_blank_lines() -> None:
|
|||
assert fragments == {"S00001": "first", "S00002": "second"}
|
||||
|
||||
|
||||
def test_source_fragment_index_trims_indentation_but_preserves_exact_offsets() -> None:
|
||||
text = " indented Unicode: \u201creviewed\u201d \nnext\n"
|
||||
|
||||
fragments, locations = preparer.build_source_fragment_index(text)
|
||||
|
||||
assert fragments["S00001"] == "indented Unicode: \u201creviewed\u201d"
|
||||
selected = locations["S00001"]
|
||||
assert selected["line"] == 1
|
||||
assert text[selected["char_start"] : selected["char_end"]] == fragments["S00001"]
|
||||
|
||||
|
||||
def test_bundle_location_validation_rejects_ambiguous_rebound() -> None:
|
||||
text = "same line\nsame line\n"
|
||||
fragments, locations = preparer.build_source_fragment_index(text)
|
||||
extraction = preparer.resolve_model_output(
|
||||
{
|
||||
"claims": [
|
||||
{
|
||||
"claim_key": "second_occurrence",
|
||||
"type": "structural",
|
||||
"text": "The second occurrence was selected.",
|
||||
"quote_ref": "S00002",
|
||||
"confidence": 0.5,
|
||||
"tags": [],
|
||||
"evidence": [{"quote_ref": "S00002", "role": "grounds"}],
|
||||
}
|
||||
],
|
||||
"duplicates": [],
|
||||
"extraction_notes": "Select the second repeated line.",
|
||||
},
|
||||
fragments,
|
||||
locations,
|
||||
)
|
||||
fake_bundle = {
|
||||
"quote_bindings": [
|
||||
{
|
||||
"kind": "claim",
|
||||
"claim_key": "second_occurrence",
|
||||
"quote": "same line",
|
||||
"first_start": 0,
|
||||
"first_end": 9,
|
||||
},
|
||||
{
|
||||
"kind": "evidence",
|
||||
"claim_key": "second_occurrence",
|
||||
"evidence_role": "grounds",
|
||||
"quote": "same line",
|
||||
"first_start": 0,
|
||||
"first_end": 9,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with pytest.raises(preparer.PreparationError, match="ambiguous repeated quote"):
|
||||
preparer.validate_bundle_source_locations(fake_bundle, extraction["selected_source_references"])
|
||||
|
||||
|
||||
def test_prepare_returns_no_novel_claims_without_manifest_or_stageable_packet(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
|
|
@ -195,6 +258,13 @@ def test_binary_artifact_requires_explicit_utf8_extraction(tmp_path: Path) -> No
|
|||
preparer.extract_utf8_text(artifact, None)
|
||||
|
||||
|
||||
def test_repo_native_jsonl_transcript_is_accepted_as_strict_utf8(tmp_path: Path) -> None:
|
||||
artifact = tmp_path / "telegram.jsonl"
|
||||
artifact.write_text('{"chat_id":1,"message_id":2,"message":"hello"}\n', encoding="utf-8")
|
||||
|
||||
assert preparer.extract_utf8_text(artifact, None) == artifact.read_bytes()
|
||||
|
||||
|
||||
def test_openrouter_key_cannot_be_redirected_to_an_unapproved_endpoint(tmp_path: Path) -> None:
|
||||
with pytest.raises(preparer.PreparationError, match=r"must equal https://openrouter\.ai"):
|
||||
preparer.call_openrouter(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
|
@ -15,12 +17,12 @@ def _skill(name: str) -> str:
|
|||
def test_manifest_indexes_valid_repo_native_skills() -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
|
||||
assert manifest["status"] == "repo_native_validated"
|
||||
assert manifest["status"] == "repo_native_path_validated"
|
||||
assert manifest["repo_skill_root"] == ".agents/skills"
|
||||
assert manifest["validation"] == "tests/test_repo_skill_pack.py"
|
||||
assert "37/37" in manifest["claim_ceiling"]
|
||||
assert "auto-synchronized" in manifest["claim_ceiling"]
|
||||
assert "apply worker remained disabled/inactive" in manifest["claim_ceiling"]
|
||||
assert "PRs #146 and #147 are merged" in manifest["claim_ceiling"]
|
||||
assert "PR #148 is open candidate evidence only" in manifest["claim_ceiling"]
|
||||
|
||||
for entry in manifest["skills"]:
|
||||
path = ROOT / entry["path"]
|
||||
|
|
@ -34,6 +36,139 @@ def test_manifest_indexes_valid_repo_native_skills() -> None:
|
|||
assert (ROOT / relative).is_file(), relative
|
||||
|
||||
|
||||
def test_manifest_covers_the_required_onboarding_surface() -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
expected = {
|
||||
"company_product_context",
|
||||
"vps",
|
||||
"gcp",
|
||||
"hermes_runtime",
|
||||
"database_provenance",
|
||||
"ingestion",
|
||||
"proposal_apply",
|
||||
"reconstruction",
|
||||
"identity",
|
||||
"testing",
|
||||
"security",
|
||||
"secrets",
|
||||
"incident_recovery",
|
||||
}
|
||||
assert set(manifest["required_coverage"]) == expected
|
||||
skill_names = {entry["name"] for entry in manifest["skills"]}
|
||||
repo_skill_names = {path.parent.name for path in (ROOT / ".agents" / "skills").glob("*/SKILL.md")}
|
||||
assert skill_names == repo_skill_names
|
||||
for owners in manifest["required_coverage"].values():
|
||||
assert owners
|
||||
assert set(owners) <= skill_names
|
||||
|
||||
|
||||
def test_path_validator_and_clean_installer() -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
validator = ROOT / manifest["validator"]
|
||||
result = subprocess.run(
|
||||
[str(validator), "--root", str(ROOT)],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["status"] == "pass"
|
||||
assert payload["skill_count"] == len(manifest["skills"])
|
||||
assert payload["coverage_area_count"] == len(manifest["required_coverage"])
|
||||
assert payload["problems"] == []
|
||||
|
||||
|
||||
def test_installer_copies_only_manifest_skills(tmp_path: Path) -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
installer = ROOT / manifest["installer"]
|
||||
target = tmp_path / "skills"
|
||||
result = subprocess.run(
|
||||
[str(installer), "--root", str(ROOT), "--target", str(target)],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
payload = json.loads(result.stdout)
|
||||
assert payload["status"] == "pass"
|
||||
assert payload["installed_skill_count"] == len(manifest["skills"])
|
||||
assert {path.name for path in target.iterdir()} == {entry["name"] for entry in manifest["skills"]}
|
||||
assert all((target / entry["name"] / "SKILL.md").is_file() for entry in manifest["skills"])
|
||||
|
||||
|
||||
def test_installer_rejects_manifest_path_traversal(tmp_path: Path) -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
manifest["skills"][0]["name"] = "../escape"
|
||||
malicious_manifest = tmp_path / "manifest.json"
|
||||
malicious_manifest.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
target = tmp_path / "target" / "skills"
|
||||
|
||||
result = subprocess.run(
|
||||
[
|
||||
str(ROOT / manifest["installer"]),
|
||||
"--root",
|
||||
str(ROOT),
|
||||
"--manifest",
|
||||
str(malicious_manifest),
|
||||
"--target",
|
||||
str(target),
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
assert result.returncode == 1
|
||||
assert "invalid skill name" in result.stdout
|
||||
assert not (tmp_path / "target" / "escape").exists()
|
||||
assert not target.exists()
|
||||
|
||||
|
||||
def test_isolated_install_canary_reaches_t2_without_claiming_agent_reasoning(tmp_path: Path) -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
canary = ROOT / manifest["isolated_install_canary"]
|
||||
output = tmp_path / "receipt.json"
|
||||
result = subprocess.run(
|
||||
[str(canary), "--root", str(ROOT), "--output", str(output)],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
payload = json.loads(output.read_text(encoding="utf-8"))
|
||||
assert payload["status"] == "pass"
|
||||
assert payload["required_tier"] == "T2_runtime"
|
||||
assert payload["current_tier"] == "T2_runtime"
|
||||
assert payload["proof_scope"] == "deterministic_isolated_install_and_route_validation"
|
||||
assert payload["fresh_temporary_skill_root"] is True
|
||||
assert payload["ambient_skill_root_used"] is False
|
||||
assert payload["agent_reasoning_exercised"] is False
|
||||
assert payload["installed_skill_count"] == len(manifest["skills"])
|
||||
assert all(payload["semantic_checks"].values())
|
||||
assert payload["canary_exit_status"] == 0
|
||||
assert payload["cleanup_readback"]["temporary_skill_root_removed"] is True
|
||||
assert payload["missing_to_reach_required_tier"] == []
|
||||
|
||||
|
||||
def test_fresh_agent_receipt_rejects_stale_routes() -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
receipt = json.loads((ROOT / manifest["fresh_agent_receipt"]).read_text(encoding="utf-8"))
|
||||
|
||||
assert receipt["status"] == "pass"
|
||||
assert receipt["evidence_origin"]["fork_turns"] == "none"
|
||||
assert receipt["evidence_origin"]["memory_used"] is False
|
||||
assert all(receipt["stale_claims_rejected"].values())
|
||||
assert receipt["validator"]["status"] == "pass"
|
||||
assert receipt["validator"]["skill_count"] == 16
|
||||
assert receipt["repository_readback"]["unchanged"] is True
|
||||
assert receipt["cleanup_readback"]["temporary_skill_root_removed"] is True
|
||||
assert receipt["external_runtime_contacted"] is False
|
||||
assert receipt["production_mutation"] is False
|
||||
assert receipt["contains_secrets"] is False
|
||||
assert receipt["problems"] == []
|
||||
|
||||
|
||||
def test_db_change_skill_tracks_guarded_v2_runtime_proof() -> None:
|
||||
text = _skill("teleo-kb-db-change-workflow")
|
||||
squashed = " ".join(text.split())
|
||||
|
|
@ -120,3 +255,31 @@ def test_vps_onboarding_and_m3taversal_skills_point_to_current_proof() -> None:
|
|||
assert "Next proof-changing follow-up:" in outcomes
|
||||
assert "working-leo-current-proof-20260712.md" in outcomes
|
||||
assert "Cory" not in outcomes
|
||||
|
||||
|
||||
def test_reconstruction_and_pr_state_are_routed_without_candidate_overclaim() -> None:
|
||||
reconstruction = _skill("teleo-reconstruction-recovery")
|
||||
onboarding = _skill("teleo-leo-onboarding")
|
||||
gcp = _skill("teleo-gcp-parity-ops")
|
||||
current_truth = (REPORT_DIR / "current-truth-index.md").read_text(encoding="utf-8")
|
||||
|
||||
assert "ops/run_local_genesis_ledger_rebuild.py" in reconstruction
|
||||
assert "scripts/verify_leo_unseen_reasoning_chain.py" in reconstruction
|
||||
assert "Semantic source-to-blank-database recompilation remains incomplete" in reconstruction
|
||||
assert "PRs #146 and #147 are merged repository truth" in onboarding
|
||||
assert "PR #148 remains an open GCP" in onboarding
|
||||
assert "candidate evidence only" in gcp
|
||||
assert "PRs #146 and #147 are merged into canonical `main`" in current_truth
|
||||
assert "PR #148 remains open candidate evidence" in current_truth
|
||||
|
||||
|
||||
def test_active_onboarding_prose_uses_exact_m3taversal_identity() -> None:
|
||||
manifest = json.loads(MANIFEST.read_text(encoding="utf-8"))
|
||||
active_files = [
|
||||
*[ROOT / relative for relative in manifest["scan_files"]],
|
||||
*[ROOT / entry["path"] for entry in manifest["skills"]],
|
||||
]
|
||||
for path in active_files:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
prose = re.sub(r"`[^`]*\/[^`]+\.(?:md|json|svg)`", "", text)
|
||||
assert re.search(r"\bcory(?:-style)?\b", prose, flags=re.IGNORECASE) is None, path
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -13,90 +14,420 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|||
import run_leo_local_ingestion_proposal_canary as canary # noqa: E402
|
||||
|
||||
|
||||
def _loaded() -> tuple[dict, dict, dict]:
|
||||
fixture, input_receipt = canary.load_fixture(canary.DEFAULT_FIXTURE)
|
||||
row_ids = canary.build_row_ids(input_receipt["artifact_sha256"])
|
||||
return fixture, input_receipt, row_ids
|
||||
|
||||
|
||||
def test_fixture_is_realistic_hash_bound_and_exactly_evidenced() -> None:
|
||||
fixture, input_receipt, row_ids = _loaded()
|
||||
|
||||
assert fixture["extraction"]["evidence"]["body"] in fixture["source"]["body"]
|
||||
assert len(input_receipt["artifact_sha256"]) == 64
|
||||
assert len(input_receipt["source_content_sha256"]) == 64
|
||||
assert input_receipt["artifact_sha256"] != input_receipt["source_content_sha256"]
|
||||
assert len(set(row_ids.values())) == 4
|
||||
|
||||
|
||||
def test_fixture_validation_fails_closed_when_evidence_is_not_in_source(tmp_path: Path) -> None:
|
||||
fixture = json.loads(canary.DEFAULT_FIXTURE.read_text(encoding="utf-8"))
|
||||
fixture["extraction"]["evidence"]["body"] = "invented evidence"
|
||||
path = tmp_path / "bad-fixture.json"
|
||||
path.write_text(json.dumps(fixture), encoding="utf-8")
|
||||
|
||||
with pytest.raises(canary.CanaryError, match="exact substring"):
|
||||
canary.load_fixture(path)
|
||||
|
||||
|
||||
def test_fixture_validation_fails_closed_when_claim_body_is_not_in_source(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
fixture = json.loads(canary.DEFAULT_FIXTURE.read_text(encoding="utf-8"))
|
||||
fixture["extraction"]["claim"]["body"] = "invented claim body"
|
||||
path = tmp_path / "bad-claim-fixture.json"
|
||||
path.write_text(json.dumps(fixture), encoding="utf-8")
|
||||
|
||||
with pytest.raises(canary.CanaryError, match="claim body must be an exact substring"):
|
||||
canary.load_fixture(path)
|
||||
|
||||
|
||||
def test_capture_sql_escapes_quotes_and_backslashes_from_fixture(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
fixture = json.loads(canary.DEFAULT_FIXTURE.read_text(encoding="utf-8"))
|
||||
fixture["source"]["title"] = "O'Brien\\review"
|
||||
fixture["source"]["metadata"]["note"] = "quoted ' value \\ path"
|
||||
path = tmp_path / "quoted-fixture.json"
|
||||
path.write_text(json.dumps(fixture), encoding="utf-8")
|
||||
loaded, input_receipt = canary.load_fixture(path)
|
||||
row_ids = canary.build_row_ids(input_receipt["artifact_sha256"])
|
||||
|
||||
sql = canary.build_capture_sql(loaded, input_receipt, row_ids)
|
||||
|
||||
assert canary.ap.sql_literal(fixture["source"]["title"]) in sql
|
||||
assert canary.ap.sql_literal(
|
||||
json.dumps(fixture["source"]["metadata"], sort_keys=True)
|
||||
) in sql
|
||||
|
||||
|
||||
def test_parent_normalizes_to_hash_bound_pending_proposal_with_embedded_row_links() -> None:
|
||||
fixture, input_receipt, row_ids = _loaded()
|
||||
def _loaded(path: Path) -> tuple[dict, dict, dict, dict, dict]:
|
||||
fixture, input_receipt = canary.load_fixture(path)
|
||||
row_ids = canary.build_row_ids(input_receipt, fixture)
|
||||
parent = canary.build_parent_packet(fixture, input_receipt, row_ids)
|
||||
child = canary.normalized_stage.prepare_normalized_child(parent)
|
||||
payload = child["payload"]["apply_payload"]
|
||||
|
||||
assert child["status"] == "pending_review"
|
||||
assert child["proposal_type"] == "approve_claim"
|
||||
assert len(payload["claims"]) == 1
|
||||
assert len(payload["sources"]) == 2
|
||||
assert len(payload["evidence"]) == 2
|
||||
assert input_receipt["source_content_sha256"] in {row["hash"] for row in payload["sources"]}
|
||||
excerpts = "\n".join(row["excerpt"] for row in payload["sources"])
|
||||
assert input_receipt["artifact_sha256"] in excerpts
|
||||
assert row_ids["source_capture_id"] in excerpts
|
||||
assert row_ids["claim_extraction_id"] in excerpts
|
||||
assert row_ids["evidence_extraction_id"] in excerpts
|
||||
return fixture, input_receipt, row_ids, parent, child
|
||||
|
||||
|
||||
def test_capture_and_stage_sql_never_mutate_canonical_public_tables() -> None:
|
||||
fixture, input_receipt, row_ids = _loaded()
|
||||
child = canary.normalized_stage.prepare_normalized_child(
|
||||
canary.build_parent_packet(fixture, input_receipt, row_ids)
|
||||
def _copy_scenario(tmp_path: Path, scenario_path: Path) -> Path:
|
||||
scenario = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||
artifact_source = scenario_path.parent / scenario["artifact"]["path"]
|
||||
artifact_target = tmp_path / artifact_source.name
|
||||
artifact_target.write_bytes(artifact_source.read_bytes())
|
||||
scenario_target = tmp_path / scenario_path.name
|
||||
scenario_target.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
return scenario_target
|
||||
|
||||
|
||||
def _canonical_snapshot() -> dict:
|
||||
return {
|
||||
"claims": [{"id": "1"}],
|
||||
"sources": [{"id": "2"}],
|
||||
"claim_evidence": [{"claim_id": "1"}],
|
||||
"claim_edges": [{"from_claim": "1"}],
|
||||
}
|
||||
|
||||
|
||||
def _row_id_values(row_ids: dict) -> set[str]:
|
||||
values: set[str] = set()
|
||||
for value in row_ids.values():
|
||||
if isinstance(value, dict):
|
||||
values.update(_row_id_values(value))
|
||||
else:
|
||||
values.add(value)
|
||||
return values
|
||||
|
||||
|
||||
def _planned_uuid_values(child: dict) -> set[str]:
|
||||
apply_payload = child["payload"]["apply_payload"]
|
||||
values = {child["id"]}
|
||||
for row in apply_payload["claims"] + apply_payload["sources"]:
|
||||
values.add(row["id"])
|
||||
for row in apply_payload["evidence"]:
|
||||
values.update((row["claim_id"], row["source_id"]))
|
||||
for row in apply_payload["edges"]:
|
||||
values.update((row["from_claim"], row["to_claim"]))
|
||||
return values
|
||||
|
||||
|
||||
def _synthetic_readback(fixture: dict, input_receipt: dict, row_ids: dict, child: dict) -> dict:
|
||||
claim_rows = []
|
||||
evidence_rows = []
|
||||
segment_by_id = {segment["id"]: segment for segment in fixture["segments"]}
|
||||
for claim in fixture["extraction"]["claims"]:
|
||||
claim_id = row_ids["claim_extraction_ids"][claim["claim_key"]]
|
||||
claim_rows.append(
|
||||
{
|
||||
"id": claim_id,
|
||||
"source_capture_id": row_ids["source_capture_id"],
|
||||
"claim_key": claim["claim_key"],
|
||||
"body": claim["body"],
|
||||
"metadata": {
|
||||
**claim["metadata"],
|
||||
"claim_type": claim["type"],
|
||||
"confidence": claim["confidence"],
|
||||
"text": claim["text"],
|
||||
"extractor": input_receipt["extractor"],
|
||||
"replay_identity_sha256": input_receipt["replay_identity_sha256"],
|
||||
},
|
||||
}
|
||||
)
|
||||
for index, evidence in enumerate(claim["evidence"]):
|
||||
segment = segment_by_id[evidence["segment_id"]]
|
||||
evidence_rows.append(
|
||||
{
|
||||
"id": row_ids["evidence_extraction_ids"][f"{claim['claim_key']}:{index}"],
|
||||
"claim_extraction_id": claim_id,
|
||||
"source_capture_id": row_ids["source_capture_id"],
|
||||
"source_segment_id": segment["id"],
|
||||
"source_locator": segment["locator"],
|
||||
"source_json_pointer": segment["json_pointer"],
|
||||
"role": evidence["role"],
|
||||
"source_content_sha256": segment["content_sha256"],
|
||||
"body": evidence["excerpt"],
|
||||
"body_sha256": canary.sha256_bytes(evidence["excerpt"].encode()),
|
||||
"metadata": {
|
||||
**evidence.get("metadata", {}),
|
||||
"source_text_sha256": segment["text_sha256"],
|
||||
},
|
||||
}
|
||||
)
|
||||
duplicate_rows = []
|
||||
for index, duplicate in enumerate(fixture["extraction"]["duplicates"]):
|
||||
duplicate_rows.append(
|
||||
{
|
||||
"id": row_ids["duplicate_assessment_ids"][str(index)],
|
||||
"source_capture_id": row_ids["source_capture_id"],
|
||||
"source_segment_id": duplicate["segment_id"],
|
||||
"source_locator": duplicate["source_locator"],
|
||||
"duplicate_of_claim_key": duplicate["duplicate_of_claim_key"],
|
||||
"excerpt": duplicate["excerpt"],
|
||||
"excerpt_sha256": canary.sha256_bytes(duplicate["excerpt"].encode()),
|
||||
"reason": duplicate["reason"],
|
||||
"metadata": {
|
||||
"json_pointer": duplicate["source_json_pointer"],
|
||||
"source_content_sha256": duplicate["source_content_sha256"],
|
||||
"source_text_sha256": duplicate["source_text_sha256"],
|
||||
},
|
||||
}
|
||||
)
|
||||
conflict_rows = []
|
||||
for index, conflict in enumerate(fixture["extraction"]["conflicts"]):
|
||||
conflict_rows.append(
|
||||
{
|
||||
"id": row_ids["conflict_assessment_ids"][str(index)],
|
||||
"source_capture_id": row_ids["source_capture_id"],
|
||||
"from_claim_key": conflict["from_claim_key"],
|
||||
"to_claim_key": conflict["to_claim_key"],
|
||||
"relationship": conflict["relationship"],
|
||||
"metadata": {
|
||||
key: value
|
||||
for key, value in conflict.items()
|
||||
if key not in {"from_claim_key", "to_claim_key", "relationship"}
|
||||
},
|
||||
}
|
||||
)
|
||||
proposal = {
|
||||
**child,
|
||||
"reviewed_by_handle": None,
|
||||
"reviewed_at": None,
|
||||
"applied_by_handle": None,
|
||||
"applied_at": None,
|
||||
}
|
||||
counts = input_receipt["counts"]
|
||||
return {
|
||||
"source_captures": [
|
||||
{
|
||||
"id": row_ids["source_capture_id"],
|
||||
"source_identity": fixture["source"]["identity"],
|
||||
"source_type": fixture["source"]["source_type"],
|
||||
"title": fixture["source"]["title"],
|
||||
"locator": fixture["source"]["locator"],
|
||||
"artifact_path": input_receipt["artifact_path"],
|
||||
"artifact_sha256": input_receipt["artifact_sha256"],
|
||||
"content_sha256": input_receipt["source_content_sha256"],
|
||||
"replay_identity_sha256": input_receipt["replay_identity_sha256"],
|
||||
"metadata": {**fixture["source"]["metadata"], "extractor": input_receipt["extractor"]},
|
||||
}
|
||||
],
|
||||
"claim_extractions": claim_rows,
|
||||
"evidence_extractions": evidence_rows,
|
||||
"duplicate_assessments": duplicate_rows,
|
||||
"conflict_assessments": conflict_rows,
|
||||
"staged_proposal": proposal,
|
||||
"counts": {
|
||||
"source_captures": 1,
|
||||
"claim_extractions": counts["claim_candidates"],
|
||||
"evidence_extractions": counts["evidence_candidates"],
|
||||
"duplicate_assessments": counts["duplicate_judgments"],
|
||||
"conflict_assessments": counts["conflict_relationships"],
|
||||
"staged_proposals": 1,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("scenario_path", canary.DEFAULT_FIXTURES)
|
||||
def test_source_artifact_is_separate_hash_bound_and_replayable(scenario_path: Path) -> None:
|
||||
fixture, input_receipt, row_ids, parent, child = _loaded(scenario_path)
|
||||
artifact_path = Path(input_receipt["artifact_path"])
|
||||
|
||||
assert input_receipt["artifact_sha256"] == hashlib.sha256(artifact_path.read_bytes()).hexdigest()
|
||||
assert input_receipt["artifact_sha256"] != input_receipt["scenario_sha256"]
|
||||
assert len(input_receipt["replay_identity_sha256"]) == 64
|
||||
assert row_ids == canary.build_row_ids(input_receipt, fixture)
|
||||
ingestion = child["payload"]["apply_payload"]["normalization_manifest"]["ingestion_manifest"]
|
||||
assert ingestion["artifact_sha256"] == input_receipt["artifact_sha256"]
|
||||
assert ingestion["extractor"] == input_receipt["extractor"]
|
||||
assert ingestion["replay_identity_sha256"] == input_receipt["replay_identity_sha256"]
|
||||
assert ingestion["row_ids"] == row_ids
|
||||
assert parent["status"] == child["status"] == "pending_review"
|
||||
|
||||
|
||||
def test_document_and_telegram_candidate_accounting_is_exact() -> None:
|
||||
_doc, doc_input, _doc_ids, _doc_parent, doc_child = _loaded(canary.DEFAULT_DOCUMENT_FIXTURE)
|
||||
telegram, telegram_input, _telegram_ids, _telegram_parent, telegram_child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
|
||||
assert doc_input["counts"] == {
|
||||
"source_segments": 3,
|
||||
"claim_candidates": 1,
|
||||
"evidence_candidates": 1,
|
||||
"duplicate_judgments": 0,
|
||||
"conflict_relationships": 0,
|
||||
}
|
||||
assert telegram_input["counts"] == {
|
||||
"source_segments": 3,
|
||||
"claim_candidates": 2,
|
||||
"evidence_candidates": 3,
|
||||
"duplicate_judgments": 1,
|
||||
"conflict_relationships": 1,
|
||||
}
|
||||
doc_payload = doc_child["payload"]["apply_payload"]
|
||||
telegram_payload = telegram_child["payload"]["apply_payload"]
|
||||
assert {key: len(doc_payload[key]) for key in ("claims", "sources", "evidence", "edges")} == {
|
||||
"claims": 1,
|
||||
"sources": 2,
|
||||
"evidence": 2,
|
||||
"edges": 0,
|
||||
}
|
||||
assert {key: len(telegram_payload[key]) for key in ("claims", "sources", "evidence", "edges")} == {
|
||||
"claims": 2,
|
||||
"sources": 5,
|
||||
"evidence": 5,
|
||||
"edges": 1,
|
||||
}
|
||||
assessment = telegram_payload["normalization_manifest"]["dedupe_conflict_assessment"]
|
||||
assert assessment["duplicates"] == telegram["extraction"]["duplicates"]
|
||||
assert assessment["conflicts"] == telegram["extraction"]["conflicts"]
|
||||
assert telegram_payload["edges"][0]["edge_type"] == "contradicts"
|
||||
|
||||
|
||||
def test_telegram_duplicate_text_keeps_distinct_exact_message_provenance() -> None:
|
||||
fixture, _input, _row_ids, _parent, _child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
first, _second, repeated = fixture["segments"]
|
||||
|
||||
assert first["body"] == repeated["body"]
|
||||
assert first["text_sha256"] == repeated["text_sha256"]
|
||||
assert first["content_sha256"] != repeated["content_sha256"]
|
||||
assert first["locator"] == "telegram://chat/900001/message/7301"
|
||||
assert repeated["locator"] == "telegram://chat/900001/message/7303"
|
||||
duplicate = fixture["extraction"]["duplicates"][0]
|
||||
assert duplicate["source_locator"] == repeated["locator"]
|
||||
assert duplicate["source_json_pointer"] == "jsonl://line/3/message"
|
||||
|
||||
|
||||
def test_fixture_validation_fails_closed_on_invented_evidence(tmp_path: Path) -> None:
|
||||
scenario_path = _copy_scenario(tmp_path, canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
scenario = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||
scenario["extraction"]["claims"][0]["evidence"][0]["excerpt"] = "invented evidence"
|
||||
scenario_path.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
|
||||
with pytest.raises(canary.CanaryError, match="not an exact segment substring"):
|
||||
canary.load_fixture(scenario_path)
|
||||
|
||||
|
||||
def test_fixture_artifact_path_cannot_escape_scenario_directory(tmp_path: Path) -> None:
|
||||
scenario = json.loads(canary.DEFAULT_DOCUMENT_FIXTURE.read_text(encoding="utf-8"))
|
||||
scenario["artifact"]["path"] = "../outside.txt"
|
||||
path = tmp_path / "scenario.json"
|
||||
path.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
|
||||
with pytest.raises(canary.CanaryError, match="remain inside"):
|
||||
canary.load_fixture(path)
|
||||
|
||||
|
||||
def test_capture_sql_escapes_quotes_and_backslashes(tmp_path: Path) -> None:
|
||||
scenario_path = _copy_scenario(tmp_path, canary.DEFAULT_DOCUMENT_FIXTURE)
|
||||
scenario = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||
scenario["source"]["title"] = "O'Brien\\review"
|
||||
scenario["source"]["metadata"]["note"] = "quoted ' value \\ path"
|
||||
scenario_path.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
fixture, input_receipt, row_ids, _parent, _child = _loaded(scenario_path)
|
||||
|
||||
sql = canary.build_capture_sql(fixture, input_receipt, row_ids)
|
||||
|
||||
assert canary.ap.sql_literal(scenario["source"]["title"]) in sql
|
||||
expected_metadata = {
|
||||
**scenario["source"]["metadata"],
|
||||
"extractor": input_receipt["extractor"],
|
||||
}
|
||||
assert canary.ap.sql_literal(json.dumps(expected_metadata, sort_keys=True)) in sql
|
||||
|
||||
|
||||
def test_replay_identity_and_ids_change_with_extractor_version(tmp_path: Path) -> None:
|
||||
scenario_path = _copy_scenario(tmp_path, canary.DEFAULT_DOCUMENT_FIXTURE)
|
||||
fixture, original, original_ids, _parent, original_child = _loaded(scenario_path)
|
||||
scenario = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||
scenario["extractor"]["version"] = "3"
|
||||
scenario_path.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
changed_fixture, changed, changed_ids, _changed_parent, changed_child = _loaded(scenario_path)
|
||||
|
||||
assert original["artifact_sha256"] == changed["artifact_sha256"]
|
||||
assert original["replay_identity_sha256"] != changed["replay_identity_sha256"]
|
||||
assert original_ids["source_capture_id"] != changed_ids["source_capture_id"]
|
||||
assert original_ids["claim_extraction_ids"] != changed_ids["claim_extraction_ids"]
|
||||
assert original_ids["parent_proposal_id"] != changed_ids["parent_proposal_id"]
|
||||
assert original_child["payload_sha256"] != changed_child["payload_sha256"]
|
||||
assert fixture["segments"] == changed_fixture["segments"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("source_field", "replacement"),
|
||||
(
|
||||
("identity", "document:mutated-exact-source-identity"),
|
||||
("locator", "fixture://working-leo/mutated-exact-source-locator"),
|
||||
),
|
||||
)
|
||||
def test_replay_identity_and_every_row_id_bind_exact_source_identity(
|
||||
tmp_path: Path, source_field: str, replacement: str
|
||||
) -> None:
|
||||
scenario_path = _copy_scenario(tmp_path, canary.DEFAULT_DOCUMENT_FIXTURE)
|
||||
fixture, before, before_ids, _parent, before_child = _loaded(scenario_path)
|
||||
scenario = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||
scenario["source"][source_field] = replacement
|
||||
scenario_path.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
|
||||
changed_fixture, after, after_ids, _changed_parent, after_child = _loaded(scenario_path)
|
||||
|
||||
assert before["artifact_sha256"] == after["artifact_sha256"]
|
||||
assert before["extraction_spec_sha256"] == after["extraction_spec_sha256"]
|
||||
assert before["replay_identity_sha256"] != after["replay_identity_sha256"]
|
||||
assert _row_id_values(before_ids).isdisjoint(_row_id_values(after_ids))
|
||||
assert _planned_uuid_values(before_child).isdisjoint(_planned_uuid_values(after_child))
|
||||
assert fixture["source"][source_field] != changed_fixture["source"][source_field]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("segment_field", "replacement"),
|
||||
(
|
||||
("id", "message-900001-mutated"),
|
||||
("locator", "telegram://chat/900001/message/999999"),
|
||||
("json_pointer", "jsonl://line/999/message"),
|
||||
("content_sha256", "a" * 64),
|
||||
("text_sha256", "b" * 64),
|
||||
),
|
||||
)
|
||||
def test_replay_identity_and_every_row_id_bind_complete_normalized_segment(
|
||||
segment_field: str, replacement: str
|
||||
) -> None:
|
||||
fixture, before, before_ids, _parent, before_child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
changed_fixture = copy.deepcopy(fixture)
|
||||
after = copy.deepcopy(before)
|
||||
segment = changed_fixture["segments"][-1]
|
||||
original_id = segment["id"]
|
||||
segment[segment_field] = replacement
|
||||
for claim in changed_fixture["extraction"]["claims"]:
|
||||
for evidence in claim["evidence"]:
|
||||
if evidence["segment_id"] == original_id:
|
||||
evidence["segment_id"] = segment["id"]
|
||||
evidence["source_locator"] = segment["locator"]
|
||||
evidence["source_json_pointer"] = segment["json_pointer"]
|
||||
evidence["source_content_sha256"] = segment["content_sha256"]
|
||||
evidence["source_text_sha256"] = segment["text_sha256"]
|
||||
for duplicate in changed_fixture["extraction"]["duplicates"]:
|
||||
if duplicate["segment_id"] == original_id:
|
||||
duplicate["segment_id"] = segment["id"]
|
||||
duplicate["source_locator"] = segment["locator"]
|
||||
duplicate["source_json_pointer"] = segment["json_pointer"]
|
||||
duplicate["source_content_sha256"] = segment["content_sha256"]
|
||||
duplicate["source_text_sha256"] = segment["text_sha256"]
|
||||
after["source_content_sha256"] = canary.canonical_sha256(
|
||||
canary.normalized_segment_manifest(changed_fixture["segments"])
|
||||
)
|
||||
after["replay_identity_components"] = canary.replay_identity_payload(
|
||||
artifact_sha256=after["artifact_sha256"],
|
||||
artifact_format=after["artifact_format"],
|
||||
source_identity=after["source_identity"],
|
||||
source_locator=after["source_locator"],
|
||||
normalized_segments_sha256=after["source_content_sha256"],
|
||||
extractor=after["extractor"],
|
||||
extraction_spec_sha256=after["extraction_spec_sha256"],
|
||||
)
|
||||
after["replay_identity_sha256"] = canary.canonical_sha256(after["replay_identity_components"])
|
||||
after_ids = canary.build_row_ids(after, changed_fixture)
|
||||
after_parent = canary.build_parent_packet(changed_fixture, after, after_ids)
|
||||
after_child = canary.normalized_stage.prepare_normalized_child(after_parent)
|
||||
|
||||
assert before["artifact_sha256"] == after["artifact_sha256"]
|
||||
assert before["scenario_sha256"] == after["scenario_sha256"]
|
||||
assert before["extraction_spec_sha256"] == after["extraction_spec_sha256"]
|
||||
assert before["replay_identity_sha256"] != after["replay_identity_sha256"]
|
||||
assert _row_id_values(before_ids).isdisjoint(_row_id_values(after_ids))
|
||||
assert _planned_uuid_values(before_child).isdisjoint(_planned_uuid_values(after_child))
|
||||
|
||||
|
||||
def test_source_byte_tamper_changes_hashes_and_replay_ids(tmp_path: Path) -> None:
|
||||
scenario_path = _copy_scenario(tmp_path, canary.DEFAULT_DOCUMENT_FIXTURE)
|
||||
fixture, before, before_ids, _parent, _child = _loaded(scenario_path)
|
||||
artifact_path = Path(before["artifact_path"])
|
||||
artifact_path.write_text(
|
||||
artifact_path.read_text(encoding="utf-8") + "\n\nA new source paragraph.", encoding="utf-8"
|
||||
)
|
||||
changed_fixture, after, after_ids, _changed_parent, _changed_child = _loaded(scenario_path)
|
||||
|
||||
assert before["artifact_sha256"] != after["artifact_sha256"]
|
||||
assert before["source_content_sha256"] != after["source_content_sha256"]
|
||||
assert before["replay_identity_sha256"] != after["replay_identity_sha256"]
|
||||
assert before_ids["source_capture_id"] != after_ids["source_capture_id"]
|
||||
assert before_ids["claim_extraction_ids"] != after_ids["claim_extraction_ids"]
|
||||
assert len(changed_fixture["segments"]) == len(fixture["segments"]) + 1
|
||||
|
||||
|
||||
def test_replay_properties_hold_across_many_version_labels(tmp_path: Path) -> None:
|
||||
scenario_path = _copy_scenario(tmp_path, canary.DEFAULT_DOCUMENT_FIXTURE)
|
||||
base = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||
observed: set[str] = set()
|
||||
for index in range(40):
|
||||
scenario = copy.deepcopy(base)
|
||||
scenario["extractor"]["version"] = f"property-{index}"
|
||||
scenario_path.write_text(json.dumps(scenario), encoding="utf-8")
|
||||
fixture_a, receipt_a = canary.load_fixture(scenario_path)
|
||||
fixture_b, receipt_b = canary.load_fixture(scenario_path)
|
||||
ids_a = canary.build_row_ids(receipt_a, fixture_a)
|
||||
ids_b = canary.build_row_ids(receipt_b, fixture_b)
|
||||
assert receipt_a == receipt_b
|
||||
assert ids_a == ids_b
|
||||
observed.add(receipt_a["replay_identity_sha256"])
|
||||
assert len(observed) == 40
|
||||
|
||||
|
||||
@pytest.mark.parametrize("scenario_path", canary.DEFAULT_FIXTURES)
|
||||
def test_capture_and_stage_sql_do_not_mutate_canonical_tables(scenario_path: Path) -> None:
|
||||
fixture, input_receipt, row_ids, parent, child = _loaded(scenario_path)
|
||||
sql = "\n".join(
|
||||
(
|
||||
canary.build_schema_sql(),
|
||||
canary.build_capture_sql(fixture, input_receipt, row_ids),
|
||||
canary.normalized_stage.build_stage_sql(child),
|
||||
)
|
||||
|
|
@ -106,59 +437,346 @@ def test_capture_and_stage_sql_never_mutate_canonical_public_tables() -> None:
|
|||
assert "update public." not in sql
|
||||
assert "delete from public." not in sql
|
||||
assert "insert into kb_canary.source_captures" in sql
|
||||
assert "insert into kb_canary.claim_extractions" in sql
|
||||
assert "insert into kb_canary.evidence_extractions" in sql
|
||||
assert "insert into kb_stage.kb_proposals" in sql
|
||||
assert parent["payload"]["ingestion_manifest"]["row_ids"] == row_ids
|
||||
|
||||
|
||||
def test_check_evaluation_requires_every_row_link_and_staging_boundary() -> None:
|
||||
fixture, input_receipt, row_ids = _loaded()
|
||||
child = canary.normalized_stage.prepare_normalized_child(
|
||||
canary.build_parent_packet(fixture, input_receipt, row_ids)
|
||||
def test_canonical_snapshot_covers_all_nonempty_canonical_tables() -> None:
|
||||
schema_sql = canary.build_schema_sql().lower()
|
||||
snapshot_sql = canary.build_canonical_snapshot_sql().lower()
|
||||
|
||||
for table in ("claims", "sources", "claim_evidence", "claim_edges"):
|
||||
assert f"insert into public.{table}" in schema_sql
|
||||
assert f"from public.{table}" in snapshot_sql
|
||||
assert "order by" in snapshot_sql
|
||||
assert "begin transaction read only" in snapshot_sql
|
||||
assert canary.canonical_snapshot_complete({}) is False
|
||||
assert (
|
||||
canary.canonical_snapshot_complete(
|
||||
{
|
||||
"claims": [{"id": "1"}],
|
||||
"sources": [{"id": "2"}],
|
||||
"claim_evidence": [{"claim_id": "1"}],
|
||||
"claim_edges": [{"from_claim": "1"}],
|
||||
}
|
||||
)
|
||||
is True
|
||||
)
|
||||
apply_payload = child["payload"]["apply_payload"]
|
||||
readback = {
|
||||
"source_capture": {
|
||||
"id": row_ids["source_capture_id"],
|
||||
"artifact_sha256": input_receipt["artifact_sha256"],
|
||||
"content_sha256": input_receipt["source_content_sha256"],
|
||||
"source_identity": input_receipt["source_identity"],
|
||||
},
|
||||
"claim_extraction": {
|
||||
"id": row_ids["claim_extraction_id"],
|
||||
"source_capture_id": row_ids["source_capture_id"],
|
||||
"body": fixture["extraction"]["claim"]["body"],
|
||||
"metadata": {"text": fixture["extraction"]["claim"]["text"]},
|
||||
},
|
||||
"evidence_extraction": {
|
||||
"id": row_ids["evidence_extraction_id"],
|
||||
"claim_extraction_id": row_ids["claim_extraction_id"],
|
||||
"source_capture_id": row_ids["source_capture_id"],
|
||||
"body": fixture["extraction"]["evidence"]["body"],
|
||||
"metadata": fixture["extraction"]["evidence"]["metadata"],
|
||||
},
|
||||
"staged_proposal": {
|
||||
"id": child["id"],
|
||||
"status": "pending_review",
|
||||
"source_ref": child["source_ref"],
|
||||
"payload": {"apply_payload": apply_payload},
|
||||
"reviewed_by_handle": None,
|
||||
"reviewed_at": None,
|
||||
"applied_by_handle": None,
|
||||
"applied_at": None,
|
||||
},
|
||||
"counts": {
|
||||
"source_captures": 1,
|
||||
"claim_extractions": 1,
|
||||
"evidence_extractions": 1,
|
||||
"staged_proposals": 1,
|
||||
},
|
||||
|
||||
|
||||
def test_evaluation_fails_when_exact_evidence_locator_is_changed() -> None:
|
||||
fixture, input_receipt, row_ids, _parent, child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
readback = _synthetic_readback(fixture, input_receipt, row_ids, child)
|
||||
canonical = {
|
||||
"claims": [{"id": "1"}],
|
||||
"sources": [{"id": "2"}],
|
||||
"claim_evidence": [{"claim_id": "1"}],
|
||||
"claim_edges": [{"from_claim": "1"}],
|
||||
}
|
||||
|
||||
checks = canary.evaluate_checks(fixture, input_receipt, row_ids, readback)
|
||||
checks = canary.evaluate_checks(fixture, input_receipt, row_ids, readback, canonical, copy.deepcopy(canonical))
|
||||
assert all(checks.values())
|
||||
broken = copy.deepcopy(readback)
|
||||
broken["evidence_extraction"]["claim_extraction_id"] = canary.stable_uuid("0" * 64, "wrong")
|
||||
broken["evidence_extractions"][0]["source_locator"] = "telegram://chat/900001/message/9999"
|
||||
assert (
|
||||
canary.evaluate_checks(fixture, input_receipt, row_ids, broken)["evidence_links_to_claim_and_source"] is False
|
||||
canary.evaluate_checks(fixture, input_receipt, row_ids, broken, canonical, copy.deepcopy(canonical))[
|
||||
"all_evidence_has_exact_source_location"
|
||||
]
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
def test_evaluation_recomputes_instead_of_trusting_supplied_row_ids() -> None:
|
||||
fixture, input_receipt, row_ids, _parent, _child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
tampered_ids = copy.deepcopy(row_ids)
|
||||
tampered_ids["source_capture_id"] = "00000000-0000-4000-8000-000000009990"
|
||||
tampered_parent = canary.build_parent_packet(fixture, input_receipt, tampered_ids)
|
||||
tampered_child = canary.normalized_stage.prepare_normalized_child(tampered_parent)
|
||||
readback = _synthetic_readback(fixture, input_receipt, tampered_ids, tampered_child)
|
||||
|
||||
checks = canary.evaluate_checks(
|
||||
fixture, input_receipt, tampered_ids, readback, _canonical_snapshot(), _canonical_snapshot()
|
||||
)
|
||||
|
||||
assert checks["deterministic_row_ids_recomputed_exact"] is False
|
||||
assert checks["source_capture_identity_exact"] is False
|
||||
assert checks["planned_proposal_identities_and_linkages_exact"] is False
|
||||
|
||||
|
||||
def test_independent_graph_oracle_rejects_consistent_generator_edge_corruption(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
fixture, input_receipt = canary.load_fixture(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
row_ids = canary.build_row_ids(input_receipt, fixture)
|
||||
parent = canary.build_parent_packet(fixture, input_receipt, row_ids)
|
||||
original_prepare = canary.normalized_stage.prepare_normalized_child
|
||||
|
||||
def corrupted_prepare(parent_packet: dict) -> dict:
|
||||
child = copy.deepcopy(original_prepare(parent_packet))
|
||||
child["payload"]["apply_payload"]["edges"][0]["to_claim"] = "00000000-0000-4000-8000-000000009996"
|
||||
return child
|
||||
|
||||
monkeypatch.setattr(canary.normalized_stage, "prepare_normalized_child", corrupted_prepare)
|
||||
corrupted_child = corrupted_prepare(parent)
|
||||
readback = _synthetic_readback(fixture, input_receipt, row_ids, corrupted_child)
|
||||
|
||||
checks = canary.evaluate_checks(
|
||||
fixture, input_receipt, row_ids, readback, _canonical_snapshot(), _canonical_snapshot()
|
||||
)
|
||||
|
||||
assert checks["planned_graph_matches_independent_source_oracle"] is False
|
||||
assert checks["planned_proposal_identities_and_linkages_exact"] is False
|
||||
assert checks["conflicts_and_relationships_persisted"] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mutation", "failed_check"),
|
||||
(
|
||||
("source_id", "source_capture_identity_exact"),
|
||||
("source_identity", "source_capture_identity_exact"),
|
||||
("source_locator", "source_capture_identity_exact"),
|
||||
("claim_id", "claim_extraction_identities_and_fks_exact"),
|
||||
("claim_source_fk", "claim_extraction_identities_and_fks_exact"),
|
||||
("evidence_id", "evidence_extraction_identities_and_fks_exact"),
|
||||
("evidence_claim_fk", "evidence_extraction_identities_and_fks_exact"),
|
||||
("evidence_source_fk", "evidence_extraction_identities_and_fks_exact"),
|
||||
("evidence_segment_id", "evidence_extraction_identities_and_fks_exact"),
|
||||
("evidence_json_pointer", "evidence_extraction_identities_and_fks_exact"),
|
||||
("evidence_role", "evidence_extraction_identities_and_fks_exact"),
|
||||
("evidence_body_sha256", "evidence_extraction_identities_and_fks_exact"),
|
||||
("proposal_id", "planned_proposal_identities_and_linkages_exact"),
|
||||
("planned_claim_id", "planned_proposal_identities_and_linkages_exact"),
|
||||
("planned_source_id", "planned_proposal_identities_and_linkages_exact"),
|
||||
("planned_evidence_claim_fk", "planned_proposal_identities_and_linkages_exact"),
|
||||
("planned_evidence_source_fk", "planned_proposal_identities_and_linkages_exact"),
|
||||
("planned_evidence_role", "planned_proposal_identities_and_linkages_exact"),
|
||||
("manifest_row_id", "planned_proposal_identities_and_linkages_exact"),
|
||||
),
|
||||
)
|
||||
def test_evaluation_rejects_wrong_deterministic_ids_and_every_fk_linkage(mutation: str, failed_check: str) -> None:
|
||||
fixture, input_receipt, row_ids, _parent, child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
readback = _synthetic_readback(fixture, input_receipt, row_ids, child)
|
||||
broken = copy.deepcopy(readback)
|
||||
wrong_uuid = "00000000-0000-4000-8000-000000009999"
|
||||
apply_payload = broken["staged_proposal"]["payload"]["apply_payload"]
|
||||
if mutation == "source_id":
|
||||
broken["source_captures"][0]["id"] = wrong_uuid
|
||||
elif mutation == "source_identity":
|
||||
broken["source_captures"][0]["source_identity"] = "fixture:wrong-source-identity"
|
||||
elif mutation == "source_locator":
|
||||
broken["source_captures"][0]["locator"] = "fixture://working-leo/wrong-source-locator"
|
||||
elif mutation == "claim_id":
|
||||
broken["claim_extractions"][0]["id"] = broken["claim_extractions"][1]["id"]
|
||||
elif mutation == "claim_source_fk":
|
||||
broken["claim_extractions"][0]["source_capture_id"] = wrong_uuid
|
||||
elif mutation == "evidence_id":
|
||||
broken["evidence_extractions"][0]["id"] = broken["evidence_extractions"][1]["id"]
|
||||
elif mutation == "evidence_claim_fk":
|
||||
broken["evidence_extractions"][0]["claim_extraction_id"] = broken["claim_extractions"][1]["id"]
|
||||
elif mutation == "evidence_source_fk":
|
||||
broken["evidence_extractions"][0]["source_capture_id"] = wrong_uuid
|
||||
elif mutation == "evidence_segment_id":
|
||||
broken["evidence_extractions"][0]["source_segment_id"] = "message-900001-9999"
|
||||
elif mutation == "evidence_json_pointer":
|
||||
broken["evidence_extractions"][0]["source_json_pointer"] = "jsonl://line/999/message"
|
||||
elif mutation == "evidence_role":
|
||||
broken["evidence_extractions"][0]["role"] = "supports"
|
||||
elif mutation == "evidence_body_sha256":
|
||||
broken["evidence_extractions"][0]["body_sha256"] = "d" * 64
|
||||
elif mutation == "proposal_id":
|
||||
broken["staged_proposal"]["id"] = wrong_uuid
|
||||
elif mutation == "planned_claim_id":
|
||||
apply_payload["claims"][0]["id"] = apply_payload["claims"][1]["id"]
|
||||
elif mutation == "planned_source_id":
|
||||
apply_payload["sources"][0]["id"] = apply_payload["sources"][1]["id"]
|
||||
elif mutation == "planned_evidence_claim_fk":
|
||||
apply_payload["evidence"][0]["claim_id"] = apply_payload["claims"][1]["id"]
|
||||
elif mutation == "planned_evidence_source_fk":
|
||||
apply_payload["evidence"][0]["source_id"] = apply_payload["sources"][1]["id"]
|
||||
elif mutation == "planned_evidence_role":
|
||||
apply_payload["evidence"][0]["role"] = "supports"
|
||||
elif mutation == "manifest_row_id":
|
||||
apply_payload["normalization_manifest"]["ingestion_manifest"]["row_ids"]["source_capture_id"] = wrong_uuid
|
||||
|
||||
checks = canary.evaluate_checks(
|
||||
fixture, input_receipt, row_ids, broken, _canonical_snapshot(), _canonical_snapshot()
|
||||
)
|
||||
|
||||
assert checks[failed_check] is False
|
||||
assert not all(checks.values())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("surface", "field", "replacement", "failed_check"),
|
||||
(
|
||||
("duplicate", "id", "00000000-0000-4000-8000-000000009991", "duplicate_judgments_persisted"),
|
||||
(
|
||||
"duplicate",
|
||||
"source_capture_id",
|
||||
"00000000-0000-4000-8000-000000009992",
|
||||
"duplicate_judgments_persisted",
|
||||
),
|
||||
("duplicate", "source_segment_id", "message-900001-9999", "duplicate_judgments_persisted"),
|
||||
(
|
||||
"duplicate",
|
||||
"source_locator",
|
||||
"telegram://chat/900001/message/9999",
|
||||
"duplicate_judgments_persisted",
|
||||
),
|
||||
("duplicate", "duplicate_of_claim_key", "approval_alone_makes_canonical", "duplicate_judgments_persisted"),
|
||||
("duplicate", "excerpt", "fabricated duplicate excerpt", "duplicate_judgments_persisted"),
|
||||
("duplicate", "excerpt_sha256", "c" * 64, "duplicate_judgments_persisted"),
|
||||
("duplicate", "reason", "fabricated duplicate reason", "duplicate_judgments_persisted"),
|
||||
("duplicate", "metadata", {"json_pointer": "fabricated"}, "duplicate_judgments_persisted"),
|
||||
("conflict", "id", "00000000-0000-4000-8000-000000009993", "conflicts_and_relationships_persisted"),
|
||||
(
|
||||
"conflict",
|
||||
"source_capture_id",
|
||||
"00000000-0000-4000-8000-000000009994",
|
||||
"conflicts_and_relationships_persisted",
|
||||
),
|
||||
("conflict", "from_claim_key", "approval_alone_makes_canonical", "conflicts_and_relationships_persisted"),
|
||||
(
|
||||
"conflict",
|
||||
"to_claim_key",
|
||||
"pending_review_does_not_change_canonical",
|
||||
"conflicts_and_relationships_persisted",
|
||||
),
|
||||
("conflict", "relationship", "supports", "conflicts_and_relationships_persisted"),
|
||||
("conflict", "metadata", {"reason": "fabricated"}, "conflicts_and_relationships_persisted"),
|
||||
("edge", "from_claim", "00000000-0000-4000-8000-000000009995", "conflicts_and_relationships_persisted"),
|
||||
("edge", "to_claim", "00000000-0000-4000-8000-000000009996", "conflicts_and_relationships_persisted"),
|
||||
("edge", "edge_type", "supports", "conflicts_and_relationships_persisted"),
|
||||
("edge", "weight", 0.5, "conflicts_and_relationships_persisted"),
|
||||
("edge", "created_by", "00000000-0000-4000-8000-000000009997", "conflicts_and_relationships_persisted"),
|
||||
),
|
||||
)
|
||||
def test_evaluation_rejects_mutated_duplicate_conflict_and_edge_identities(
|
||||
surface: str, field: str, replacement: object, failed_check: str
|
||||
) -> None:
|
||||
fixture, input_receipt, row_ids, _parent, child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
broken = _synthetic_readback(fixture, input_receipt, row_ids, child)
|
||||
if surface == "duplicate":
|
||||
broken["duplicate_assessments"][0][field] = replacement
|
||||
elif surface == "conflict":
|
||||
broken["conflict_assessments"][0][field] = replacement
|
||||
else:
|
||||
broken["staged_proposal"]["payload"]["apply_payload"]["edges"][0][field] = replacement
|
||||
|
||||
checks = canary.evaluate_checks(
|
||||
fixture, input_receipt, row_ids, broken, _canonical_snapshot(), _canonical_snapshot()
|
||||
)
|
||||
|
||||
assert checks[failed_check] is False
|
||||
assert not all(checks.values())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field", "replacement"),
|
||||
(
|
||||
("reviewed_by_handle", "reviewer"),
|
||||
("reviewed_by_agent_id", "00000000-0000-4000-8000-000000009981"),
|
||||
("reviewed_at", "2026-07-15T00:00:00+00:00"),
|
||||
("review_note", "fabricated review note"),
|
||||
("applied_by_handle", "applier"),
|
||||
("applied_by_agent_id", "00000000-0000-4000-8000-000000009982"),
|
||||
("applied_at", "2026-07-15T00:00:01+00:00"),
|
||||
),
|
||||
)
|
||||
def test_evaluation_rejects_every_review_and_apply_metadata_mutation(field: str, replacement: str) -> None:
|
||||
fixture, input_receipt, row_ids, _parent, child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||
broken = _synthetic_readback(fixture, input_receipt, row_ids, child)
|
||||
broken["staged_proposal"][field] = replacement
|
||||
|
||||
checks = canary.evaluate_checks(
|
||||
fixture, input_receipt, row_ids, broken, _canonical_snapshot(), _canonical_snapshot()
|
||||
)
|
||||
|
||||
assert checks["no_review_or_apply_metadata"] is False
|
||||
assert checks["planned_proposal_identities_and_linkages_exact"] is False
|
||||
|
||||
|
||||
def _suite_child(artifact_format: str, *, passed: bool = True, canonical_unchanged: bool = True) -> dict:
|
||||
before = "a" * 64
|
||||
after = before if canonical_unchanged else "b" * 64
|
||||
return {
|
||||
"status": "pass" if passed else "fail",
|
||||
"input": {"artifact_format": artifact_format},
|
||||
"canonical": {
|
||||
"fingerprint_before": before,
|
||||
"fingerprint_after": after,
|
||||
"unchanged": canonical_unchanged,
|
||||
},
|
||||
"checks": {"canonical_fingerprint_unchanged": canonical_unchanged},
|
||||
}
|
||||
|
||||
|
||||
def test_single_fixture_suite_is_truthfully_partial(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
monkeypatch.setattr(canary, "run_canary", lambda _path: _suite_child("plain_text"))
|
||||
|
||||
suite = canary.run_suite([tmp_path / "document.scenario.json"])
|
||||
|
||||
assert suite["status"] == "partial"
|
||||
assert suite["full_suite_passed"] is False
|
||||
assert suite["coverage_status"] == "partial"
|
||||
assert suite["missing_required_coverage"] == ["social_conversation"]
|
||||
|
||||
|
||||
def test_malformed_fixture_fails_closed_with_receipt_and_without_runtime(tmp_path: Path) -> None:
|
||||
malformed = tmp_path / "malformed.scenario.json"
|
||||
malformed.write_text("{not-json", encoding="utf-8")
|
||||
|
||||
receipt = canary.run_canary(malformed)
|
||||
suite = canary.run_suite([malformed])
|
||||
|
||||
assert receipt["status"] == "fail"
|
||||
assert receipt["error"]["type"] == "CanaryError"
|
||||
assert receipt["cleanup"]["runtime_not_started"] is True
|
||||
assert receipt["cleanup"]["container_absent"] is True
|
||||
assert suite["status"] == "fail"
|
||||
assert suite["receipts"][0]["error"]["type"] == "CanaryError"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("children", "expected_status"),
|
||||
(
|
||||
(
|
||||
[_suite_child("plain_text"), _suite_child("telegram_jsonl")],
|
||||
"pass",
|
||||
),
|
||||
(
|
||||
[_suite_child("plain_text", canonical_unchanged=False), _suite_child("telegram_jsonl")],
|
||||
"fail",
|
||||
),
|
||||
(
|
||||
[_suite_child("plain_text"), _suite_child("telegram_jsonl", passed=False)],
|
||||
"fail",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_full_suite_status_requires_both_shapes_all_children_and_canonical_invariance(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, children: list[dict], expected_status: str
|
||||
) -> None:
|
||||
queue = iter(children)
|
||||
monkeypatch.setattr(canary, "run_canary", lambda _path: next(queue))
|
||||
|
||||
suite = canary.run_suite([tmp_path / "document.scenario.json", tmp_path / "telegram.scenario.json"])
|
||||
|
||||
assert suite["status"] == expected_status
|
||||
assert suite["full_suite_passed"] is (expected_status == "pass")
|
||||
|
||||
|
||||
def test_suite_recomputes_canonical_invariance_instead_of_trusting_stale_boolean(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
children = [_suite_child("plain_text"), _suite_child("telegram_jsonl")]
|
||||
children[0]["canonical"]["fingerprint_after"] = "c" * 64
|
||||
queue = iter(children)
|
||||
monkeypatch.setattr(canary, "run_canary", lambda _path: next(queue))
|
||||
|
||||
suite = canary.run_suite([tmp_path / "document.scenario.json", tmp_path / "telegram.scenario.json"])
|
||||
|
||||
assert suite["status"] == "fail"
|
||||
assert suite["canonical_fingerprint_invariant_all"] is False
|
||||
assert suite["full_suite_passed"] is False
|
||||
assert "canonical_fingerprint_invariance" in suite["missing_required_coverage"]
|
||||
|
|
|
|||
|
|
@ -23,6 +23,18 @@ REVIEWER_ID = "11111111-1111-4111-8111-111111111111"
|
|||
SERVICE_ID = "44444444-4444-4444-4444-444444444444"
|
||||
PROPOSAL_ID = "99999999-9999-4999-8999-999999999999"
|
||||
EDGE_ID = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"
|
||||
OTHER_AGENT_ID = "22222222-2222-4222-8222-222222222222"
|
||||
REVISE_PROPOSAL_ID = "88888888-8888-4888-8888-888888888888"
|
||||
PRIOR_STRATEGY_ID = "33333333-3333-4333-8333-333333333333"
|
||||
PRIOR_ACTIVE_NODE_ID = "55555555-5555-4555-8555-555555555555"
|
||||
PRIOR_RETIRED_NODE_ID = "66666666-6666-4666-8666-666666666666"
|
||||
OTHER_STRATEGY_ID = "77777777-7777-4777-8777-777777777777"
|
||||
OTHER_ACTIVE_NODE_ID = "12121212-1212-4212-8212-121212121212"
|
||||
NULL_AGENT_NODE_ID = "13131313-1313-4313-8313-131313131313"
|
||||
PRIOR_NODE_ANCHOR_ID = "19191919-1919-4919-8919-191919191919"
|
||||
RECEIPT_STRATEGY_ID = "14141414-1414-4414-8414-141414141414"
|
||||
RECEIPT_NODE_A_ID = "15151515-1515-4515-8515-151515151515"
|
||||
RECEIPT_NODE_B_ID = "16161616-1616-4616-8616-161616161616"
|
||||
PRIVATE_MARKER = "PRIVATE-REPLAY-MATERIAL-MUST-NOT-LEAK"
|
||||
|
||||
|
||||
|
|
@ -155,6 +167,58 @@ def proposal_rows() -> tuple[dict, dict, dict]:
|
|||
return approved, applied, approval
|
||||
|
||||
|
||||
def revise_strategy_proposal_rows() -> tuple[dict, dict, dict]:
|
||||
approved, _, approval = proposal_rows()
|
||||
payload = {
|
||||
"apply_payload": {
|
||||
"agent_id": REVIEWER_ID,
|
||||
"strategy": {
|
||||
"diagnosis": "Deterministic reconstruction needs exact mutation deltas.",
|
||||
"guiding_policy": "Replay every guarded transition and fail closed on drift.",
|
||||
"proximate_objectives": ["exact parity", "zero orphan containers"],
|
||||
},
|
||||
"strategy_nodes": [
|
||||
{
|
||||
"node_type": "policy",
|
||||
"title": "Replay exactly",
|
||||
"body": "Bind the guarded transition to its canonical receipt.",
|
||||
"rank": 1,
|
||||
},
|
||||
{
|
||||
"node_type": "policy",
|
||||
"title": "Replay exactly",
|
||||
"body": "Bind the guarded transition to its canonical receipt.",
|
||||
"rank": 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
"private_title": PRIVATE_MARKER,
|
||||
}
|
||||
approved = {
|
||||
**approved,
|
||||
"id": REVISE_PROPOSAL_ID,
|
||||
"proposal_type": "revise_strategy",
|
||||
"payload": payload,
|
||||
"created_at": "2026-07-14T00:58:00+00:00",
|
||||
"updated_at": "2026-07-14T01:00:00+00:00",
|
||||
}
|
||||
applied = {
|
||||
**approved,
|
||||
"status": "applied",
|
||||
"applied_by_handle": "kb-apply",
|
||||
"applied_by_agent_id": SERVICE_ID,
|
||||
"applied_at": "2026-07-14T01:01:00+00:00",
|
||||
"updated_at": "2026-07-14T01:01:00.000001+00:00",
|
||||
}
|
||||
approval = {
|
||||
**approval,
|
||||
"proposal_id": REVISE_PROPOSAL_ID,
|
||||
"proposal_type": "revise_strategy",
|
||||
"payload": payload,
|
||||
}
|
||||
return approved, applied, approval
|
||||
|
||||
|
||||
def applied_envelope(applied: dict) -> dict:
|
||||
fields = (
|
||||
"id",
|
||||
|
|
@ -202,6 +266,73 @@ def replay_material() -> dict:
|
|||
}
|
||||
|
||||
|
||||
def revise_strategy_replay_material(*, apply_sql_source: str = "exact_executed_sql", version: int = 2) -> dict:
|
||||
approved, applied, approval = revise_strategy_proposal_rows()
|
||||
proposal = applied_envelope(applied)
|
||||
transaction_timestamp = "2026-07-14T01:00:30+00:00"
|
||||
canonical_rows = {
|
||||
"strategies": [
|
||||
{
|
||||
"id": RECEIPT_STRATEGY_ID,
|
||||
"agent_id": REVIEWER_ID,
|
||||
**proposal["payload"]["apply_payload"]["strategy"],
|
||||
"version": version,
|
||||
"active": True,
|
||||
"created_at": transaction_timestamp,
|
||||
}
|
||||
],
|
||||
"strategy_nodes": [
|
||||
{
|
||||
"id": node_id,
|
||||
"agent_id": REVIEWER_ID,
|
||||
**node,
|
||||
"status": "active",
|
||||
"horizon": None,
|
||||
"measure": None,
|
||||
"source_ref": None,
|
||||
"metadata": {},
|
||||
"created_at": transaction_timestamp,
|
||||
"updated_at": transaction_timestamp,
|
||||
}
|
||||
for node_id, node in zip(
|
||||
(RECEIPT_NODE_A_ID, RECEIPT_NODE_B_ID),
|
||||
proposal["payload"]["apply_payload"]["strategy_nodes"],
|
||||
strict=True,
|
||||
)
|
||||
],
|
||||
}
|
||||
apply_sql = rebuild.apply_engine.build_apply_sql(proposal, applied["applied_by_handle"])
|
||||
receipt = rebuild.replay_receipt.build_replay_receipt(
|
||||
proposal,
|
||||
canonical_rows,
|
||||
apply_sql_sha256=rebuild.replay_receipt.sha256_text(apply_sql),
|
||||
apply_sql_source=apply_sql_source,
|
||||
exported_at_utc="2026-07-14T01:02:00+00:00",
|
||||
)
|
||||
return {
|
||||
"artifact": rebuild.MATERIAL_ARTIFACT,
|
||||
"contract_version": rebuild.MATERIAL_CONTRACT_VERSION,
|
||||
"sequence": 1,
|
||||
"approved_proposal": approved,
|
||||
"approval_snapshot": approval,
|
||||
"applied_proposal": applied,
|
||||
"replay_receipt": receipt,
|
||||
}
|
||||
|
||||
|
||||
def rehash_revise_strategy_receipt(material: dict) -> None:
|
||||
prior_receipt = material["replay_receipt"]
|
||||
proposal = applied_envelope(material["applied_proposal"])
|
||||
apply_sql = rebuild.apply_engine.build_apply_sql(proposal, material["applied_proposal"]["applied_by_handle"])
|
||||
material["replay_receipt"] = rebuild.replay_receipt.build_replay_receipt(
|
||||
proposal,
|
||||
prior_receipt["canonical_rows"],
|
||||
apply_sql_sha256=rebuild.replay_receipt.sha256_text(apply_sql),
|
||||
apply_sql_source=prior_receipt["apply_engine"]["source"],
|
||||
exported_at_utc=prior_receipt["exported_at_utc"],
|
||||
)
|
||||
|
||||
|
||||
def write_bundle(tmp_path: Path, *, material: dict | None = None) -> argparse.Namespace:
|
||||
dump = tmp_path / "genesis.dump"
|
||||
dump.write_bytes(b"PGDMPfixture")
|
||||
|
|
@ -238,7 +369,7 @@ def write_bundle(tmp_path: Path, *, material: dict | None = None) -> argparse.Na
|
|||
ledger=ledger_path,
|
||||
ledger_sha256=sha256_file(ledger_path),
|
||||
database="teleo",
|
||||
image=rebuild.canonical_rebuild.DEFAULT_IMAGE,
|
||||
image=rebuild.DEFAULT_REBUILD_IMAGE,
|
||||
container_prefix="teleo-genesis-ledger-test",
|
||||
tmpfs_mb=256,
|
||||
startup_timeout=30.0,
|
||||
|
|
@ -302,18 +433,192 @@ def test_dump_hash_mismatch_fails_before_container_start(tmp_path: Path, monkeyp
|
|||
assert not any(len(command) > 1 and command[1] == "run" for command in commands)
|
||||
|
||||
|
||||
def test_revise_strategy_receipt_fails_closed_before_receipt_rows_are_used(tmp_path: Path) -> None:
|
||||
material = replay_material()
|
||||
material["replay_receipt"]["proposal"]["proposal_type"] = "revise_strategy"
|
||||
material["applied_proposal"]["proposal_type"] = "revise_strategy"
|
||||
material["approved_proposal"]["proposal_type"] = "revise_strategy"
|
||||
material["approval_snapshot"]["proposal_type"] = "revise_strategy"
|
||||
def test_revise_strategy_material_validates_exact_engine_and_canonicalizes_row_order(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
material = revise_strategy_replay_material()
|
||||
material["replay_receipt"]["canonical_rows"]["strategy_nodes"].reverse()
|
||||
args = write_bundle(tmp_path, material=material)
|
||||
|
||||
with pytest.raises(rebuild.ReconstructionError, match="prior strategy") as exc_info:
|
||||
entry = rebuild.load_bundle(args).entries[0]
|
||||
|
||||
assert entry.applied_proposal["proposal_type"] == "revise_strategy"
|
||||
assert entry.receipt["apply_engine"]["source"] == "exact_executed_sql"
|
||||
assert entry.receipt["apply_engine"]["apply_sql_sha256"] == entry.current_apply_sql_sha256
|
||||
assert entry.receipt["canonical_rows"]["strategy_nodes"] == sorted(
|
||||
entry.receipt["canonical_rows"]["strategy_nodes"], key=rebuild._canonical_json
|
||||
)
|
||||
|
||||
|
||||
def test_revise_strategy_material_rejects_reconstructed_apply_engine(tmp_path: Path) -> None:
|
||||
args = write_bundle(
|
||||
tmp_path,
|
||||
material=revise_strategy_replay_material(apply_sql_source="reconstructed_current_engine"),
|
||||
)
|
||||
|
||||
with pytest.raises(rebuild.ReconstructionError) as exc_info:
|
||||
rebuild.load_bundle(args)
|
||||
|
||||
assert exc_info.value.code == "unsupported_mutating_receipt"
|
||||
assert exc_info.value.code == "mutating_apply_engine_mismatch"
|
||||
|
||||
|
||||
def test_revise_strategy_rejects_fully_rehashed_transaction_before_approval(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
material = revise_strategy_replay_material()
|
||||
original_receipt = material["replay_receipt"]
|
||||
forged_timestamp = "2026-07-13T01:00:30+00:00"
|
||||
canonical_rows = copy.deepcopy(original_receipt["canonical_rows"])
|
||||
canonical_rows["strategies"][0]["created_at"] = forged_timestamp
|
||||
for row in canonical_rows["strategy_nodes"]:
|
||||
row["created_at"] = forged_timestamp
|
||||
row["updated_at"] = forged_timestamp
|
||||
material["replay_receipt"]["canonical_rows"] = canonical_rows
|
||||
rehash_revise_strategy_receipt(material)
|
||||
args = write_bundle(tmp_path, material=material)
|
||||
ledger = json.loads(args.ledger.read_text(encoding="utf-8"))
|
||||
material_path = args.ledger.parent / ledger["entries"][0]["material"]
|
||||
|
||||
assert material["replay_receipt"]["hashes"] != original_receipt["hashes"]
|
||||
assert ledger["entries"][0]["sha256"] == sha256_file(material_path)
|
||||
assert (
|
||||
ledger["entries"][0]["replay_material_sha256"] == material["replay_receipt"]["hashes"]["replay_material_sha256"]
|
||||
)
|
||||
assert args.ledger_sha256 == sha256_file(args.ledger)
|
||||
with pytest.raises(rebuild.ReconstructionError) as exc_info:
|
||||
rebuild.load_bundle(args)
|
||||
|
||||
assert exc_info.value.code == "invalid_mutating_receipt"
|
||||
assert "before proposal approval" in exc_info.value.safe_message
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("reviewed_at", "transaction_timestamp", "applied_at"),
|
||||
(
|
||||
(
|
||||
"2026-07-14T03:00:00+02:00",
|
||||
"2026-07-14T01:00:00+00:00",
|
||||
"2026-07-14T01:01:00+00:00",
|
||||
),
|
||||
(
|
||||
"2026-07-14T01:00:00+00:00",
|
||||
"2026-07-14T01:01:00+00:00",
|
||||
"2026-07-14T03:01:00+02:00",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_revise_strategy_accepts_timezone_aware_closed_timestamp_boundaries(
|
||||
tmp_path: Path,
|
||||
reviewed_at: str,
|
||||
transaction_timestamp: str,
|
||||
applied_at: str,
|
||||
) -> None:
|
||||
material = revise_strategy_replay_material()
|
||||
for proposal_row in (material["approved_proposal"], material["applied_proposal"]):
|
||||
proposal_row["reviewed_at"] = reviewed_at
|
||||
material["approval_snapshot"]["reviewed_at"] = reviewed_at
|
||||
material["applied_proposal"]["applied_at"] = applied_at
|
||||
rows = material["replay_receipt"]["canonical_rows"]
|
||||
rows["strategies"][0]["created_at"] = transaction_timestamp
|
||||
for row in rows["strategy_nodes"]:
|
||||
row["created_at"] = transaction_timestamp
|
||||
row["updated_at"] = transaction_timestamp
|
||||
rehash_revise_strategy_receipt(material)
|
||||
|
||||
entry = rebuild.load_bundle(write_bundle(tmp_path, material=material)).entries[0]
|
||||
|
||||
assert entry.receipt["canonical_rows"]["strategies"][0]["created_at"] == transaction_timestamp
|
||||
|
||||
|
||||
def test_revise_strategy_rejects_reviewed_at_without_timezone(tmp_path: Path) -> None:
|
||||
material = revise_strategy_replay_material()
|
||||
reviewed_at = "2026-07-14T01:00:00"
|
||||
for proposal_row in (material["approved_proposal"], material["applied_proposal"]):
|
||||
proposal_row["reviewed_at"] = reviewed_at
|
||||
material["approval_snapshot"]["reviewed_at"] = reviewed_at
|
||||
rehash_revise_strategy_receipt(material)
|
||||
|
||||
with pytest.raises(rebuild.ReconstructionError) as exc_info:
|
||||
rebuild.load_bundle(write_bundle(tmp_path, material=material))
|
||||
|
||||
assert exc_info.value.code == "invalid_mutating_receipt"
|
||||
assert "reviewed_at must include a timezone" in exc_info.value.safe_message
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_case",
|
||||
("extra_strategy_field", "duplicate_node_id", "node_timestamp_drift", "transaction_after_apply"),
|
||||
)
|
||||
def test_revise_strategy_receipt_rejects_impossible_poststate(tmp_path: Path, invalid_case: str) -> None:
|
||||
material = revise_strategy_replay_material()
|
||||
rows = material["replay_receipt"]["canonical_rows"]
|
||||
if invalid_case == "extra_strategy_field":
|
||||
rows["strategies"][0]["unexpected"] = "ignored by jsonb_populate_record"
|
||||
elif invalid_case == "duplicate_node_id":
|
||||
rows["strategy_nodes"][1]["id"] = rows["strategy_nodes"][0]["id"]
|
||||
elif invalid_case == "node_timestamp_drift":
|
||||
rows["strategy_nodes"][0]["updated_at"] = "2026-07-14T01:00:31+00:00"
|
||||
else:
|
||||
rows["strategies"][0]["created_at"] = "2026-07-14T01:01:01+00:00"
|
||||
for row in rows["strategy_nodes"]:
|
||||
row["created_at"] = "2026-07-14T01:01:01+00:00"
|
||||
row["updated_at"] = "2026-07-14T01:01:01+00:00"
|
||||
args = write_bundle(tmp_path, material=material)
|
||||
|
||||
with pytest.raises(rebuild.ReconstructionError) as exc_info:
|
||||
rebuild.load_bundle(args)
|
||||
|
||||
assert exc_info.value.code == "invalid_mutating_receipt"
|
||||
|
||||
|
||||
def test_revise_strategy_transition_builders_preserve_historical_prestate(tmp_path: Path) -> None:
|
||||
entry = rebuild.load_bundle(write_bundle(tmp_path, material=revise_strategy_replay_material())).entries[0]
|
||||
prestate = {
|
||||
"strategy_ids": [PRIOR_STRATEGY_ID],
|
||||
"active_strategy_ids": [PRIOR_STRATEGY_ID],
|
||||
"max_strategy_version": 1,
|
||||
"strategy_node_ids": [PRIOR_ACTIVE_NODE_ID, PRIOR_RETIRED_NODE_ID],
|
||||
"non_retired_strategy_node_ids": [PRIOR_ACTIVE_NODE_ID],
|
||||
}
|
||||
generated_rows = copy.deepcopy(entry.receipt["canonical_rows"])
|
||||
generated_rows["strategies"][0]["id"] = "17171717-1717-4717-8717-171717171717"
|
||||
generated_rows["strategies"][0]["created_at"] = "2026-07-15T01:00:30+00:00"
|
||||
for index, row in enumerate(generated_rows["strategy_nodes"], start=1):
|
||||
row["id"] = f"18181818-1818-4818-8818-18181818181{index}"
|
||||
row["created_at"] = "2026-07-15T01:00:30+00:00"
|
||||
row["updated_at"] = "2026-07-15T01:00:30+00:00"
|
||||
|
||||
sql = rebuild.build_revise_strategy_normalization_sql(entry, prestate, generated_rows)
|
||||
|
||||
assert PRIOR_ACTIVE_NODE_ID in sql
|
||||
assert PRIOR_RETIRED_NODE_ID not in sql
|
||||
assert PRIOR_STRATEGY_ID in sql
|
||||
assert RECEIPT_STRATEGY_ID in sql
|
||||
assert "updated_at = E'2026-07-14T01:00:30+00:00'::timestamptz" in sql
|
||||
|
||||
|
||||
def test_revise_strategy_transition_builders_allow_empty_prestate(tmp_path: Path) -> None:
|
||||
entry = rebuild.load_bundle(write_bundle(tmp_path, material=revise_strategy_replay_material(version=1))).entries[0]
|
||||
prestate = {
|
||||
"strategy_ids": [],
|
||||
"active_strategy_ids": [],
|
||||
"max_strategy_version": 0,
|
||||
"strategy_node_ids": [],
|
||||
"non_retired_strategy_node_ids": [],
|
||||
}
|
||||
generated_rows = copy.deepcopy(entry.receipt["canonical_rows"])
|
||||
generated_rows["strategies"][0]["id"] = "17171717-1717-4717-8717-171717171717"
|
||||
generated_rows["strategies"][0]["created_at"] = "2026-07-15T01:00:30+00:00"
|
||||
for index, row in enumerate(generated_rows["strategy_nodes"], start=1):
|
||||
row["id"] = f"18181818-1818-4818-8818-18181818181{index}"
|
||||
row["created_at"] = "2026-07-15T01:00:30+00:00"
|
||||
row["updated_at"] = "2026-07-15T01:00:30+00:00"
|
||||
|
||||
sql = rebuild.build_revise_strategy_normalization_sql(entry, prestate, generated_rows)
|
||||
|
||||
assert "prior active strategy transition mismatch" not in sql
|
||||
assert "prior node normalization mismatch" not in sql
|
||||
assert RECEIPT_STRATEGY_ID in sql
|
||||
|
||||
|
||||
def test_legacy_freeform_receipt_fails_closed(tmp_path: Path) -> None:
|
||||
|
|
@ -331,6 +636,17 @@ def test_legacy_freeform_receipt_fails_closed(tmp_path: Path) -> None:
|
|||
assert PRIVATE_MARKER not in exc_info.value.safe_message
|
||||
|
||||
|
||||
def test_unknown_proposal_operation_fails_closed_before_replay(tmp_path: Path) -> None:
|
||||
material = replay_material()
|
||||
material["replay_receipt"]["proposal"]["proposal_type"] = "delete_claim"
|
||||
args = write_bundle(tmp_path, material=material)
|
||||
|
||||
with pytest.raises(rebuild.ReconstructionError) as exc_info:
|
||||
rebuild.load_bundle(args)
|
||||
|
||||
assert exc_info.value.code == "unsupported_proposal_type"
|
||||
|
||||
|
||||
def test_proposal_metadata_outside_apply_transition_is_rejected(tmp_path: Path) -> None:
|
||||
material = replay_material()
|
||||
material["applied_proposal"]["rationale"] = "silently changed after review"
|
||||
|
|
@ -354,6 +670,28 @@ def test_output_cannot_overwrite_a_fixed_guard_or_engine_file(tmp_path: Path) ->
|
|||
assert exc_info.value.code == "unsafe_output_path"
|
||||
|
||||
|
||||
def test_cli_defaults_to_pinned_image_and_rejects_moving_tag(tmp_path: Path) -> None:
|
||||
bundle = write_bundle(tmp_path)
|
||||
argv = [
|
||||
"--genesis-dump",
|
||||
str(bundle.genesis_dump),
|
||||
"--genesis-manifest",
|
||||
str(bundle.genesis_manifest),
|
||||
"--ledger",
|
||||
str(bundle.ledger),
|
||||
"--ledger-sha256",
|
||||
bundle.ledger_sha256,
|
||||
"--output",
|
||||
str(tmp_path / "receipt.json"),
|
||||
]
|
||||
|
||||
assert rebuild.parse_args(argv).image == rebuild.DEFAULT_REBUILD_IMAGE
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
rebuild.parse_args([*argv, "--image", "postgres:16-alpine"])
|
||||
|
||||
assert exc_info.value.code == 2
|
||||
|
||||
|
||||
def test_unknown_private_field_name_is_not_echoed_in_public_error(tmp_path: Path) -> None:
|
||||
material = replay_material()
|
||||
material[PRIVATE_MARKER] = "private value"
|
||||
|
|
@ -484,17 +822,18 @@ create table public.reasoning_tools (
|
|||
created_at timestamptz not null default now()
|
||||
);
|
||||
create table public.strategies (
|
||||
id uuid primary key,
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
agent_id uuid not null references public.agents(id),
|
||||
diagnosis text,
|
||||
guiding_policy text,
|
||||
proximate_objectives jsonb,
|
||||
version integer not null default 1,
|
||||
active boolean not null default true,
|
||||
created_at timestamptz not null default now()
|
||||
created_at timestamptz not null default now(),
|
||||
unique (agent_id, version)
|
||||
);
|
||||
create table public.strategy_nodes (
|
||||
id uuid primary key,
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
agent_id uuid references public.agents(id),
|
||||
node_type text,
|
||||
title text,
|
||||
|
|
@ -508,6 +847,20 @@ create table public.strategy_nodes (
|
|||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
create table public.strategy_node_anchors (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
from_node_id uuid not null references public.strategy_nodes(id) on delete cascade,
|
||||
anchor_role text not null,
|
||||
to_node_id uuid references public.strategy_nodes(id) on delete cascade,
|
||||
to_shared_root_id uuid,
|
||||
belief_id uuid,
|
||||
claim_id uuid,
|
||||
source_id uuid,
|
||||
weight numeric,
|
||||
note text,
|
||||
created_at timestamptz not null default now(),
|
||||
check (num_nonnulls(to_node_id, to_shared_root_id, belief_id, claim_id, source_id) = 1)
|
||||
);
|
||||
create table kb_stage.kb_proposals (
|
||||
id uuid primary key,
|
||||
proposal_type text not null,
|
||||
|
|
@ -530,10 +883,34 @@ create table kb_stage.kb_proposals (
|
|||
);
|
||||
|
||||
insert into public.agents (id, handle, kind) values
|
||||
('{REVIEWER_ID}', 'm3ta', 'human');
|
||||
('{REVIEWER_ID}', 'm3ta', 'human'),
|
||||
('{OTHER_AGENT_ID}', 'other-agent', 'system');
|
||||
insert into public.claims (id, type, text, status, confidence, tags, created_by) values
|
||||
('{CLAIM_A}', 'structural', 'Fixture claim A', 'open', 0.8, array['fixture'], '{REVIEWER_ID}'),
|
||||
('{CLAIM_B}', 'structural', 'Fixture claim B', 'open', 0.8, array['fixture'], '{REVIEWER_ID}');
|
||||
insert into public.strategies
|
||||
(id, agent_id, diagnosis, guiding_policy, proximate_objectives, version, active, created_at)
|
||||
values
|
||||
('{PRIOR_STRATEGY_ID}', '{REVIEWER_ID}', 'Prior diagnosis', 'Prior policy', '["prior"]', 1, true,
|
||||
'2026-07-01T00:00:00+00:00'),
|
||||
('{OTHER_STRATEGY_ID}', '{OTHER_AGENT_ID}', 'Other diagnosis', 'Other policy', '["other"]', 1, true,
|
||||
'2026-07-01T00:00:00+00:00');
|
||||
insert into public.strategy_nodes
|
||||
(id, agent_id, node_type, title, body, rank, status, created_at, updated_at)
|
||||
values
|
||||
('{PRIOR_ACTIVE_NODE_ID}', '{REVIEWER_ID}', 'policy', 'Prior active', 'Retire exactly once', 1,
|
||||
'active', '2026-07-01T00:00:00+00:00', '2026-07-01T00:00:00+00:00'),
|
||||
('{PRIOR_RETIRED_NODE_ID}', '{REVIEWER_ID}', 'policy', 'Prior retired', 'Leave timestamp unchanged', 2,
|
||||
'retired', '2026-06-01T00:00:00+00:00', '2026-06-02T00:00:00+00:00'),
|
||||
('{OTHER_ACTIVE_NODE_ID}', '{OTHER_AGENT_ID}', 'policy', 'Other active', 'Leave other agent unchanged', 1,
|
||||
'active', '2026-07-01T00:00:00+00:00', '2026-07-01T00:00:00+00:00'),
|
||||
('{NULL_AGENT_NODE_ID}', null, 'policy', 'Shared active', 'Leave shared node unchanged', 1,
|
||||
'active', '2026-07-01T00:00:00+00:00', '2026-07-01T00:00:00+00:00');
|
||||
insert into public.strategy_node_anchors
|
||||
(id, from_node_id, anchor_role, to_node_id, weight, note, created_at)
|
||||
values
|
||||
('{PRIOR_NODE_ANCHOR_ID}', '{PRIOR_ACTIVE_NODE_ID}', 'serves', '{PRIOR_RETIRED_NODE_ID}', 1.0,
|
||||
'Existing anchor must survive strategy revision replay', '2026-07-01T00:00:00+00:00');
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -631,13 +1008,17 @@ def source_postgres() -> Iterator[tuple[str, str]]:
|
|||
"run",
|
||||
"--rm",
|
||||
"--detach",
|
||||
"--network",
|
||||
"none",
|
||||
"--name",
|
||||
container,
|
||||
"--label",
|
||||
f"{rebuild.canonical_rebuild.CANARY_LABEL}={container}",
|
||||
"--env",
|
||||
f"POSTGRES_DB={database}",
|
||||
"--env",
|
||||
"POSTGRES_HOST_AUTH_METHOD=trust",
|
||||
rebuild.canonical_rebuild.DEFAULT_IMAGE,
|
||||
rebuild.DEFAULT_REBUILD_IMAGE,
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
|
|
@ -692,14 +1073,24 @@ def source_postgres() -> Iterator[tuple[str, str]]:
|
|||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
inspected = subprocess.run(
|
||||
["docker", "container", "inspect", container],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
if inspected.returncode == 0:
|
||||
pytest.fail(f"fixture PostgreSQL container was not removed: {container}")
|
||||
|
||||
|
||||
def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
||||
def capture_source_snapshot(
|
||||
source_container: str,
|
||||
database: str,
|
||||
tmp_path: Path,
|
||||
source_postgres: tuple[str, str],
|
||||
) -> None:
|
||||
source_container, database = source_postgres
|
||||
genesis_dump = tmp_path / "genesis.dump"
|
||||
*,
|
||||
stem: str,
|
||||
) -> tuple[Path, Path]:
|
||||
dump = tmp_path / f"{stem}.dump"
|
||||
dump_result = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
|
|
@ -711,7 +1102,7 @@ def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
|||
"-d",
|
||||
database,
|
||||
"--format=custom",
|
||||
"--file=/tmp/genesis.dump",
|
||||
f"--file=/tmp/{stem}.dump",
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
|
|
@ -719,15 +1110,130 @@ def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
|||
)
|
||||
assert dump_result.returncode == 0, dump_result.stderr
|
||||
copy_result = subprocess.run(
|
||||
["docker", "cp", f"{source_container}:/tmp/genesis.dump", str(genesis_dump)],
|
||||
["docker", "cp", f"{source_container}:/tmp/{stem}.dump", str(dump)],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
assert copy_result.returncode == 0, copy_result.stderr
|
||||
manifest = tmp_path / f"{stem}-manifest.jsonl"
|
||||
capture_manifest(source_container, database, manifest)
|
||||
return dump, manifest
|
||||
|
||||
genesis_manifest = tmp_path / "genesis-manifest.jsonl"
|
||||
capture_manifest(source_container, database, genesis_manifest)
|
||||
|
||||
def seed_proposal_and_approval(
|
||||
source_container: str,
|
||||
database: str,
|
||||
approved: dict,
|
||||
approval: dict,
|
||||
) -> dict:
|
||||
sql = f"""begin;
|
||||
set local standard_conforming_strings = on;
|
||||
insert into kb_stage.kb_proposals
|
||||
select seeded.* from jsonb_populate_record(
|
||||
null::kb_stage.kb_proposals, {rebuild._jsonb_literal(approved)}
|
||||
) seeded;
|
||||
insert into kb_stage.kb_proposal_approvals
|
||||
select seeded.* from jsonb_populate_record(
|
||||
null::kb_stage.kb_proposal_approvals, {rebuild._jsonb_literal(approval)}
|
||||
) seeded;
|
||||
commit;
|
||||
"""
|
||||
docker_psql(source_container, database, sql)
|
||||
raw = docker_psql(
|
||||
source_container,
|
||||
database,
|
||||
rebuild.build_proposal_readback_sql(approved["id"]),
|
||||
).stdout.strip()
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def run_genesis_ledger_command(
|
||||
*,
|
||||
tmp_path: Path,
|
||||
database: str,
|
||||
genesis_dump: Path,
|
||||
genesis_manifest: Path,
|
||||
material_path: Path,
|
||||
final_manifest: Path,
|
||||
artifact_stem: str,
|
||||
container_prefix: str,
|
||||
run_number: int,
|
||||
) -> tuple[subprocess.CompletedProcess[str], dict, Path]:
|
||||
ledger = {
|
||||
"artifact": rebuild.LEDGER_ARTIFACT,
|
||||
"contract_version": rebuild.LEDGER_CONTRACT_VERSION,
|
||||
"engine": rebuild._engine_hashes(),
|
||||
"genesis": {
|
||||
"dump_sha256": sha256_file(genesis_dump),
|
||||
"parity_manifest_sha256": sha256_file(genesis_manifest),
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"material": material_path.name,
|
||||
"sha256": sha256_file(material_path),
|
||||
"replay_material_sha256": json.loads(material_path.read_text(encoding="utf-8"))["replay_receipt"][
|
||||
"hashes"
|
||||
]["replay_material_sha256"],
|
||||
}
|
||||
],
|
||||
"final_parity": {
|
||||
"manifest": final_manifest.name,
|
||||
"sha256": sha256_file(final_manifest),
|
||||
},
|
||||
}
|
||||
ledger_path = write_json(tmp_path / f"{artifact_stem}-ledger-run-{run_number}.json", ledger)
|
||||
output = tmp_path / f"{artifact_stem}-reconstruction-receipt-run-{run_number}.json"
|
||||
command = [
|
||||
sys.executable,
|
||||
str(Path(rebuild.__file__).resolve()),
|
||||
"--genesis-dump",
|
||||
str(genesis_dump),
|
||||
"--genesis-manifest",
|
||||
str(genesis_manifest),
|
||||
"--ledger",
|
||||
str(ledger_path),
|
||||
"--ledger-sha256",
|
||||
sha256_file(ledger_path),
|
||||
"--database",
|
||||
database,
|
||||
"--container-prefix",
|
||||
container_prefix,
|
||||
"--tmpfs-mb",
|
||||
"256",
|
||||
"--max-target-query-ms",
|
||||
"5000",
|
||||
"--max-target-source-ratio",
|
||||
"100",
|
||||
"--output",
|
||||
str(output),
|
||||
]
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
cwd=rebuild.REPO_ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
timeout=180,
|
||||
)
|
||||
assert completed.returncode == 0, completed.stderr + completed.stdout
|
||||
assert output.is_file(), "rebuild command succeeded without writing its receipt"
|
||||
receipt = json.loads(output.read_text(encoding="utf-8"))
|
||||
return completed, receipt, output
|
||||
|
||||
|
||||
def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
||||
tmp_path: Path,
|
||||
source_postgres: tuple[str, str],
|
||||
) -> None:
|
||||
source_container, database = source_postgres
|
||||
genesis_dump, genesis_manifest = capture_source_snapshot(
|
||||
source_container,
|
||||
database,
|
||||
tmp_path,
|
||||
stem="insert-only-genesis",
|
||||
)
|
||||
|
||||
material = replay_material()
|
||||
material_path = write_json(tmp_path / "entry-0001.json", material)
|
||||
|
|
@ -753,64 +1259,18 @@ def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
|||
|
||||
final_manifest = tmp_path / "final-manifest.jsonl"
|
||||
capture_manifest(source_container, database, final_manifest)
|
||||
ledger = {
|
||||
"artifact": rebuild.LEDGER_ARTIFACT,
|
||||
"contract_version": rebuild.LEDGER_CONTRACT_VERSION,
|
||||
"engine": rebuild._engine_hashes(),
|
||||
"genesis": {
|
||||
"dump_sha256": sha256_file(genesis_dump),
|
||||
"parity_manifest_sha256": sha256_file(genesis_manifest),
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"sequence": 1,
|
||||
"material": material_path.name,
|
||||
"sha256": sha256_file(material_path),
|
||||
"replay_material_sha256": entry.replay_material_sha256,
|
||||
}
|
||||
],
|
||||
"final_parity": {
|
||||
"manifest": final_manifest.name,
|
||||
"sha256": sha256_file(final_manifest),
|
||||
},
|
||||
}
|
||||
ledger_path = write_json(tmp_path / "ledger.json", ledger)
|
||||
output = tmp_path / "reconstruction-receipt.json"
|
||||
command = [
|
||||
sys.executable,
|
||||
str(Path(rebuild.__file__).resolve()),
|
||||
"--genesis-dump",
|
||||
str(genesis_dump),
|
||||
"--genesis-manifest",
|
||||
str(genesis_manifest),
|
||||
"--ledger",
|
||||
str(ledger_path),
|
||||
"--ledger-sha256",
|
||||
sha256_file(ledger_path),
|
||||
"--database",
|
||||
database,
|
||||
"--container-prefix",
|
||||
"teleo-genesis-ledger-live-test",
|
||||
"--tmpfs-mb",
|
||||
"256",
|
||||
"--max-target-query-ms",
|
||||
"5000",
|
||||
"--max-target-source-ratio",
|
||||
"100",
|
||||
"--output",
|
||||
str(output),
|
||||
]
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
cwd=rebuild.REPO_ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
timeout=180,
|
||||
completed, receipt, output = run_genesis_ledger_command(
|
||||
tmp_path=tmp_path,
|
||||
database=database,
|
||||
genesis_dump=genesis_dump,
|
||||
genesis_manifest=genesis_manifest,
|
||||
material_path=material_path,
|
||||
final_manifest=final_manifest,
|
||||
artifact_stem="insert-only",
|
||||
container_prefix="teleo-genesis-ledger-live-test",
|
||||
run_number=1,
|
||||
)
|
||||
|
||||
assert completed.returncode == 0, completed.stderr + completed.stdout
|
||||
receipt = json.loads(output.read_text(encoding="utf-8"))
|
||||
assert receipt["status"] == "pass"
|
||||
assert receipt["genesis_parity"]["status"] == "pass"
|
||||
assert receipt["ledger"]["entries"][0]["status"] == "pass"
|
||||
|
|
@ -831,3 +1291,131 @@ def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
|||
check=False,
|
||||
)
|
||||
assert inspect.returncode != 0
|
||||
|
||||
|
||||
def test_live_revise_strategy_rebuild_is_exact_repeatable_and_leaves_no_container(
|
||||
tmp_path: Path,
|
||||
source_postgres: tuple[str, str],
|
||||
) -> None:
|
||||
source_container, database = source_postgres
|
||||
genesis_dump, genesis_manifest = capture_source_snapshot(
|
||||
source_container,
|
||||
database,
|
||||
tmp_path,
|
||||
stem="revise-strategy-genesis",
|
||||
)
|
||||
|
||||
approved, _, approval = revise_strategy_proposal_rows()
|
||||
pre_apply = seed_proposal_and_approval(
|
||||
source_container,
|
||||
database,
|
||||
approved,
|
||||
approval,
|
||||
)
|
||||
apply_sql = rebuild.apply_engine.build_apply_sql(pre_apply["proposal"], "kb-apply")
|
||||
docker_psql(source_container, database, apply_sql, role="kb_apply")
|
||||
post_apply = json.loads(
|
||||
docker_psql(
|
||||
source_container,
|
||||
database,
|
||||
rebuild.build_proposal_readback_sql(REVISE_PROPOSAL_ID),
|
||||
).stdout.strip()
|
||||
)
|
||||
applied_proposal = post_apply["proposal"]
|
||||
proposal_envelope = applied_envelope(applied_proposal)
|
||||
source_receipt_path, receipt = rebuild.apply_engine.capture_replay_receipt(
|
||||
argparse.Namespace(
|
||||
container=source_container,
|
||||
role="kb_apply",
|
||||
host="127.0.0.1",
|
||||
db=database,
|
||||
receipt_out=str(tmp_path / "source-revise-strategy-private-receipt.json"),
|
||||
receipt_dir=None,
|
||||
),
|
||||
proposal_envelope,
|
||||
"",
|
||||
apply_sql=apply_sql,
|
||||
)
|
||||
assert source_receipt_path.is_file()
|
||||
assert stat.S_IMODE(source_receipt_path.stat().st_mode) == 0o600
|
||||
material = {
|
||||
"artifact": rebuild.MATERIAL_ARTIFACT,
|
||||
"contract_version": rebuild.MATERIAL_CONTRACT_VERSION,
|
||||
"sequence": 1,
|
||||
"approved_proposal": pre_apply["proposal"],
|
||||
"approval_snapshot": pre_apply["approval_snapshot"],
|
||||
"applied_proposal": applied_proposal,
|
||||
"replay_receipt": receipt,
|
||||
}
|
||||
material_path = write_json(tmp_path / "revise-strategy-entry-0001.json", material)
|
||||
final_manifest = tmp_path / "revise-strategy-final-manifest.jsonl"
|
||||
capture_manifest(source_container, database, final_manifest)
|
||||
|
||||
runs = [
|
||||
run_genesis_ledger_command(
|
||||
tmp_path=tmp_path,
|
||||
database=database,
|
||||
genesis_dump=genesis_dump,
|
||||
genesis_manifest=genesis_manifest,
|
||||
material_path=material_path,
|
||||
final_manifest=final_manifest,
|
||||
artifact_stem="revise-strategy",
|
||||
container_prefix="teleo-genesis-ledger-revise",
|
||||
run_number=run_number,
|
||||
)
|
||||
for run_number in (1, 2)
|
||||
]
|
||||
|
||||
for completed, reconstruction, output in runs:
|
||||
assert reconstruction["status"] == "pass"
|
||||
assert reconstruction["genesis_parity"]["status"] == "pass"
|
||||
assert reconstruction["final_parity"]["status"] == "pass"
|
||||
entry_summary = reconstruction["ledger"]["entries"][0]
|
||||
assert entry_summary["proposal_type"] == "revise_strategy"
|
||||
assert entry_summary["proposal_seed_exact"] is True
|
||||
assert entry_summary["canonical_seed_exact"] is False
|
||||
assert entry_summary["seed_exact"] is False
|
||||
assert entry_summary["guarded_apply_executed"] is True
|
||||
assert entry_summary["mutating_prestate_captured"] is True
|
||||
assert entry_summary["mutating_delta_validated"] is True
|
||||
assert entry_summary["mutating_poststate_normalized"] is True
|
||||
assert entry_summary["proposal_exact"] is True
|
||||
assert entry_summary["canonical_rows_exact"] is True
|
||||
assert reconstruction["cleanup"]["container_absent"] is True
|
||||
assert reconstruction["safety"]["network_access_available_to_container"] is False
|
||||
assert reconstruction["safety"]["private_replay_material_emitted"] is False
|
||||
assert PRIVATE_MARKER not in completed.stdout
|
||||
assert PRIVATE_MARKER not in output.read_text(encoding="utf-8")
|
||||
assert stat.S_IMODE(output.stat().st_mode) == 0o600
|
||||
|
||||
container_name = reconstruction["container"]["name"]
|
||||
inspect = subprocess.run(
|
||||
["docker", "container", "inspect", container_name],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
assert inspect.returncode != 0
|
||||
labeled = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"ps",
|
||||
"-aq",
|
||||
"--filter",
|
||||
f"label={rebuild.canonical_rebuild.CANARY_LABEL}={container_name}",
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
assert labeled.returncode == 0
|
||||
assert labeled.stdout.strip() == ""
|
||||
|
||||
first = runs[0][1]
|
||||
second = runs[1][1]
|
||||
assert first["inputs"] == second["inputs"]
|
||||
assert first["ledger"]["entries"][0]["material_sha256"] == second["ledger"]["entries"][0]["material_sha256"]
|
||||
assert (
|
||||
first["ledger"]["entries"][0]["replay_material_sha256"]
|
||||
== second["ledger"]["entries"][0]["replay_material_sha256"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -116,6 +116,37 @@ def test_invalid_source_hash_refuses_to_prepare_a_child() -> None:
|
|||
stage.prepare_normalized_child(parent)
|
||||
|
||||
|
||||
def test_prepare_normalized_child_preserves_replayable_ingestion_manifest() -> None:
|
||||
parent = _parent()
|
||||
parent["payload"]["ingestion_manifest"] = {
|
||||
"artifact_sha256": "a" * 64,
|
||||
"extractor": {"name": "deterministic_fixture_replay", "version": "2"},
|
||||
"replay_identity_sha256": "b" * 64,
|
||||
"segments": [
|
||||
{
|
||||
"id": "message-1",
|
||||
"locator": "telegram://chat/1/message/1",
|
||||
"content_sha256": "c" * 64,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
child = stage.prepare_normalized_child(parent)
|
||||
|
||||
assert (
|
||||
child["payload"]["apply_payload"]["normalization_manifest"]["ingestion_manifest"]
|
||||
== parent["payload"]["ingestion_manifest"]
|
||||
)
|
||||
|
||||
|
||||
def test_prepare_normalized_child_rejects_non_object_ingestion_manifest() -> None:
|
||||
parent = _parent()
|
||||
parent["payload"]["ingestion_manifest"] = "not-an-object"
|
||||
|
||||
with pytest.raises(stage.bound.CheckpointError, match="ingestion_manifest must be an object"):
|
||||
stage.prepare_normalized_child(parent)
|
||||
|
||||
|
||||
def test_stage_sql_validates_exact_pending_row_before_commit_and_never_writes_public() -> None:
|
||||
child = stage.prepare_normalized_child(_parent())
|
||||
sql = stage.build_stage_sql(child)
|
||||
|
|
|
|||
Loading…
Reference in a new issue