Merge remote-tracking branch 'origin/main' into codex/leo-proposal-apply-lifecycle-20260715
This commit is contained in:
commit
d089fd9804
37 changed files with 7802 additions and 518 deletions
|
|
@ -148,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:
|
||||
|
|
@ -206,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:
|
||||
|
|
@ -272,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
|
||||
|
|
|
|||
212
docs/leo-reproducible-identity.md
Normal file
212
docs/leo-reproducible-identity.md
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# Reproducible Leo identity
|
||||
|
||||
## Definition of Working
|
||||
|
||||
- **Working target:** generate one pinned Leo identity manifest, compile a
|
||||
disposable profile, answer the fixed identity query in a fresh process,
|
||||
restart in a second process, and reject any drift before an answer.
|
||||
- **Operator path:** run
|
||||
`python -m scripts.run_leo_identity_reconstruction_canary` with the committed
|
||||
identity fixtures from a clean checkout.
|
||||
- **Done means:** the T2 receipt reports two distinct stopped processes with the
|
||||
same manifest, identity inputs, query, answer hash, and output provenance;
|
||||
the second process starts from a newly compiled empty profile; the drift
|
||||
attempt exits before answering; every process group and temporary profile is
|
||||
removed.
|
||||
- **Not done:** a hand-edited `SOUL.md`, a manifest self-hash, unit tests without
|
||||
process restart, or prior VPS restart evidence that did not reconstruct from
|
||||
this manifest.
|
||||
- **Required tier:** `T2_runtime`. T3 VPS restart/readback is a post-merge
|
||||
graduation target.
|
||||
|
||||
## Identity input inventory
|
||||
|
||||
`GatewayRunner` and the surrounding Hermes profile can change an answer through
|
||||
the following surfaces. The manifest either pins each surface or explicitly
|
||||
keeps it outside identity authority.
|
||||
|
||||
| Surface | Binding | Authority |
|
||||
| --- | --- | --- |
|
||||
| Model provider, route, limits, and reasoning settings | secret-free semantic config hash | runtime input; the actual model remains a per-turn receipt |
|
||||
| Hosted model weights | provider-managed, explicitly not locally hashable | never claimed as a local hash |
|
||||
| Hermes and Teleo code | clean Git commits plus executable source-tree hashes | runtime input |
|
||||
| Python ABI and imported runtime packages | exact implementation/version and curated package versions | runtime input |
|
||||
| Skills, plugins, and database tools | content hashes | runtime input |
|
||||
| Gateway/tool permissions | strict allowlisted config hash plus exact no-send/read-only policy | runtime input; this local compiler does not claim an authorizer readback |
|
||||
| Static constitutional rules | committed source path, byte hash, and semantic hash | static authority |
|
||||
| Database snapshot version | database/user/system identity plus content, row, structure, and capture-provenance hashes; only WAL position is excluded | T2 pins a noncanonical fixture; it cannot grant canonical authority |
|
||||
| Database-derived identity rows | typed table/row/kind/rank/evidence bindings plus source mode | synthetic fixtures are explicitly noncanonical; T3 requires independently verified database-exporter output |
|
||||
| `SOUL.md` and `identity.json` | deterministic compiled-view hashes | generated views, never authorities |
|
||||
| Sessions, state, and memory | excluded from the identity input hash and labeled temporary | continuity only; never evidence |
|
||||
|
||||
The disposable profile is compiled from a strict non-secret configuration
|
||||
allowlist; unrecognized transport/credential fields are not copied. Credential
|
||||
values are omitted rather than hashed. Rotating a bot token changes the broad
|
||||
operational behavior observation but does not change Leo's identity.
|
||||
Changing a non-secret permission, model setting, skill, code file, database
|
||||
fingerprint, constitutional rule, database identity row, or compiled view does
|
||||
change a pinned input and fails closed.
|
||||
|
||||
### Current `GatewayRunner` consumption map
|
||||
|
||||
The T2 compiler contract was checked against the current live runtime source.
|
||||
This inventory defines what the post-merge T3 receipt must observe rather than
|
||||
silently assuming that a profile file is the whole prompt:
|
||||
|
||||
- Startup resolves `-p leoclean` to `HERMES_HOME`, then loads profile and project
|
||||
environment files before `config.yaml`; environment expansion, legacy
|
||||
`gateway.json`, and defaults can change the effective configuration. The T2
|
||||
compiler therefore enforces an exact top-level profile allowlist (including
|
||||
rejection of dangling symlinks) rather than relying on a filename denylist.
|
||||
- Routing consumes the requested default model, the top-level
|
||||
`smart_model_routing` switch, provider endpoints/defaults, auth pool choice,
|
||||
reasoning settings, token/turn limits, and fallbacks. The pinned top-level
|
||||
routing switch must be a boolean; arbitrary nested routing data is rejected.
|
||||
Credentials are runtime capability, not identity, and their values are never
|
||||
retained.
|
||||
- Prompt construction consumes generated `SOUL.md`, the gateway/agent system
|
||||
message, persistent `MEMORY.md`/`USER.md`, compiled skills, project-context
|
||||
files, current time/timezone, and the effective model/provider. T2 requires
|
||||
memory and project context absent; T3 records the effective system-prompt and
|
||||
compiled-skills-prompt hashes.
|
||||
- Plugin discovery can include profile plugins, optional project plugins, and
|
||||
installed entry points. The live database-context hook can inject a
|
||||
query-bound contract before the model and can reject or replace the reply
|
||||
afterward, so T3 retains plugin registry, contract, retrieval, and delivered
|
||||
response hashes.
|
||||
- Effective tools come from platform toolsets and the runtime registry, not a
|
||||
descriptive fixture field. T3 must read back exact tool schemas, prove the
|
||||
send tool absent, and keep terminal restricted to the clone-bound read-only
|
||||
wrapper.
|
||||
- Authorization consumes chat/user allowlists and pairing-store state. T2 starts
|
||||
with pairing absent; T3 records the fixed synthetic source and the actual
|
||||
authorization decision without exposing IDs or tokens.
|
||||
- Session keys consume platform/chat/user/thread/topic fields. Auto-skill,
|
||||
reply/media/file context, persisted transcripts, and a reused system prompt
|
||||
can all change a turn. Verification therefore happens before every child
|
||||
starts, and T3 binds session key, persisted session ID, message-context
|
||||
absence, and prompt hashes.
|
||||
|
||||
The committed T2 database/identity inputs are synthetic fixtures and the
|
||||
compiled view labels them `synthetic_noncanonical_fixture`; they do not stand
|
||||
in for approved production rows. This T2 schema never grants canonical
|
||||
authority from caller-supplied or merely self-hashed JSON. T3 must use output
|
||||
that is independently verified by the database-owning same-transaction exporter
|
||||
and bound to the database fingerprint, database user, system identifier, and
|
||||
row digest.
|
||||
|
||||
Every pinned source tree and profile component is structurally revalidated:
|
||||
the verifier recomputes its file count, total bytes, sorted unique paths, and
|
||||
canonical content hash, and rejects missing files, unreadable entries, or
|
||||
symlinks. Rehashing forged structural metadata cannot make it authoritative.
|
||||
|
||||
The current live Hermes checkout is not clean, so its Git SHA alone is not a
|
||||
reproducible source bundle. This T2 receipt intentionally uses the committed
|
||||
clean local runtime and the committed database/identity snapshot fixture. It
|
||||
does not claim to reconstruct the current dirty VPS runtime. T3 must run only
|
||||
after the merged identity code is deployed and must bind the deployed clean
|
||||
content (or a content-addressed dirty-source bundle if the live checkout has not
|
||||
yet been repaired).
|
||||
|
||||
## Commands
|
||||
|
||||
The complete canary is the preferred operator command:
|
||||
|
||||
```bash
|
||||
python -m scripts.run_leo_identity_reconstruction_canary \
|
||||
--profile-template fixtures/working-leo/leo-identity-v1/profile-template \
|
||||
--hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \
|
||||
--deployment-root . \
|
||||
--source-root . \
|
||||
--database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \
|
||||
--constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \
|
||||
--database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \
|
||||
--output docs/reports/leo-working-state-20260709/leo-reproducible-identity-t2-current.json
|
||||
```
|
||||
|
||||
The negative lifecycle is separately runnable. It proves drift is caught before
|
||||
the second process starts:
|
||||
|
||||
```bash
|
||||
python -m scripts.run_leo_identity_reconstruction_canary \
|
||||
--profile-template fixtures/working-leo/leo-identity-v1/profile-template \
|
||||
--hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \
|
||||
--deployment-root . \
|
||||
--source-root . \
|
||||
--database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \
|
||||
--constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \
|
||||
--database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \
|
||||
--inject-drift-before-restart \
|
||||
--output /tmp/leo-identity-drift-rejection.json
|
||||
```
|
||||
|
||||
For inspection, the lifecycle can be decomposed into explicit manifest and
|
||||
compile commands:
|
||||
|
||||
```bash
|
||||
python -m scripts.leo_behavior_manifest \
|
||||
--profile fixtures/working-leo/leo-identity-v1/profile-template \
|
||||
--hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \
|
||||
--deployment-root . \
|
||||
--output /tmp/leo-behavior.json
|
||||
|
||||
python -m scripts.leo_identity_manifest generate \
|
||||
--behavior-manifest /tmp/leo-behavior.json \
|
||||
--database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \
|
||||
--constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \
|
||||
--database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \
|
||||
--source-root . \
|
||||
--output /tmp/leo-identity-manifest.json
|
||||
|
||||
python -m scripts.leo_identity_profile compile \
|
||||
--manifest /tmp/leo-identity-manifest.json \
|
||||
--source-root . \
|
||||
--profile-template fixtures/working-leo/leo-identity-v1/profile-template \
|
||||
--profile /tmp/leo-disposable-profile \
|
||||
--hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \
|
||||
--deployment-root .
|
||||
```
|
||||
|
||||
All commands require a clean Git worktree because a commit plus an unretained
|
||||
dirty source tree is not reproducible.
|
||||
|
||||
Child processes execute the real interpreter and temporary-profile paths only
|
||||
in process-local memory. Retained receipts persist a repo-relative
|
||||
`logical_command`, the stable `<python>` and `<temporary-profile>` placeholders,
|
||||
and an explicit `profile_role`; they never serialize the checkout, interpreter,
|
||||
or temporary directory path.
|
||||
|
||||
## State inventory and transitions
|
||||
|
||||
| State | Meaning | Next valid transition |
|
||||
| --- | --- | --- |
|
||||
| `inputs_unverified` | source paths exist but are not yet bound | validate clean Git, runtime, DB, rules, rows, and permissions |
|
||||
| `manifest_pinned` | every material identity input has a stable hash | compile deterministic views |
|
||||
| `profile_compiled` | static profile copied and generated views match the manifest | verify immediately before start |
|
||||
| `runtime_verified` | runtime/code/view hashes match and session state is non-authoritative | answer fixed query |
|
||||
| `stopped_cleanly` | child and process group are absent | compile a new empty profile from the manifest |
|
||||
| `restart_reproduced` | child from the newly compiled profile has the same identity/query/provenance inputs | retain receipt and clean every profile |
|
||||
| `drift_detected` | any input or generated view differs | fail closed; no answer and no next start |
|
||||
|
||||
The irreversible boundary is not a database or production mutation: this T2
|
||||
canary is a local compiler/process runtime, no-send, database-read-only, and
|
||||
disposable. The successful-restart receipt reaches `T2_runtime`; the standalone
|
||||
pre-restart drift receipt is a passing negative component but reports
|
||||
`T1_model` and `goal_tier_satisfied=false`. Neither proves the live VPS
|
||||
`GatewayRunner`, Telegram delivery, hosted-model behavior, production database
|
||||
state, or T3 restart recovery.
|
||||
|
||||
Here `T2_runtime` uses the required Capability Tier Proof vocabulary: local
|
||||
runtime behavior with restart. It does not imply Hermes/GatewayRunner or a
|
||||
hosted model; those exclusions are explicit in the receipt's `runtime_variant`,
|
||||
`tier_basis`, and `claim_ceiling`.
|
||||
|
||||
## T3 graduation
|
||||
|
||||
After merge and rollback proof, extend the schema with independent verification
|
||||
of the database-owning same-transaction exporter, generate the manifest from
|
||||
that live read-only canonical capture and deployed clean commits, compile a
|
||||
disposable VPS profile, invoke the real no-send `GatewayRunner`, terminate that
|
||||
child, open a new child from the same manifest, and retain model-call, DB-read,
|
||||
service, cleanup, and drift-negative readbacks. Do not replace the production
|
||||
profile during that graduation.
|
||||
|
|
@ -0,0 +1,332 @@
|
|||
{
|
||||
"actual_hosted_model_call_performed": false,
|
||||
"behavior_observation_before_compile": {
|
||||
"behavior_sha256": "693a5d542e3d817c1c77c8aa3180f14d5f98ec1e71f5e4313685b4da70b489aa",
|
||||
"identity_runtime_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"source_commit": "b238fd6fc2636ed3582e14900774014c52732698"
|
||||
},
|
||||
"claim_ceiling": "Local first-process and pre-restart drift-refusal component only; no successful restart proof, GatewayRunner, hosted model, production database, VPS, or T3 claim.",
|
||||
"cleanup": {
|
||||
"no_profile_retained": true,
|
||||
"temporary_root_removed": true
|
||||
},
|
||||
"compile": {
|
||||
"compiled_view_sha256": {
|
||||
"SOUL.md": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"identity.json": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6"
|
||||
},
|
||||
"generated_views_are_authority": false,
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"nonauthoritative_inputs_omitted": {
|
||||
".cursorrules": true,
|
||||
".hermes.md": true,
|
||||
".skills_prompt_snapshot.json": true,
|
||||
"AGENTS.md": true,
|
||||
"CLAUDE.md": true,
|
||||
"HERMES.md": true,
|
||||
"MEMORY.md": true,
|
||||
"USER.md": true,
|
||||
"memories": true,
|
||||
"platforms/pairing": true
|
||||
},
|
||||
"profile_static_entries": [
|
||||
"config.yaml",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin"
|
||||
],
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"schema": "livingip.leoIdentityCompileReceipt.v1",
|
||||
"session_directories_started_empty": true,
|
||||
"status": "pass"
|
||||
},
|
||||
"compiled_profile_before_query": {
|
||||
"behavior_sha256": "a62b26015a7c9506f0296404be150608a816a2d545f471f7960bd27324d6849c",
|
||||
"identity_runtime_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159"
|
||||
},
|
||||
"current_tier": "T1_model",
|
||||
"drift_target": "compiled SOUL.md generated view",
|
||||
"errors": [],
|
||||
"first_start": {
|
||||
"actual_argv_persisted": false,
|
||||
"command_serialization": "logical_redacted_v1",
|
||||
"launcher_pid": 80040,
|
||||
"logical_command": [
|
||||
"<python>",
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
"<temporary-profile>",
|
||||
"--source-root",
|
||||
".",
|
||||
"--hermes-root",
|
||||
"fixtures/working-leo/leo-identity-v1/hermes-runtime",
|
||||
"--deployment-root",
|
||||
".",
|
||||
"--query",
|
||||
"What defines Leo's identity, and what is temporary?"
|
||||
],
|
||||
"orphan_cleanup_required": false,
|
||||
"process_group_absent_after_wait": true,
|
||||
"profile_role": "first_start",
|
||||
"returncode": 0,
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout": {
|
||||
"answer": "Leo is defined by 3 pinned static constitutional rules and 5 active database-derived identity rows from a synthetic noncanonical fixture at fingerprint 1111111111111111111111111111111111111111111111111111111111111111. SOUL.md is a generated view; runtime/session memory is temporary, noncanonical, and cannot be evidence.",
|
||||
"answer_sha256": "e0a21e1fc5357a2f358b4e96c52e971f3addee2468e44748d898adfd62dafa25",
|
||||
"authorization": {
|
||||
"authorization_decision_observed": false,
|
||||
"claim": "local_policy_binding_not_an_authorizer_readback",
|
||||
"declared_runtime_policy": {
|
||||
"allowed_tools": [
|
||||
"identity_readback"
|
||||
],
|
||||
"database_write_enabled": false,
|
||||
"network_send_enabled": false
|
||||
}
|
||||
},
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"output_provenance": {
|
||||
"actual_model_call_performed": false,
|
||||
"compiled_identity_sha256": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6",
|
||||
"compiled_soul_sha256": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"database_authority": "synthetic_noncanonical_fixture",
|
||||
"database_canonical_authority_granted": false,
|
||||
"database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"database_identity_sha256": "70aaa7287cc41b52a3c902cf2e1b90fa4d84a5ee0a61e10c642f556fcc9304c3",
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"static_constitution_sha256": "b576d5cee08530a95e0b196a3296f4743b70d94bcadffaa36544dd6bf1387f8a"
|
||||
},
|
||||
"process_id": 80040,
|
||||
"query": "What defines Leo's identity, and what is temporary?",
|
||||
"query_sha256": "13704c2c9ae999e3f9b3f1c9c5572d062cd7fe49903c2317fa85aaa39490af66",
|
||||
"runtime_instance_id": "493f274a5f0340a2aa675f13b8e5b4af",
|
||||
"schema": "livingip.leoIdentityQueryReceipt.v1",
|
||||
"session": {
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_output_provenance": false,
|
||||
"may_be_used_as_evidence": false,
|
||||
"record_sha256": "56b6a2ea25657a87dc90dc19f49bece0ac62241f5814732ed80ef4f34d426d84"
|
||||
},
|
||||
"started_at_utc": "2026-07-15T02:46:34.316819+00:00",
|
||||
"status": "pass"
|
||||
},
|
||||
"terminated_with": null,
|
||||
"timed_out": false
|
||||
},
|
||||
"fixed_query": "What defines Leo's identity, and what is temporary?",
|
||||
"fixed_query_sha256": "13704c2c9ae999e3f9b3f1c9c5572d062cd7fe49903c2317fa85aaa39490af66",
|
||||
"gates": {
|
||||
"drift_detected_before_restart": true,
|
||||
"drift_process_had_no_orphan_cleanup": true,
|
||||
"drift_process_stopped": true,
|
||||
"drift_returned_no_answer": true,
|
||||
"first_process_stopped": true,
|
||||
"first_start_passed": true,
|
||||
"second_start_not_attempted": true
|
||||
},
|
||||
"generated_at_utc": "2026-07-15T02:46:31.831631+00:00",
|
||||
"goal_tier_satisfied": false,
|
||||
"manifest": {
|
||||
"compiled_views": {
|
||||
"SOUL.md": {
|
||||
"generated_from_identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"role": "generated_view_not_authority",
|
||||
"sha256": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a"
|
||||
},
|
||||
"identity.json": {
|
||||
"generated_from_identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"role": "generated_view_not_authority",
|
||||
"sha256": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6"
|
||||
}
|
||||
},
|
||||
"identity_inputs": {
|
||||
"canonical_database": {
|
||||
"authority": "synthetic_noncanonical_fixture",
|
||||
"canonical_authority_granted": false,
|
||||
"database": "leo_identity_fixture",
|
||||
"database_user": "leo_identity_reader",
|
||||
"fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"source": {
|
||||
"record_count": 7,
|
||||
"repo_path": "fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json",
|
||||
"semantic_sha256": "c5bbf346613270a5e3b17f604f69306ed42f545d4f227d784ec0dcb3d675dcb2"
|
||||
},
|
||||
"structure_sha256": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"system_identifier": "7649789040005668902",
|
||||
"table_count": 7,
|
||||
"table_rows_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"total_rows": 7
|
||||
},
|
||||
"gatewayrunner_execution_contract": {
|
||||
"fixed_message_context": {
|
||||
"auto_skill": null,
|
||||
"media_or_file_context": null,
|
||||
"reply_context": null
|
||||
},
|
||||
"profile_surfaces": [
|
||||
"config.yaml",
|
||||
"generated SOUL.md",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin tools"
|
||||
],
|
||||
"required_absent_or_empty": {
|
||||
"generated_skill_prompt_cache": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"pairing_store": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"persistent_memory": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"prior_sessions_at_first_start": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"project_context": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945"
|
||||
},
|
||||
"required_t3_turn_receipt_fields": [
|
||||
"runner_class",
|
||||
"authorization_decision",
|
||||
"effective_system_prompt_sha256",
|
||||
"compiled_skills_prompt_sha256",
|
||||
"tool_registry_schema_sha256",
|
||||
"plugin_registry_sha256",
|
||||
"selected_model",
|
||||
"selected_provider",
|
||||
"api_mode",
|
||||
"base_url_host",
|
||||
"database_retrieval_receipt",
|
||||
"session_key_sha256",
|
||||
"persisted_session_id_sha256"
|
||||
]
|
||||
},
|
||||
"identity_name": "Leo",
|
||||
"identity_sources": {
|
||||
"database_derived_identity": {
|
||||
"authority": "synthetic_noncanonical_fixture",
|
||||
"content_sha256": "9de2dfa37529b23ba277348f3d910e967551e490a9b4d2be637bf45bb2aa7dde",
|
||||
"database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"provenance": {
|
||||
"authority": "synthetic_noncanonical_fixture",
|
||||
"canonical_authority_granted": false,
|
||||
"export_receipt_sha256": null,
|
||||
"mode": "synthetic_noncanonical_fixture",
|
||||
"same_transaction_as_fingerprint": false
|
||||
},
|
||||
"record_count": 5,
|
||||
"repo_path": "fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json",
|
||||
"semantic_sha256": "70aaa7287cc41b52a3c902cf2e1b90fa4d84a5ee0a61e10c642f556fcc9304c3"
|
||||
},
|
||||
"static_constitution": {
|
||||
"authority": "pinned_static_source",
|
||||
"content_sha256": "b5477e778a2be0abba783390dac2e04ed199b8ce5679fc324270f808d46543f1",
|
||||
"record_count": 3,
|
||||
"repo_path": "fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json",
|
||||
"semantic_sha256": "b576d5cee08530a95e0b196a3296f4743b70d94bcadffaa36544dd6bf1387f8a"
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"actual_model_required_in_turn_receipt": true,
|
||||
"default_model": "leo-identity-readback-v1",
|
||||
"gateway_smart_model_routing": null,
|
||||
"hosted_weights": "external_provider_managed_not_locally_hashable",
|
||||
"model_section_sha256": "90b3943ace9ff83b1711002fda8dc9736448e8d3c1c58a17ac90608f4ce58756",
|
||||
"provider": "fixture-local",
|
||||
"smart_routing": false,
|
||||
"smart_routing_model": null
|
||||
},
|
||||
"permissions": {
|
||||
"authorization_decision_required_in_runtime_receipt": true,
|
||||
"gateway_permissions_sha256": "eac17ada1d02b5413183587d12fc265538479de00709ed8c275e9045cd720290",
|
||||
"identity_config_sha256": "e6df9b65bab148ea8502555bbc260df66b4785e7dd527cfd15d2f0a48c2e8b3e",
|
||||
"runtime_policy": {
|
||||
"allowed_tools": [
|
||||
"identity_readback"
|
||||
],
|
||||
"database_write_enabled": false,
|
||||
"network_send_enabled": false
|
||||
},
|
||||
"tool_permissions_sha256": "9bb2cec2f65d43efb597a72bdced44076c25aac292f2fccbc4c448b6e7fd3e16"
|
||||
},
|
||||
"runtime": {
|
||||
"hermes_git_head": "b238fd6fc2636ed3582e14900774014c52732698",
|
||||
"hermes_source_sha256": "dcf8109051e0b69cafe2e8dd328ff501211b62ea19725416c3781bbdd594d028",
|
||||
"identity_runtime_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"python": {
|
||||
"abi_tag": "cpython-311",
|
||||
"implementation": "CPython",
|
||||
"python_version": "3.11.15",
|
||||
"runtime_distributions": {
|
||||
"PyYAML": "6.0.3"
|
||||
},
|
||||
"runtime_distributions_sha256": "57a91bb880a01800903c1604b6eec1419514864ebd6a0644121920da15a8f165"
|
||||
},
|
||||
"teleo_git_head": "b238fd6fc2636ed3582e14900774014c52732698",
|
||||
"teleo_source_sha256": "2bd2e659eafb0ac0823f7f51c4296b2eb500b6f74469b3ac5f5287a5d4810aea"
|
||||
},
|
||||
"session_boundary": {
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_identity_inputs": false,
|
||||
"may_be_used_as_evidence": false,
|
||||
"restart_policy": "fresh_process_with_session_state_outside_identity_authority"
|
||||
},
|
||||
"skills": {
|
||||
"content_sha256": "d3f449ddcd7c88238dc88c6233cf2130875f51bd2e0911b2ce477b7c602ae90f",
|
||||
"file_count": 1
|
||||
},
|
||||
"source_commit": "b238fd6fc2636ed3582e14900774014c52732698"
|
||||
},
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"schema": "livingip.leoIdentityManifest.v1"
|
||||
},
|
||||
"mode": "drift_before_restart",
|
||||
"pre_restart_drift": {
|
||||
"actual_argv_persisted": false,
|
||||
"command_serialization": "logical_redacted_v1",
|
||||
"launcher_pid": 80246,
|
||||
"logical_command": [
|
||||
"<python>",
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
"<temporary-profile>",
|
||||
"--source-root",
|
||||
".",
|
||||
"--hermes-root",
|
||||
"fixtures/working-leo/leo-identity-v1/hermes-runtime",
|
||||
"--deployment-root",
|
||||
".",
|
||||
"--query",
|
||||
"What defines Leo's identity, and what is temporary?"
|
||||
],
|
||||
"orphan_cleanup_required": false,
|
||||
"process_group_absent_after_wait": true,
|
||||
"profile_role": "pre_restart_drift",
|
||||
"returncode": 3,
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout": {
|
||||
"error": "identity_drift",
|
||||
"message": "compiled view drift detected: SOUL.md",
|
||||
"status": "error"
|
||||
},
|
||||
"terminated_with": null,
|
||||
"timed_out": false
|
||||
},
|
||||
"production_database_contacted": false,
|
||||
"production_profile_replaced": false,
|
||||
"receipt_sha256": "50278cc740d0427316f73a78abad840b3e3b128a516f464001d42e632e19c2a8",
|
||||
"required_tier": "T2_runtime",
|
||||
"runtime_component_proven": "first_local_process_plus_pre_restart_drift_refusal",
|
||||
"runtime_variant": "local_deterministic_identity_compiler_readback",
|
||||
"schema": "livingip.leoIdentityReconstructionReceipt.v1",
|
||||
"second_start_attempted": false,
|
||||
"session_boundary_readback": {
|
||||
"full_behavior_changed_after_session": true,
|
||||
"identity_runtime_unchanged_after_session": true,
|
||||
"session_classification": "temporary_noncanonical",
|
||||
"session_file_count": 1,
|
||||
"session_used_as_output_provenance": false
|
||||
},
|
||||
"status": "pass",
|
||||
"telegram_message_sent": false,
|
||||
"tier_basis": "This negative component does not complete the required restart path, so it remains below T2_runtime."
|
||||
}
|
||||
|
|
@ -0,0 +1,478 @@
|
|||
{
|
||||
"actual_hosted_model_call_performed": false,
|
||||
"behavior_observation_before_compile": {
|
||||
"behavior_sha256": "693a5d542e3d817c1c77c8aa3180f14d5f98ec1e71f5e4313685b4da70b489aa",
|
||||
"identity_runtime_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"source_commit": "b238fd6fc2636ed3582e14900774014c52732698"
|
||||
},
|
||||
"claim_ceiling": "Local disposable identity compilation, fresh-profile reconstruction, and cold-process restart only; not a live GatewayRunner, hosted-model, Telegram, production database, VPS restart, or T3 proof.",
|
||||
"cleanup": {
|
||||
"no_profile_retained": true,
|
||||
"temporary_root_removed": true
|
||||
},
|
||||
"compile": {
|
||||
"compiled_view_sha256": {
|
||||
"SOUL.md": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"identity.json": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6"
|
||||
},
|
||||
"generated_views_are_authority": false,
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"nonauthoritative_inputs_omitted": {
|
||||
".cursorrules": true,
|
||||
".hermes.md": true,
|
||||
".skills_prompt_snapshot.json": true,
|
||||
"AGENTS.md": true,
|
||||
"CLAUDE.md": true,
|
||||
"HERMES.md": true,
|
||||
"MEMORY.md": true,
|
||||
"USER.md": true,
|
||||
"memories": true,
|
||||
"platforms/pairing": true
|
||||
},
|
||||
"profile_static_entries": [
|
||||
"config.yaml",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin"
|
||||
],
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"schema": "livingip.leoIdentityCompileReceipt.v1",
|
||||
"session_directories_started_empty": true,
|
||||
"status": "pass"
|
||||
},
|
||||
"compiled_profile_before_query": {
|
||||
"behavior_sha256": "a62b26015a7c9506f0296404be150608a816a2d545f471f7960bd27324d6849c",
|
||||
"identity_runtime_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159"
|
||||
},
|
||||
"current_tier": "T2_runtime",
|
||||
"drift_compile": {
|
||||
"compiled_view_sha256": {
|
||||
"SOUL.md": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"identity.json": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6"
|
||||
},
|
||||
"generated_views_are_authority": false,
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"nonauthoritative_inputs_omitted": {
|
||||
".cursorrules": true,
|
||||
".hermes.md": true,
|
||||
".skills_prompt_snapshot.json": true,
|
||||
"AGENTS.md": true,
|
||||
"CLAUDE.md": true,
|
||||
"HERMES.md": true,
|
||||
"MEMORY.md": true,
|
||||
"USER.md": true,
|
||||
"memories": true,
|
||||
"platforms/pairing": true
|
||||
},
|
||||
"profile_static_entries": [
|
||||
"config.yaml",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin"
|
||||
],
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"schema": "livingip.leoIdentityCompileReceipt.v1",
|
||||
"session_directories_started_empty": true,
|
||||
"status": "pass"
|
||||
},
|
||||
"drift_negative": {
|
||||
"actual_argv_persisted": false,
|
||||
"answer_returned": false,
|
||||
"command_serialization": "logical_redacted_v1",
|
||||
"fail_closed": true,
|
||||
"launcher_pid": 80851,
|
||||
"logical_command": [
|
||||
"<python>",
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
"<temporary-profile>",
|
||||
"--source-root",
|
||||
".",
|
||||
"--hermes-root",
|
||||
"fixtures/working-leo/leo-identity-v1/hermes-runtime",
|
||||
"--deployment-root",
|
||||
".",
|
||||
"--query",
|
||||
"What defines Leo's identity, and what is temporary?"
|
||||
],
|
||||
"orphan_cleanup_required": false,
|
||||
"process_group_absent_after_wait": true,
|
||||
"profile_role": "drift_negative",
|
||||
"returncode": 3,
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout": {
|
||||
"error": "identity_drift",
|
||||
"message": "compiled view drift detected: SOUL.md",
|
||||
"status": "error"
|
||||
},
|
||||
"terminated_with": null,
|
||||
"timed_out": false
|
||||
},
|
||||
"drift_target": "compiled SOUL.md generated view",
|
||||
"errors": [],
|
||||
"first_start": {
|
||||
"actual_argv_persisted": false,
|
||||
"command_serialization": "logical_redacted_v1",
|
||||
"launcher_pid": 80054,
|
||||
"logical_command": [
|
||||
"<python>",
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
"<temporary-profile>",
|
||||
"--source-root",
|
||||
".",
|
||||
"--hermes-root",
|
||||
"fixtures/working-leo/leo-identity-v1/hermes-runtime",
|
||||
"--deployment-root",
|
||||
".",
|
||||
"--query",
|
||||
"What defines Leo's identity, and what is temporary?"
|
||||
],
|
||||
"orphan_cleanup_required": false,
|
||||
"process_group_absent_after_wait": true,
|
||||
"profile_role": "first_start",
|
||||
"returncode": 0,
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout": {
|
||||
"answer": "Leo is defined by 3 pinned static constitutional rules and 5 active database-derived identity rows from a synthetic noncanonical fixture at fingerprint 1111111111111111111111111111111111111111111111111111111111111111. SOUL.md is a generated view; runtime/session memory is temporary, noncanonical, and cannot be evidence.",
|
||||
"answer_sha256": "e0a21e1fc5357a2f358b4e96c52e971f3addee2468e44748d898adfd62dafa25",
|
||||
"authorization": {
|
||||
"authorization_decision_observed": false,
|
||||
"claim": "local_policy_binding_not_an_authorizer_readback",
|
||||
"declared_runtime_policy": {
|
||||
"allowed_tools": [
|
||||
"identity_readback"
|
||||
],
|
||||
"database_write_enabled": false,
|
||||
"network_send_enabled": false
|
||||
}
|
||||
},
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"output_provenance": {
|
||||
"actual_model_call_performed": false,
|
||||
"compiled_identity_sha256": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6",
|
||||
"compiled_soul_sha256": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"database_authority": "synthetic_noncanonical_fixture",
|
||||
"database_canonical_authority_granted": false,
|
||||
"database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"database_identity_sha256": "70aaa7287cc41b52a3c902cf2e1b90fa4d84a5ee0a61e10c642f556fcc9304c3",
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"static_constitution_sha256": "b576d5cee08530a95e0b196a3296f4743b70d94bcadffaa36544dd6bf1387f8a"
|
||||
},
|
||||
"process_id": 80054,
|
||||
"query": "What defines Leo's identity, and what is temporary?",
|
||||
"query_sha256": "13704c2c9ae999e3f9b3f1c9c5572d062cd7fe49903c2317fa85aaa39490af66",
|
||||
"runtime_instance_id": "4bbcaeeeb5654b69adabed33b91cf3e4",
|
||||
"schema": "livingip.leoIdentityQueryReceipt.v1",
|
||||
"session": {
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_output_provenance": false,
|
||||
"may_be_used_as_evidence": false,
|
||||
"record_sha256": "56b6a2ea25657a87dc90dc19f49bece0ac62241f5814732ed80ef4f34d426d84"
|
||||
},
|
||||
"started_at_utc": "2026-07-15T02:46:34.391880+00:00",
|
||||
"status": "pass"
|
||||
},
|
||||
"terminated_with": null,
|
||||
"timed_out": false
|
||||
},
|
||||
"fixed_query": "What defines Leo's identity, and what is temporary?",
|
||||
"fixed_query_sha256": "13704c2c9ae999e3f9b3f1c9c5572d062cd7fe49903c2317fa85aaa39490af66",
|
||||
"gates": {
|
||||
"answer_and_provenance_explainable": true,
|
||||
"drift_failed_closed": true,
|
||||
"drift_process_had_no_orphan_cleanup": true,
|
||||
"drift_process_stopped": true,
|
||||
"drift_profile_started_without_sessions": true,
|
||||
"first_process_stopped": true,
|
||||
"first_start_passed": true,
|
||||
"identity_inputs_reproduced": true,
|
||||
"manifest_generated": true,
|
||||
"manifest_reproduced": true,
|
||||
"pre_restart_verification_passed": true,
|
||||
"query_reproduced": true,
|
||||
"restart_passed": true,
|
||||
"restart_process_is_distinct": true,
|
||||
"restart_process_stopped": true,
|
||||
"restart_profile_recompiled_from_manifest": true,
|
||||
"restart_profile_started_without_sessions": true,
|
||||
"session_excluded_from_identity": true,
|
||||
"views_compiled_and_bound": true
|
||||
},
|
||||
"generated_at_utc": "2026-07-15T02:46:31.346851+00:00",
|
||||
"goal_tier_satisfied": true,
|
||||
"manifest": {
|
||||
"compiled_views": {
|
||||
"SOUL.md": {
|
||||
"generated_from_identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"role": "generated_view_not_authority",
|
||||
"sha256": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a"
|
||||
},
|
||||
"identity.json": {
|
||||
"generated_from_identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"role": "generated_view_not_authority",
|
||||
"sha256": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6"
|
||||
}
|
||||
},
|
||||
"identity_inputs": {
|
||||
"canonical_database": {
|
||||
"authority": "synthetic_noncanonical_fixture",
|
||||
"canonical_authority_granted": false,
|
||||
"database": "leo_identity_fixture",
|
||||
"database_user": "leo_identity_reader",
|
||||
"fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"source": {
|
||||
"record_count": 7,
|
||||
"repo_path": "fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json",
|
||||
"semantic_sha256": "c5bbf346613270a5e3b17f604f69306ed42f545d4f227d784ec0dcb3d675dcb2"
|
||||
},
|
||||
"structure_sha256": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"system_identifier": "7649789040005668902",
|
||||
"table_count": 7,
|
||||
"table_rows_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"total_rows": 7
|
||||
},
|
||||
"gatewayrunner_execution_contract": {
|
||||
"fixed_message_context": {
|
||||
"auto_skill": null,
|
||||
"media_or_file_context": null,
|
||||
"reply_context": null
|
||||
},
|
||||
"profile_surfaces": [
|
||||
"config.yaml",
|
||||
"generated SOUL.md",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin tools"
|
||||
],
|
||||
"required_absent_or_empty": {
|
||||
"generated_skill_prompt_cache": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"pairing_store": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"persistent_memory": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"prior_sessions_at_first_start": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945",
|
||||
"project_context": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945"
|
||||
},
|
||||
"required_t3_turn_receipt_fields": [
|
||||
"runner_class",
|
||||
"authorization_decision",
|
||||
"effective_system_prompt_sha256",
|
||||
"compiled_skills_prompt_sha256",
|
||||
"tool_registry_schema_sha256",
|
||||
"plugin_registry_sha256",
|
||||
"selected_model",
|
||||
"selected_provider",
|
||||
"api_mode",
|
||||
"base_url_host",
|
||||
"database_retrieval_receipt",
|
||||
"session_key_sha256",
|
||||
"persisted_session_id_sha256"
|
||||
]
|
||||
},
|
||||
"identity_name": "Leo",
|
||||
"identity_sources": {
|
||||
"database_derived_identity": {
|
||||
"authority": "synthetic_noncanonical_fixture",
|
||||
"content_sha256": "9de2dfa37529b23ba277348f3d910e967551e490a9b4d2be637bf45bb2aa7dde",
|
||||
"database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"provenance": {
|
||||
"authority": "synthetic_noncanonical_fixture",
|
||||
"canonical_authority_granted": false,
|
||||
"export_receipt_sha256": null,
|
||||
"mode": "synthetic_noncanonical_fixture",
|
||||
"same_transaction_as_fingerprint": false
|
||||
},
|
||||
"record_count": 5,
|
||||
"repo_path": "fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json",
|
||||
"semantic_sha256": "70aaa7287cc41b52a3c902cf2e1b90fa4d84a5ee0a61e10c642f556fcc9304c3"
|
||||
},
|
||||
"static_constitution": {
|
||||
"authority": "pinned_static_source",
|
||||
"content_sha256": "b5477e778a2be0abba783390dac2e04ed199b8ce5679fc324270f808d46543f1",
|
||||
"record_count": 3,
|
||||
"repo_path": "fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json",
|
||||
"semantic_sha256": "b576d5cee08530a95e0b196a3296f4743b70d94bcadffaa36544dd6bf1387f8a"
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"actual_model_required_in_turn_receipt": true,
|
||||
"default_model": "leo-identity-readback-v1",
|
||||
"gateway_smart_model_routing": null,
|
||||
"hosted_weights": "external_provider_managed_not_locally_hashable",
|
||||
"model_section_sha256": "90b3943ace9ff83b1711002fda8dc9736448e8d3c1c58a17ac90608f4ce58756",
|
||||
"provider": "fixture-local",
|
||||
"smart_routing": false,
|
||||
"smart_routing_model": null
|
||||
},
|
||||
"permissions": {
|
||||
"authorization_decision_required_in_runtime_receipt": true,
|
||||
"gateway_permissions_sha256": "eac17ada1d02b5413183587d12fc265538479de00709ed8c275e9045cd720290",
|
||||
"identity_config_sha256": "e6df9b65bab148ea8502555bbc260df66b4785e7dd527cfd15d2f0a48c2e8b3e",
|
||||
"runtime_policy": {
|
||||
"allowed_tools": [
|
||||
"identity_readback"
|
||||
],
|
||||
"database_write_enabled": false,
|
||||
"network_send_enabled": false
|
||||
},
|
||||
"tool_permissions_sha256": "9bb2cec2f65d43efb597a72bdced44076c25aac292f2fccbc4c448b6e7fd3e16"
|
||||
},
|
||||
"runtime": {
|
||||
"hermes_git_head": "b238fd6fc2636ed3582e14900774014c52732698",
|
||||
"hermes_source_sha256": "dcf8109051e0b69cafe2e8dd328ff501211b62ea19725416c3781bbdd594d028",
|
||||
"identity_runtime_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"python": {
|
||||
"abi_tag": "cpython-311",
|
||||
"implementation": "CPython",
|
||||
"python_version": "3.11.15",
|
||||
"runtime_distributions": {
|
||||
"PyYAML": "6.0.3"
|
||||
},
|
||||
"runtime_distributions_sha256": "57a91bb880a01800903c1604b6eec1419514864ebd6a0644121920da15a8f165"
|
||||
},
|
||||
"teleo_git_head": "b238fd6fc2636ed3582e14900774014c52732698",
|
||||
"teleo_source_sha256": "2bd2e659eafb0ac0823f7f51c4296b2eb500b6f74469b3ac5f5287a5d4810aea"
|
||||
},
|
||||
"session_boundary": {
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_identity_inputs": false,
|
||||
"may_be_used_as_evidence": false,
|
||||
"restart_policy": "fresh_process_with_session_state_outside_identity_authority"
|
||||
},
|
||||
"skills": {
|
||||
"content_sha256": "d3f449ddcd7c88238dc88c6233cf2130875f51bd2e0911b2ce477b7c602ae90f",
|
||||
"file_count": 1
|
||||
},
|
||||
"source_commit": "b238fd6fc2636ed3582e14900774014c52732698"
|
||||
},
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"schema": "livingip.leoIdentityManifest.v1"
|
||||
},
|
||||
"mode": "successful_restart_plus_drift_negative",
|
||||
"pre_restart_verification": "pass",
|
||||
"production_database_contacted": false,
|
||||
"production_profile_replaced": false,
|
||||
"receipt_sha256": "685641533be36b78e382dfd690bdc588bc95045379b44dd885d1d1d0ee57b507",
|
||||
"required_tier": "T2_runtime",
|
||||
"restart": {
|
||||
"actual_argv_persisted": false,
|
||||
"command_serialization": "logical_redacted_v1",
|
||||
"launcher_pid": 80642,
|
||||
"logical_command": [
|
||||
"<python>",
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
"<temporary-profile>",
|
||||
"--source-root",
|
||||
".",
|
||||
"--hermes-root",
|
||||
"fixtures/working-leo/leo-identity-v1/hermes-runtime",
|
||||
"--deployment-root",
|
||||
".",
|
||||
"--query",
|
||||
"What defines Leo's identity, and what is temporary?"
|
||||
],
|
||||
"orphan_cleanup_required": false,
|
||||
"process_group_absent_after_wait": true,
|
||||
"profile_role": "restart",
|
||||
"returncode": 0,
|
||||
"stderr_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
"stdout": {
|
||||
"answer": "Leo is defined by 3 pinned static constitutional rules and 5 active database-derived identity rows from a synthetic noncanonical fixture at fingerprint 1111111111111111111111111111111111111111111111111111111111111111. SOUL.md is a generated view; runtime/session memory is temporary, noncanonical, and cannot be evidence.",
|
||||
"answer_sha256": "e0a21e1fc5357a2f358b4e96c52e971f3addee2468e44748d898adfd62dafa25",
|
||||
"authorization": {
|
||||
"authorization_decision_observed": false,
|
||||
"claim": "local_policy_binding_not_an_authorizer_readback",
|
||||
"declared_runtime_policy": {
|
||||
"allowed_tools": [
|
||||
"identity_readback"
|
||||
],
|
||||
"database_write_enabled": false,
|
||||
"network_send_enabled": false
|
||||
}
|
||||
},
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"output_provenance": {
|
||||
"actual_model_call_performed": false,
|
||||
"compiled_identity_sha256": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6",
|
||||
"compiled_soul_sha256": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"database_authority": "synthetic_noncanonical_fixture",
|
||||
"database_canonical_authority_granted": false,
|
||||
"database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"database_identity_sha256": "70aaa7287cc41b52a3c902cf2e1b90fa4d84a5ee0a61e10c642f556fcc9304c3",
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"static_constitution_sha256": "b576d5cee08530a95e0b196a3296f4743b70d94bcadffaa36544dd6bf1387f8a"
|
||||
},
|
||||
"process_id": 80642,
|
||||
"query": "What defines Leo's identity, and what is temporary?",
|
||||
"query_sha256": "13704c2c9ae999e3f9b3f1c9c5572d062cd7fe49903c2317fa85aaa39490af66",
|
||||
"runtime_instance_id": "109445cde86f4ac3b266930462fb62c3",
|
||||
"schema": "livingip.leoIdentityQueryReceipt.v1",
|
||||
"session": {
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_output_provenance": false,
|
||||
"may_be_used_as_evidence": false,
|
||||
"record_sha256": "56b6a2ea25657a87dc90dc19f49bece0ac62241f5814732ed80ef4f34d426d84"
|
||||
},
|
||||
"started_at_utc": "2026-07-15T02:46:37.036778+00:00",
|
||||
"status": "pass"
|
||||
},
|
||||
"terminated_with": null,
|
||||
"timed_out": false
|
||||
},
|
||||
"restart_compile": {
|
||||
"compiled_view_sha256": {
|
||||
"SOUL.md": "83d27dba6e1b160085388a5dc84da565abf5736351b1af3f220d13b35aac244a",
|
||||
"identity.json": "40419152862e7d9bca631c53eaa2201052cf61eafe6d304c8a302f8ea97d6fb6"
|
||||
},
|
||||
"generated_views_are_authority": false,
|
||||
"identity_inputs_sha256": "c101c488a9f623b4cef841ddb561c0f289a74df54aadfdfe9b28c4b7643fec2e",
|
||||
"manifest_sha256": "22e66cf39b2c5aac3b4043f37aa0773b4861fec616a435e8b5e0ff8c86586bef",
|
||||
"nonauthoritative_inputs_omitted": {
|
||||
".cursorrules": true,
|
||||
".hermes.md": true,
|
||||
".skills_prompt_snapshot.json": true,
|
||||
"AGENTS.md": true,
|
||||
"CLAUDE.md": true,
|
||||
"HERMES.md": true,
|
||||
"MEMORY.md": true,
|
||||
"USER.md": true,
|
||||
"memories": true,
|
||||
"platforms/pairing": true
|
||||
},
|
||||
"profile_static_entries": [
|
||||
"config.yaml",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin"
|
||||
],
|
||||
"runtime_identity_sha256": "5c54248a5d1f00b35912d47d8b3045b8cfbed16ae9b86409b549ab7042f51159",
|
||||
"schema": "livingip.leoIdentityCompileReceipt.v1",
|
||||
"session_directories_started_empty": true,
|
||||
"status": "pass"
|
||||
},
|
||||
"runtime_component_proven": "fresh_profile_recompile_plus_distinct_process_restart",
|
||||
"runtime_variant": "local_deterministic_identity_compiler_readback",
|
||||
"schema": "livingip.leoIdentityReconstructionReceipt.v1",
|
||||
"second_start_attempted": true,
|
||||
"session_boundary_readback": {
|
||||
"full_behavior_changed_after_session": true,
|
||||
"identity_runtime_unchanged_after_session": true,
|
||||
"session_classification": "temporary_noncanonical",
|
||||
"session_file_count": 1,
|
||||
"session_used_as_output_provenance": false
|
||||
},
|
||||
"status": "pass",
|
||||
"telegram_message_sent": false,
|
||||
"tier_basis": "Capability Tier Proof defines T2_runtime as local API/runtime behavior with restart where relevant; this is compiler-process runtime proof and explicitly excludes GatewayRunner and hosted-model proof."
|
||||
}
|
||||
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 @@
|
|||
"""Pinned fixture prompt boundary."""
|
||||
|
||||
GENERATED_SOUL_IS_AUTHORITY = False
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""Pinned fixture process boundary; the canary uses the real profile verifier."""
|
||||
|
||||
SESSION_STATE_IS_IDENTITY = False
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""Pinned fixture tool configuration."""
|
||||
|
||||
ALLOWED_TOOLS = ("identity_readback",)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""Pinned fixture entrypoint for the disposable identity runtime."""
|
||||
|
||||
RUNTIME_NAME = "leo-identity-readback-v1"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
"""Pinned fixture registry for the no-send identity readback tool."""
|
||||
|
||||
REGISTERED_TOOLS = ("identity_readback",)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"identity_name": "Leo",
|
||||
"rules": [
|
||||
{
|
||||
"id": "canonical-evidence",
|
||||
"text": "Treat canonical database rows and their bound evidence as durable knowledge; never promote session memory to evidence."
|
||||
},
|
||||
{
|
||||
"id": "review-before-apply",
|
||||
"text": "Proposals may be prepared, but review and guarded apply remain separate authorized actions."
|
||||
},
|
||||
{
|
||||
"id": "truthful-proof-tier",
|
||||
"text": "State exactly what was observed, separate inference from proof, and fail closed when required provenance drifts."
|
||||
}
|
||||
],
|
||||
"schema": "livingip.leoConstitutionSource.v1"
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"environment": "disposable_fixture",
|
||||
"fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"read_consistency": {
|
||||
"after": {
|
||||
"database": "leo_identity_fixture",
|
||||
"database_user": "leo_identity_reader",
|
||||
"system_identifier": "7649789040005668902",
|
||||
"wal_lsn": "0/16B6C50"
|
||||
},
|
||||
"before": {
|
||||
"database": "leo_identity_fixture",
|
||||
"database_user": "leo_identity_reader",
|
||||
"system_identifier": "7649789040005668902",
|
||||
"wal_lsn": "0/16B6C50"
|
||||
},
|
||||
"status": "stable_wal_marker"
|
||||
},
|
||||
"schema": "livingip.teleoCanonicalDatabaseFingerprint.v1",
|
||||
"status": "ok",
|
||||
"structure_sha256": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||
"table_count": 7,
|
||||
"table_rows_sha256": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"total_rows": 7
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"identity_name": "Leo",
|
||||
"source_provenance": {
|
||||
"canonical_authority_granted": false,
|
||||
"export_receipt_sha256": null,
|
||||
"mode": "synthetic_noncanonical_fixture",
|
||||
"same_transaction_as_fingerprint": false
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"evidence_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"kind": "persona",
|
||||
"rank": 0,
|
||||
"row_id": "11111111-1111-4111-8111-111111111111",
|
||||
"status": "active",
|
||||
"table": "public.personas",
|
||||
"text": "Leo is the evidence-bound reasoning interface for the Teleo collective."
|
||||
},
|
||||
{
|
||||
"evidence_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"kind": "strategy",
|
||||
"rank": 0,
|
||||
"row_id": "22222222-2222-4222-8222-222222222222",
|
||||
"status": "active",
|
||||
"table": "public.strategies",
|
||||
"text": "Prefer source-grounded synthesis that exposes uncertainty and review boundaries."
|
||||
},
|
||||
{
|
||||
"evidence_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"kind": "belief",
|
||||
"rank": 0,
|
||||
"row_id": "33333333-3333-4333-8333-333333333333",
|
||||
"status": "active",
|
||||
"table": "public.beliefs",
|
||||
"text": "A plausible answer without provenance is weaker than a bounded answer with an exact receipt."
|
||||
},
|
||||
{
|
||||
"evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
"kind": "shared_root",
|
||||
"rank": 0,
|
||||
"row_id": "44444444-4444-4444-8444-444444444444",
|
||||
"status": "active",
|
||||
"table": "public.shared_root",
|
||||
"text": "Collective intelligence must remain inspectable, reversible, and accountable to evidence."
|
||||
},
|
||||
{
|
||||
"evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
||||
"kind": "strategy_node",
|
||||
"rank": 1,
|
||||
"row_id": "55555555-5555-4555-8555-555555555555",
|
||||
"status": "active",
|
||||
"table": "public.strategy_nodes",
|
||||
"text": "Compile identity from exact canonical inputs and reject drift before runtime startup."
|
||||
}
|
||||
],
|
||||
"schema": "livingip.leoDatabaseIdentitySource.v1"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Fixture marker for the no-send identity readback tool."""
|
||||
|
||||
ACCESS_MODE = "read_only"
|
||||
NETWORK_SEND_ENABLED = False
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
model:
|
||||
provider: fixture-local
|
||||
default: leo-identity-readback-v1
|
||||
smart_routing: false
|
||||
max_tokens: 512
|
||||
agent:
|
||||
reasoning_effort: deterministic
|
||||
memory:
|
||||
enabled: false
|
||||
search: disabled
|
||||
gateway:
|
||||
execution_mode: local_no_send
|
||||
telegram:
|
||||
posting_enabled: false
|
||||
bot_token: fixture-value-is-never-emitted-or-identity-hashed
|
||||
tools:
|
||||
allowed:
|
||||
- identity_readback
|
||||
write_enabled: false
|
||||
identity_runtime:
|
||||
network_send_enabled: false
|
||||
database_write_enabled: false
|
||||
allowed_tools:
|
||||
- identity_readback
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Fixture marker for the pinned identity-boundary middleware."""
|
||||
|
||||
IDENTITY_VIEW_IS_AUTHORITY = False
|
||||
SESSION_MEMORY_IS_EVIDENCE = False
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
name: identity-boundary
|
||||
version: 1
|
||||
description: Reject startup when a compiled identity view drifts.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: identity-readback
|
||||
description: Explain the pinned Leo identity and its noncanonical session boundary.
|
||||
---
|
||||
|
||||
Read only the compiled identity view and its manifest receipt. Never treat a
|
||||
session transcript, generated SOUL file, or unverified runtime memory as a
|
||||
canonical source.
|
||||
|
|
@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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,6 +1151,19 @@ def apply_entry(
|
|||
code="entry_seed_mismatch",
|
||||
action=f"ledger entry {entry.sequence} proposal seed",
|
||||
)
|
||||
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,
|
||||
|
|
@ -746,7 +1177,8 @@ def apply_entry(
|
|||
code="entry_seed_mismatch",
|
||||
action=f"ledger entry {entry.sequence} canonical seed",
|
||||
)
|
||||
summary["seed_exact"] = True
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@ from __future__ import annotations
|
|||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.metadata
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -24,6 +28,54 @@ SCHEMA = "livingip.leoBehaviorManifest.v1"
|
|||
DEFAULT_PROFILE = Path("/home/teleo/.hermes/profiles/leoclean")
|
||||
DEFAULT_HERMES_ROOT = Path("/home/teleo/.hermes/hermes-agent")
|
||||
DEFAULT_DEPLOYMENT_ROOT = Path("/opt/teleo-eval/workspaces/deploy-infra")
|
||||
STATIC_RUNTIME_COMPONENTS = (
|
||||
"profile_config",
|
||||
"procedural_skills",
|
||||
"runtime_middleware",
|
||||
"database_tools",
|
||||
)
|
||||
IDENTITY_RUNTIME_COMPONENTS = (
|
||||
"procedural_skills",
|
||||
"runtime_middleware",
|
||||
"database_tools",
|
||||
)
|
||||
STABLE_MANIFEST_FIELDS = (
|
||||
"schema",
|
||||
"model_runtime",
|
||||
"hermes_runtime",
|
||||
"teleo_infrastructure_runtime",
|
||||
"components",
|
||||
"python_runtime",
|
||||
"canonical_database",
|
||||
)
|
||||
SECRET_KEYS = frozenset(
|
||||
{
|
||||
"access_key",
|
||||
"access_token",
|
||||
"api_key",
|
||||
"authorization",
|
||||
"bot_token",
|
||||
"client_secret",
|
||||
"credential",
|
||||
"credentials",
|
||||
"password",
|
||||
"private_key",
|
||||
"refresh_token",
|
||||
"secret",
|
||||
"token",
|
||||
}
|
||||
)
|
||||
SECRET_KEY_SUFFIXES = (
|
||||
"api_key",
|
||||
"access_key",
|
||||
"private_key",
|
||||
"password",
|
||||
"secret",
|
||||
"token",
|
||||
"credential",
|
||||
)
|
||||
RUNTIME_DISTRIBUTIONS = ("PyYAML",)
|
||||
SAFE_CONFIG_SCALAR = re.compile(r"^[A-Za-z0-9._:/-]+$")
|
||||
|
||||
|
||||
def sha256_bytes(value: bytes) -> str:
|
||||
|
|
@ -50,7 +102,9 @@ def _safe_relative(path: Path, root: Path) -> str:
|
|||
return str(path)
|
||||
|
||||
|
||||
def _resolved_node_fingerprint(path: Path, seen_directories: frozenset[tuple[int, int]] = frozenset()) -> dict[str, Any]:
|
||||
def _resolved_node_fingerprint(
|
||||
path: Path, seen_directories: frozenset[tuple[int, int]] = frozenset()
|
||||
) -> dict[str, Any]:
|
||||
"""Hash a symlink target, following nested links while stopping directory cycles."""
|
||||
|
||||
try:
|
||||
|
|
@ -141,21 +195,139 @@ def _nested(config: dict[str, Any], *path: str) -> Any:
|
|||
return value
|
||||
|
||||
|
||||
def secret_free_config(value: Any) -> Any:
|
||||
"""Return a semantic config projection with secret-bearing fields omitted."""
|
||||
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
str(key): secret_free_config(item)
|
||||
for key, item in sorted(value.items(), key=lambda pair: str(pair[0]))
|
||||
if not _is_secret_key(str(key))
|
||||
}
|
||||
if isinstance(value, list):
|
||||
return [secret_free_config(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
def _validate_identity_config_value(value: Any, *, path: str) -> None:
|
||||
if value is None or isinstance(value, (bool, int)):
|
||||
return
|
||||
if isinstance(value, str):
|
||||
if not SAFE_CONFIG_SCALAR.fullmatch(value) or "://" in value or "@" in value:
|
||||
raise ValueError(f"unsafe identity config scalar: {path}")
|
||||
return
|
||||
if isinstance(value, list):
|
||||
for index, item in enumerate(value):
|
||||
_validate_identity_config_value(item, path=f"{path}[{index}]")
|
||||
return
|
||||
if isinstance(value, dict):
|
||||
for key, item in value.items():
|
||||
if _is_secret_key(str(key)):
|
||||
raise ValueError(f"secret-bearing identity config key: {path}.{key}")
|
||||
_validate_identity_config_value(item, path=f"{path}.{key}")
|
||||
return
|
||||
raise ValueError(f"unsupported identity config value: {path}")
|
||||
|
||||
|
||||
def identity_config_projection(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Build the only configuration surface copied into a disposable identity profile."""
|
||||
|
||||
section_fields = {
|
||||
"model": ("provider", "default", "smart_routing", "smart_routing_model", "max_tokens"),
|
||||
"agent": ("reasoning_effort",),
|
||||
"memory": ("enabled", "search"),
|
||||
"tools": ("allowed", "write_enabled"),
|
||||
"identity_runtime": ("network_send_enabled", "database_write_enabled", "allowed_tools"),
|
||||
}
|
||||
projection: dict[str, Any] = {}
|
||||
for section, fields in section_fields.items():
|
||||
source = raw.get(section)
|
||||
if source is None:
|
||||
continue
|
||||
if not isinstance(source, dict):
|
||||
raise ValueError(f"identity config section must be a mapping: {section}")
|
||||
selected = {field: source[field] for field in fields if field in source}
|
||||
_validate_identity_config_value(selected, path=section)
|
||||
projection[section] = selected
|
||||
|
||||
gateway = raw.get("gateway")
|
||||
if gateway is not None:
|
||||
if not isinstance(gateway, dict):
|
||||
raise ValueError("identity config section must be a mapping: gateway")
|
||||
selected_gateway = {key: gateway[key] for key in ("execution_mode",) if key in gateway}
|
||||
telegram = gateway.get("telegram")
|
||||
if telegram is not None:
|
||||
if not isinstance(telegram, dict):
|
||||
raise ValueError("identity config section must be a mapping: gateway.telegram")
|
||||
selected_telegram = {key: telegram[key] for key in ("posting_enabled",) if key in telegram}
|
||||
if selected_telegram:
|
||||
selected_gateway["telegram"] = selected_telegram
|
||||
_validate_identity_config_value(selected_gateway, path="gateway")
|
||||
projection["gateway"] = selected_gateway
|
||||
|
||||
if "smart_model_routing" in raw:
|
||||
routing = raw["smart_model_routing"]
|
||||
if not isinstance(routing, bool):
|
||||
raise ValueError("smart_model_routing must be boolean")
|
||||
projection["smart_model_routing"] = routing
|
||||
return projection
|
||||
|
||||
|
||||
def _is_secret_key(key: str) -> bool:
|
||||
snake_case = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", key)
|
||||
normalized = re.sub(r"[^a-z0-9]+", "_", snake_case.casefold()).strip("_")
|
||||
return normalized in SECRET_KEYS or any(normalized.endswith(f"_{suffix}") for suffix in SECRET_KEY_SUFFIXES)
|
||||
|
||||
|
||||
def python_runtime_manifest() -> dict[str, Any]:
|
||||
distributions: dict[str, str | None] = {}
|
||||
for name in RUNTIME_DISTRIBUTIONS:
|
||||
try:
|
||||
distributions[name] = importlib.metadata.version(name)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
distributions[name] = None
|
||||
return {
|
||||
"implementation": platform.python_implementation(),
|
||||
"python_version": platform.python_version(),
|
||||
"abi_tag": sys.implementation.cache_tag,
|
||||
"runtime_distributions": distributions,
|
||||
"runtime_distributions_sha256": canonical_sha256(distributions),
|
||||
}
|
||||
|
||||
|
||||
def safe_model_config(config_path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||
except (OSError, TypeError, yaml.YAMLError) as exc:
|
||||
return {"status": "unavailable", "error": type(exc).__name__}
|
||||
if not isinstance(raw, dict):
|
||||
return {"status": "unavailable", "error": "ConfigNotMapping"}
|
||||
semantic = secret_free_config(raw)
|
||||
try:
|
||||
identity_config = identity_config_projection(raw)
|
||||
except ValueError:
|
||||
return {"status": "unavailable", "error": "UnsafeIdentityConfig"}
|
||||
model_semantic = identity_config.get("model") or {}
|
||||
return {
|
||||
"status": "observed",
|
||||
"provider": _nested(raw, "model", "provider"),
|
||||
"default_model": _nested(raw, "model", "default"),
|
||||
"smart_routing": _nested(raw, "model", "smart_routing"),
|
||||
"smart_routing_model": _nested(raw, "model", "smart_routing_model"),
|
||||
"max_tokens": _nested(raw, "model", "max_tokens"),
|
||||
"reasoning_effort": _nested(raw, "agent", "reasoning_effort"),
|
||||
"memory_enabled": _nested(raw, "memory", "enabled"),
|
||||
"memory_search": _nested(raw, "memory", "search"),
|
||||
"config_sha256": file_sha256(config_path),
|
||||
"semantic_config_sha256": canonical_sha256(semantic),
|
||||
"identity_config_sha256": canonical_sha256(identity_config),
|
||||
"model_section_sha256": canonical_sha256(model_semantic),
|
||||
"gateway_permissions_sha256": canonical_sha256(identity_config.get("gateway")),
|
||||
"tool_permissions_sha256": canonical_sha256(identity_config.get("tools")),
|
||||
"memory_section_sha256": canonical_sha256(identity_config.get("memory")),
|
||||
"agent_runtime_sha256": canonical_sha256(identity_config.get("agent")),
|
||||
"identity_runtime_policy": identity_config.get("identity_runtime"),
|
||||
"provider": _nested(identity_config, "model", "provider"),
|
||||
"default_model": _nested(identity_config, "model", "default"),
|
||||
"smart_routing": _nested(identity_config, "model", "smart_routing"),
|
||||
"smart_routing_model": _nested(identity_config, "model", "smart_routing_model"),
|
||||
"gateway_smart_model_routing": identity_config.get("smart_model_routing"),
|
||||
"max_tokens": _nested(identity_config, "model", "max_tokens"),
|
||||
"reasoning_effort": _nested(identity_config, "agent", "reasoning_effort"),
|
||||
"memory_enabled": _nested(identity_config, "memory", "enabled"),
|
||||
"memory_search": _nested(identity_config, "memory", "search"),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -196,7 +368,13 @@ def git_state(repo: Path) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, Any]:
|
||||
def source_code_manifest(
|
||||
profile: Path,
|
||||
paths: tuple[Path, ...],
|
||||
*,
|
||||
source_root: Path | None = None,
|
||||
namespace: str = "source",
|
||||
) -> dict[str, Any]:
|
||||
"""Hash executable source while excluding generated caches and environments."""
|
||||
|
||||
allowed_suffixes = {
|
||||
|
|
@ -216,12 +394,21 @@ def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, An
|
|||
excluded_parts = {".git", ".pytest_cache", "__pycache__", "node_modules", "venv", ".venv"}
|
||||
files: list[dict[str, Any]] = []
|
||||
missing: list[str] = []
|
||||
symlinks: list[str] = []
|
||||
source_root = (source_root or profile).resolve()
|
||||
|
||||
def source_label(path: Path) -> str:
|
||||
return f"{namespace}:{_safe_relative(path, source_root)}"
|
||||
|
||||
for requested in paths:
|
||||
if not requested.exists() and not requested.is_symlink():
|
||||
missing.append(_safe_relative(requested, profile))
|
||||
missing.append(source_label(requested))
|
||||
continue
|
||||
candidates = [requested] if requested.is_file() else sorted(requested.rglob("*"))
|
||||
for path in candidates:
|
||||
if path.is_symlink():
|
||||
symlinks.append(source_label(path))
|
||||
continue
|
||||
if not path.is_file() or excluded_parts & set(path.parts):
|
||||
continue
|
||||
stat = path.stat()
|
||||
|
|
@ -229,14 +416,14 @@ def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, An
|
|||
continue
|
||||
files.append(
|
||||
{
|
||||
"path": _safe_relative(path, profile),
|
||||
"path": source_label(path),
|
||||
"bytes": stat.st_size,
|
||||
"mode": oct(stat.st_mode & 0o777),
|
||||
"sha256": file_sha256(path),
|
||||
}
|
||||
)
|
||||
files.sort(key=lambda item: item["path"])
|
||||
stable = {"files": files, "missing": sorted(missing)}
|
||||
stable = {"files": files, "missing": sorted(missing), "symlinks": sorted(symlinks)}
|
||||
return {
|
||||
**stable,
|
||||
"file_count": len(files),
|
||||
|
|
@ -261,6 +448,72 @@ def component(
|
|||
}
|
||||
|
||||
|
||||
def stable_manifest_payload(manifest: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return the canonical, path-independent behavior payload."""
|
||||
|
||||
return {field: manifest.get(field) for field in STABLE_MANIFEST_FIELDS}
|
||||
|
||||
|
||||
def static_runtime_payload(manifest: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return the exact payload committed by ``static_runtime_sha256``."""
|
||||
|
||||
components = manifest.get("components") or {}
|
||||
return {
|
||||
"schema": manifest.get("schema"),
|
||||
"model_runtime": manifest.get("model_runtime"),
|
||||
"hermes_runtime": manifest.get("hermes_runtime"),
|
||||
"teleo_infrastructure_runtime": manifest.get("teleo_infrastructure_runtime"),
|
||||
"components": {name: components.get(name) for name in STATIC_RUNTIME_COMPONENTS},
|
||||
"python_runtime": manifest.get("python_runtime"),
|
||||
"canonical_database": manifest.get("canonical_database"),
|
||||
}
|
||||
|
||||
|
||||
def identity_runtime_payload(manifest: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return the exact payload committed by ``identity_runtime_sha256``."""
|
||||
|
||||
model = manifest.get("model_runtime") or {}
|
||||
components = manifest.get("components") or {}
|
||||
teleo = manifest.get("teleo_infrastructure_runtime") or {}
|
||||
identity_model_runtime = {
|
||||
key: model.get(key)
|
||||
for key in (
|
||||
"status",
|
||||
"identity_config_sha256",
|
||||
"model_section_sha256",
|
||||
"gateway_permissions_sha256",
|
||||
"tool_permissions_sha256",
|
||||
"memory_section_sha256",
|
||||
"agent_runtime_sha256",
|
||||
"identity_runtime_policy",
|
||||
"provider",
|
||||
"default_model",
|
||||
"smart_routing",
|
||||
"smart_routing_model",
|
||||
"gateway_smart_model_routing",
|
||||
"max_tokens",
|
||||
"reasoning_effort",
|
||||
"memory_enabled",
|
||||
"memory_search",
|
||||
"base_model_weights",
|
||||
"actual_per_turn_model_must_be_recorded_by_the_call_receipt",
|
||||
)
|
||||
}
|
||||
return {
|
||||
"schema": manifest.get("schema"),
|
||||
"model_runtime": identity_model_runtime,
|
||||
"hermes_runtime": manifest.get("hermes_runtime"),
|
||||
"teleo_infrastructure_runtime": {
|
||||
"git_head": teleo.get("git_head"),
|
||||
"git_state": teleo.get("git_state"),
|
||||
"source_tree": teleo.get("identity_source_tree"),
|
||||
},
|
||||
"components": {name: components.get(name) for name in IDENTITY_RUNTIME_COMPONENTS},
|
||||
"python_runtime": manifest.get("python_runtime"),
|
||||
"canonical_database": manifest.get("canonical_database"),
|
||||
}
|
||||
|
||||
|
||||
def build_manifest(
|
||||
profile: Path = DEFAULT_PROFILE,
|
||||
hermes_root: Path = DEFAULT_HERMES_ROOT,
|
||||
|
|
@ -270,6 +523,12 @@ def build_manifest(
|
|||
hermes_root = hermes_root.resolve()
|
||||
deployment_root = deployment_root.resolve()
|
||||
config = safe_model_config(profile / "config.yaml")
|
||||
identity_runtime_sources = (
|
||||
deployment_root / "scripts" / "leo_behavior_manifest.py",
|
||||
deployment_root / "scripts" / "leo_identity_manifest.py",
|
||||
deployment_root / "scripts" / "leo_identity_profile.py",
|
||||
deployment_root / "scripts" / "run_leo_identity_reconstruction_canary.py",
|
||||
)
|
||||
components = {
|
||||
"profile_config": component(
|
||||
profile,
|
||||
|
|
@ -294,10 +553,10 @@ def build_manifest(
|
|||
),
|
||||
"runtime_middleware": component(
|
||||
profile,
|
||||
role="Pre-LLM context injection and post-LLM validation or response replacement.",
|
||||
role="Profile plugins for pre-LLM context injection and post-LLM validation or response replacement.",
|
||||
mutability="deployment_static",
|
||||
replayability="fully_hashable",
|
||||
paths=(profile / "plugins", hermes_root / "run_agent.py"),
|
||||
paths=(profile / "plugins",),
|
||||
),
|
||||
"database_tools": component(
|
||||
profile,
|
||||
|
|
@ -353,14 +612,28 @@ def build_manifest(
|
|||
hermes_root / "hermes_cli",
|
||||
hermes_root / "tools",
|
||||
),
|
||||
source_root=hermes_root,
|
||||
namespace="hermes",
|
||||
),
|
||||
},
|
||||
"teleo_infrastructure_runtime": {
|
||||
"git_head": git_head(deployment_root),
|
||||
"git_state": git_state(deployment_root),
|
||||
"source_tree": source_code_manifest(profile, (deployment_root,)),
|
||||
"source_tree": source_code_manifest(
|
||||
profile,
|
||||
(deployment_root,),
|
||||
source_root=deployment_root,
|
||||
namespace="teleo",
|
||||
),
|
||||
"identity_source_tree": source_code_manifest(
|
||||
profile,
|
||||
identity_runtime_sources,
|
||||
source_root=deployment_root,
|
||||
namespace="teleo-identity",
|
||||
),
|
||||
},
|
||||
"components": components,
|
||||
"python_runtime": python_runtime_manifest(),
|
||||
"canonical_database": {
|
||||
"included": False,
|
||||
"reason": "Fingerprint through a read-only Postgres manifest in the database-owning harness.",
|
||||
|
|
@ -372,7 +645,9 @@ def build_manifest(
|
|||
"profile_root": str(profile),
|
||||
"hermes_root": str(hermes_root),
|
||||
"deployment_root": str(deployment_root),
|
||||
"behavior_sha256": canonical_sha256(stable),
|
||||
"static_runtime_sha256": canonical_sha256(static_runtime_payload(stable)),
|
||||
"identity_runtime_sha256": canonical_sha256(identity_runtime_payload(stable)),
|
||||
"behavior_sha256": canonical_sha256(stable_manifest_payload(stable)),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
878
scripts/leo_identity_manifest.py
Normal file
878
scripts/leo_identity_manifest.py
Normal file
|
|
@ -0,0 +1,878 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build and verify Leo's pinned, reproducible identity manifest."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
if __package__ in {None, ""}:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from scripts import leo_behavior_manifest as behavior
|
||||
|
||||
SCHEMA = "livingip.leoIdentityManifest.v1"
|
||||
CONSTITUTION_SCHEMA = "livingip.leoConstitutionSource.v1"
|
||||
DATABASE_IDENTITY_SCHEMA = "livingip.leoDatabaseIdentitySource.v1"
|
||||
STRUCTURED_VIEW_SCHEMA = "livingip.leoCompiledIdentityView.v1"
|
||||
DATABASE_FINGERPRINT_SCHEMA = "livingip.teleoCanonicalDatabaseFingerprint.v1"
|
||||
SYNTHETIC_DATABASE_MODE = "synthetic_noncanonical_fixture"
|
||||
CANONICAL_DATABASE_MODE = "canonical_database_same_transaction_export"
|
||||
HEX_64 = re.compile(r"^[0-9a-f]{64}$")
|
||||
HEX_40 = re.compile(r"^[0-9a-f]{40}$")
|
||||
IDENTITY_TABLES = frozenset(
|
||||
{
|
||||
"public.personas",
|
||||
"public.strategies",
|
||||
"public.beliefs",
|
||||
"public.shared_root",
|
||||
"public.strategy_nodes",
|
||||
"public.strategy_node_anchors",
|
||||
"public.claims",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def expected_runtime_policy() -> dict[str, Any]:
|
||||
return {
|
||||
"network_send_enabled": False,
|
||||
"database_write_enabled": False,
|
||||
"allowed_tools": ["identity_readback"],
|
||||
}
|
||||
|
||||
|
||||
def expected_session_boundary() -> dict[str, Any]:
|
||||
return {
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_identity_inputs": False,
|
||||
"may_be_used_as_evidence": False,
|
||||
"restart_policy": "fresh_process_with_session_state_outside_identity_authority",
|
||||
}
|
||||
|
||||
|
||||
def expected_gatewayrunner_contract() -> dict[str, Any]:
|
||||
return {
|
||||
"profile_surfaces": ["config.yaml", "generated SOUL.md", "skills", "plugins", "bin tools"],
|
||||
"required_absent_or_empty": {
|
||||
"persistent_memory": behavior.canonical_sha256([]),
|
||||
"pairing_store": behavior.canonical_sha256([]),
|
||||
"project_context": behavior.canonical_sha256([]),
|
||||
"generated_skill_prompt_cache": behavior.canonical_sha256([]),
|
||||
"prior_sessions_at_first_start": behavior.canonical_sha256([]),
|
||||
},
|
||||
"fixed_message_context": {
|
||||
"auto_skill": None,
|
||||
"reply_context": None,
|
||||
"media_or_file_context": None,
|
||||
},
|
||||
"required_t3_turn_receipt_fields": [
|
||||
"runner_class",
|
||||
"authorization_decision",
|
||||
"effective_system_prompt_sha256",
|
||||
"compiled_skills_prompt_sha256",
|
||||
"tool_registry_schema_sha256",
|
||||
"plugin_registry_sha256",
|
||||
"selected_model",
|
||||
"selected_provider",
|
||||
"api_mode",
|
||||
"base_url_host",
|
||||
"database_retrieval_receipt",
|
||||
"session_key_sha256",
|
||||
"persisted_session_id_sha256",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class IdentityManifestError(ValueError):
|
||||
"""An identity input is incomplete, inconsistent, or drifted."""
|
||||
|
||||
|
||||
def _require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise IdentityManifestError(message)
|
||||
|
||||
|
||||
def _is_sha256(value: Any) -> bool:
|
||||
return isinstance(value, str) and bool(HEX_64.fullmatch(value))
|
||||
|
||||
|
||||
def load_json(path: Path, label: str) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise IdentityManifestError(f"cannot read {label}: {type(exc).__name__}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise IdentityManifestError(f"{label} must be a JSON object")
|
||||
return value
|
||||
|
||||
|
||||
def canonical_rules(value: dict[str, Any]) -> list[dict[str, str]]:
|
||||
_require(value.get("schema") == CONSTITUTION_SCHEMA, "unsupported constitution schema")
|
||||
_require(value.get("identity_name") == "Leo", "constitution identity_name must be Leo")
|
||||
raw_rules = value.get("rules")
|
||||
_require(isinstance(raw_rules, list) and bool(raw_rules), "constitution rules must be non-empty")
|
||||
rules: list[dict[str, str]] = []
|
||||
for raw in raw_rules:
|
||||
_require(isinstance(raw, dict), "each constitutional rule must be an object")
|
||||
rule_id = str(raw.get("id") or "").strip()
|
||||
text = str(raw.get("text") or "").strip()
|
||||
_require(bool(rule_id and text), "each constitutional rule needs id and text")
|
||||
rules.append({"id": rule_id, "text": text})
|
||||
_require(len({item["id"] for item in rules}) == len(rules), "constitutional rule ids must be unique")
|
||||
return sorted(rules, key=lambda item: item["id"])
|
||||
|
||||
|
||||
def canonical_database_records(value: dict[str, Any], *, fingerprint_sha256: str) -> list[dict[str, str | int]]:
|
||||
_require(value.get("schema") == DATABASE_IDENTITY_SCHEMA, "unsupported database identity schema")
|
||||
_require(value.get("identity_name") == "Leo", "database identity_name must be Leo")
|
||||
_require(
|
||||
value.get("database_fingerprint_sha256") == fingerprint_sha256,
|
||||
"database identity export is not bound to the supplied database fingerprint",
|
||||
)
|
||||
raw_records = value.get("records")
|
||||
_require(isinstance(raw_records, list) and bool(raw_records), "database identity records must be non-empty")
|
||||
records: list[dict[str, str | int]] = []
|
||||
for raw in raw_records:
|
||||
_require(isinstance(raw, dict), "each database identity record must be an object")
|
||||
rank = raw.get("rank", 0)
|
||||
record: dict[str, str | int] = {
|
||||
"table": str(raw.get("table") or "").strip(),
|
||||
"row_id": str(raw.get("row_id") or "").strip(),
|
||||
"kind": str(raw.get("kind") or "").strip(),
|
||||
"rank": rank if isinstance(rank, int) and not isinstance(rank, bool) else -1,
|
||||
"text": str(raw.get("text") or "").strip(),
|
||||
"status": str(raw.get("status") or "").strip(),
|
||||
"evidence_sha256": str(raw.get("evidence_sha256") or "").strip(),
|
||||
}
|
||||
_require(record["table"] in IDENTITY_TABLES, "database identity table is outside the allowlist")
|
||||
_require(bool(record["row_id"] and record["kind"] and record["text"]), "database record fields are incomplete")
|
||||
_require(isinstance(record["rank"], int) and record["rank"] >= 0, "database record rank is invalid")
|
||||
_require(record["status"] == "active", "database identity records must be active selected rows")
|
||||
_require(_is_sha256(record["evidence_sha256"]), "database record evidence_sha256 is invalid")
|
||||
records.append(record)
|
||||
keys = {(item["table"], item["row_id"]) for item in records}
|
||||
_require(len(keys) == len(records), "database identity row bindings must be unique")
|
||||
return sorted(records, key=lambda item: (str(item["table"]), int(item["rank"]), str(item["row_id"])))
|
||||
|
||||
|
||||
def database_fingerprint_semantic(value: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Bind immutable capture provenance while excluding only the volatile WAL position."""
|
||||
|
||||
result = {key: value[key] for key in sorted(value) if key != "read_consistency"}
|
||||
consistency = value["read_consistency"]
|
||||
result["read_consistency"] = {
|
||||
"status": consistency["status"],
|
||||
"before": {key: item for key, item in consistency["before"].items() if key != "wal_lsn"},
|
||||
"after": {key: item for key, item in consistency["after"].items() if key != "wal_lsn"},
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def database_identity_provenance(
|
||||
value: dict[str, Any],
|
||||
*,
|
||||
fingerprint: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
raw = value.get("source_provenance")
|
||||
_require(isinstance(raw, dict), "database identity source provenance is missing")
|
||||
mode = raw.get("mode")
|
||||
if mode == SYNTHETIC_DATABASE_MODE:
|
||||
_require(
|
||||
raw
|
||||
== {
|
||||
"mode": SYNTHETIC_DATABASE_MODE,
|
||||
"canonical_authority_granted": False,
|
||||
"same_transaction_as_fingerprint": False,
|
||||
"export_receipt_sha256": None,
|
||||
},
|
||||
"synthetic database provenance contract is invalid",
|
||||
)
|
||||
_require(
|
||||
fingerprint.get("environment") == "disposable_fixture",
|
||||
"synthetic rows require a fixture fingerprint",
|
||||
)
|
||||
_require("export_receipt" not in value, "synthetic database identity must not claim an export receipt")
|
||||
return {**raw, "authority": "synthetic_noncanonical_fixture"}
|
||||
|
||||
if mode == CANONICAL_DATABASE_MODE:
|
||||
raise IdentityManifestError(
|
||||
"canonical authority requires independently verified output from the database-owning same-transaction "
|
||||
"exporter; caller-supplied T2 JSON cannot grant it"
|
||||
)
|
||||
raise IdentityManifestError("unsupported database identity provenance mode")
|
||||
|
||||
|
||||
def validate_database_fingerprint(value: dict[str, Any]) -> None:
|
||||
_require(value.get("schema") == DATABASE_FINGERPRINT_SCHEMA, "unsupported database fingerprint schema")
|
||||
_require(value.get("status") == "ok", "database fingerprint status must be ok")
|
||||
for field in ("fingerprint_sha256", "table_rows_sha256", "structure_sha256"):
|
||||
_require(_is_sha256(value.get(field)), f"database fingerprint {field} is invalid")
|
||||
_require(isinstance(value.get("table_count"), int) and value["table_count"] > 0, "table_count must be positive")
|
||||
_require(isinstance(value.get("total_rows"), int) and value["total_rows"] >= 0, "total_rows is invalid")
|
||||
consistency = value.get("read_consistency")
|
||||
_require(isinstance(consistency, dict), "database read_consistency is missing")
|
||||
before = consistency.get("before")
|
||||
after = consistency.get("after")
|
||||
_require(consistency.get("status") == "stable_wal_marker", "database fingerprint was not read consistently")
|
||||
_require(isinstance(before, dict) and before == after and bool(before), "database read markers differ")
|
||||
for field in ("database", "database_user", "system_identifier", "wal_lsn"):
|
||||
_require(bool(before.get(field)), f"database read marker {field} is missing")
|
||||
|
||||
|
||||
def validate_content_manifest(value: Any, *, label: str) -> None:
|
||||
"""Recompute the structural metadata of a replayable content manifest."""
|
||||
|
||||
_require(isinstance(value, dict), f"{label} is invalid")
|
||||
_require(
|
||||
set(value) == {"files", "missing", "symlinks", "file_count", "total_bytes", "sha256"},
|
||||
f"{label} fields are invalid",
|
||||
)
|
||||
files = value.get("files")
|
||||
missing = value.get("missing")
|
||||
symlinks = value.get("symlinks")
|
||||
_require(isinstance(files, list) and bool(files), f"{label} is empty")
|
||||
_require(missing == [], f"{label} has missing inputs")
|
||||
_require(symlinks == [], f"{label} contains unsupported symlinks")
|
||||
|
||||
paths: list[str] = []
|
||||
total_bytes = 0
|
||||
for item in files:
|
||||
_require(isinstance(item, dict), f"{label} contains unreadable inputs")
|
||||
_require(set(item) == {"path", "bytes", "mode", "sha256"}, f"{label} contains unreadable inputs")
|
||||
path = item.get("path")
|
||||
size = item.get("bytes")
|
||||
mode = item.get("mode")
|
||||
_require(isinstance(path, str) and bool(path), f"{label} contains an invalid path")
|
||||
_require(isinstance(size, int) and not isinstance(size, bool) and size >= 0, f"{label} byte size is invalid")
|
||||
_require(isinstance(mode, str) and bool(re.fullmatch(r"0o[0-7]{3}", mode)), f"{label} mode is invalid")
|
||||
_require(_is_sha256(item.get("sha256")), f"{label} contains unreadable inputs")
|
||||
paths.append(path)
|
||||
total_bytes += size
|
||||
|
||||
_require(paths == sorted(paths) and len(paths) == len(set(paths)), f"{label} paths are not unique and sorted")
|
||||
_require(value.get("file_count") == len(files), f"{label} file count is invalid")
|
||||
_require(value.get("total_bytes") == total_bytes, f"{label} total bytes is invalid")
|
||||
stable = {"files": files, "missing": missing, "symlinks": symlinks}
|
||||
_require(behavior.canonical_sha256(stable) == value.get("sha256"), f"{label} content hash is invalid")
|
||||
|
||||
|
||||
def validate_behavior_manifest(value: dict[str, Any]) -> None:
|
||||
_require(value.get("schema") == behavior.SCHEMA, "unsupported behavior manifest schema")
|
||||
for field in ("behavior_sha256", "static_runtime_sha256", "identity_runtime_sha256"):
|
||||
_require(_is_sha256(value.get(field)), f"behavior manifest {field} is invalid")
|
||||
_require(
|
||||
behavior.canonical_sha256(behavior.identity_runtime_payload(value)) == value["identity_runtime_sha256"],
|
||||
"behavior identity runtime hash drift detected",
|
||||
)
|
||||
_require(
|
||||
behavior.canonical_sha256(behavior.static_runtime_payload(value)) == value["static_runtime_sha256"],
|
||||
"behavior static runtime hash drift detected",
|
||||
)
|
||||
_require(
|
||||
behavior.canonical_sha256(behavior.stable_manifest_payload(value)) == value["behavior_sha256"],
|
||||
"behavior manifest stable payload hash drift detected",
|
||||
)
|
||||
model = value.get("model_runtime")
|
||||
_require(isinstance(model, dict) and model.get("status") == "observed", "model runtime was not observed")
|
||||
_require(bool(model.get("provider") and model.get("default_model")), "provider and default model must be pinned")
|
||||
for field in (
|
||||
"semantic_config_sha256",
|
||||
"identity_config_sha256",
|
||||
"model_section_sha256",
|
||||
"gateway_permissions_sha256",
|
||||
"tool_permissions_sha256",
|
||||
"memory_section_sha256",
|
||||
"agent_runtime_sha256",
|
||||
):
|
||||
_require(_is_sha256(model.get(field)), f"model runtime {field} is invalid")
|
||||
_require(
|
||||
model.get("base_model_weights") == "external_provider_managed_not_locally_hashable",
|
||||
"hosted model weight boundary is missing",
|
||||
)
|
||||
_require(
|
||||
model.get("actual_per_turn_model_must_be_recorded_by_the_call_receipt") is True, "model receipt gate missing"
|
||||
)
|
||||
policy = model.get("identity_runtime_policy")
|
||||
_require(policy == expected_runtime_policy(), "identity runtime policy must be the exact local readback policy")
|
||||
for field in ("hermes_runtime", "teleo_infrastructure_runtime"):
|
||||
runtime = value.get(field)
|
||||
_require(isinstance(runtime, dict), f"{field} is missing")
|
||||
_require(bool(HEX_40.fullmatch(str(runtime.get("git_head") or ""))), f"{field} git_head is invalid")
|
||||
git_state = runtime.get("git_state")
|
||||
_require(
|
||||
isinstance(git_state, dict) and git_state.get("worktree_clean") is True,
|
||||
f"{field} is not reconstructible from clean Git",
|
||||
)
|
||||
tree = runtime.get("identity_source_tree" if field == "teleo_infrastructure_runtime" else "source_tree")
|
||||
validate_content_manifest(tree, label=f"{field} source tree")
|
||||
components = value.get("components")
|
||||
_require(isinstance(components, dict), "behavior components are missing")
|
||||
for name in behavior.IDENTITY_RUNTIME_COMPONENTS:
|
||||
content = (components.get(name) or {}).get("content")
|
||||
validate_content_manifest(content, label=f"component {name}")
|
||||
python_runtime = value.get("python_runtime")
|
||||
_require(isinstance(python_runtime, dict), "Python runtime binding is missing")
|
||||
_require(
|
||||
bool(python_runtime.get("implementation") and python_runtime.get("python_version")),
|
||||
"Python runtime is incomplete",
|
||||
)
|
||||
_require(_is_sha256(python_runtime.get("runtime_distributions_sha256")), "runtime package binding is invalid")
|
||||
_require(
|
||||
all(python_runtime.get("runtime_distributions", {}).values()), "a required runtime distribution is missing"
|
||||
)
|
||||
|
||||
|
||||
def _repo_path(path: Path, source_root: Path) -> str:
|
||||
try:
|
||||
return path.resolve(strict=True).relative_to(source_root.resolve(strict=True)).as_posix()
|
||||
except (OSError, ValueError) as exc:
|
||||
raise IdentityManifestError(f"identity source is outside the pinned source root: {path}") from exc
|
||||
|
||||
|
||||
def source_binding(
|
||||
path: Path,
|
||||
*,
|
||||
source_root: Path,
|
||||
semantic_value: Any,
|
||||
count: int,
|
||||
pin_content: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
result = {
|
||||
"repo_path": _repo_path(path, source_root),
|
||||
"semantic_sha256": behavior.canonical_sha256(semantic_value),
|
||||
"record_count": count,
|
||||
}
|
||||
if pin_content:
|
||||
result["content_sha256"] = behavior.file_sha256(path)
|
||||
return result
|
||||
|
||||
|
||||
def render_identity_views(
|
||||
*,
|
||||
identity_inputs_sha256: str,
|
||||
database_fingerprint_sha256: str,
|
||||
database_provenance: dict[str, Any],
|
||||
rules: list[dict[str, str]],
|
||||
records: list[dict[str, str | int]],
|
||||
) -> dict[str, str]:
|
||||
soul = [
|
||||
"<!-- GENERATED FILE: edit the pinned sources, never this view. -->",
|
||||
"# Leo",
|
||||
"",
|
||||
f"Identity inputs: `{identity_inputs_sha256}`",
|
||||
"",
|
||||
"## Static constitutional rules",
|
||||
"",
|
||||
"These rules are static constitutional inputs, not database claims.",
|
||||
"",
|
||||
]
|
||||
soul.extend(f"- **{item['id']}**: {item['text']}" for item in rules)
|
||||
database_description = (
|
||||
"canonical database capture"
|
||||
if database_provenance["canonical_authority_granted"]
|
||||
else "synthetic noncanonical fixture"
|
||||
)
|
||||
soul.extend(
|
||||
[
|
||||
"",
|
||||
"## Database-derived identity",
|
||||
"",
|
||||
f"Generated from {database_description} fingerprint `{database_fingerprint_sha256}`.",
|
||||
"",
|
||||
]
|
||||
)
|
||||
soul.extend(
|
||||
f"- `{item['table']}/{item['row_id']}` [{item['kind']}, rank {item['rank']}]: {item['text']} "
|
||||
f"(evidence `{item['evidence_sha256']}`)"
|
||||
for item in records
|
||||
)
|
||||
soul.extend(
|
||||
[
|
||||
"",
|
||||
"## Authority and session boundary",
|
||||
"",
|
||||
"- This `SOUL.md` is a generated view and is never an authority by itself.",
|
||||
"- Generated database identity is authoritative only when its same-transaction export receipt grants canonical authority.",
|
||||
"- Runtime/session memory is temporary, noncanonical, and cannot serve as evidence.",
|
||||
"- Hosted model weights are provider-managed and must be attributed by each turn receipt.",
|
||||
"",
|
||||
]
|
||||
)
|
||||
structured = {
|
||||
"schema": STRUCTURED_VIEW_SCHEMA,
|
||||
"identity_name": "Leo",
|
||||
"identity_inputs_sha256": identity_inputs_sha256,
|
||||
"static_constitution": {"authority": "pinned_static_source", "rules": rules},
|
||||
"database_identity": {
|
||||
"authority": database_provenance["authority"],
|
||||
"canonical_authority_granted": database_provenance["canonical_authority_granted"],
|
||||
"source_mode": database_provenance["mode"],
|
||||
"database_fingerprint_sha256": database_fingerprint_sha256,
|
||||
"records": records,
|
||||
},
|
||||
"session_boundary": {
|
||||
"authority": "none",
|
||||
"classification": "temporary_noncanonical",
|
||||
"may_be_used_as_evidence": False,
|
||||
},
|
||||
}
|
||||
return {
|
||||
"SOUL.md": "\n".join(soul),
|
||||
"identity.json": json.dumps(structured, indent=2, sort_keys=True) + "\n",
|
||||
}
|
||||
|
||||
|
||||
def build_identity_manifest(
|
||||
*,
|
||||
behavior_manifest: dict[str, Any],
|
||||
database_fingerprint_path: Path,
|
||||
constitution_path: Path,
|
||||
database_identity_path: Path,
|
||||
source_root: Path,
|
||||
source_commit: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
validate_behavior_manifest(behavior_manifest)
|
||||
database_fingerprint = load_json(database_fingerprint_path, "database fingerprint")
|
||||
validate_database_fingerprint(database_fingerprint)
|
||||
constitution = load_json(constitution_path, "constitution source")
|
||||
database_identity = load_json(database_identity_path, "database identity source")
|
||||
rules = canonical_rules(constitution)
|
||||
records = canonical_database_records(
|
||||
database_identity, fingerprint_sha256=database_fingerprint["fingerprint_sha256"]
|
||||
)
|
||||
database_provenance = database_identity_provenance(
|
||||
database_identity,
|
||||
fingerprint=database_fingerprint,
|
||||
)
|
||||
runtime_commit = behavior_manifest["teleo_infrastructure_runtime"]["git_head"]
|
||||
source_commit = source_commit or runtime_commit
|
||||
_require(bool(HEX_40.fullmatch(str(source_commit))), "source commit is invalid")
|
||||
_require(source_commit == runtime_commit, "source commit does not match the behavior manifest")
|
||||
|
||||
model = behavior_manifest["model_runtime"]
|
||||
components = behavior_manifest["components"]
|
||||
marker = database_fingerprint["read_consistency"]["before"]
|
||||
core = {
|
||||
"identity_name": "Leo",
|
||||
"source_commit": source_commit,
|
||||
"runtime": {
|
||||
"identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"],
|
||||
"hermes_git_head": behavior_manifest["hermes_runtime"]["git_head"],
|
||||
"hermes_source_sha256": behavior_manifest["hermes_runtime"]["source_tree"]["sha256"],
|
||||
"teleo_git_head": runtime_commit,
|
||||
"teleo_source_sha256": behavior_manifest["teleo_infrastructure_runtime"]["identity_source_tree"]["sha256"],
|
||||
"python": behavior_manifest["python_runtime"],
|
||||
},
|
||||
"model": {
|
||||
"provider": model["provider"],
|
||||
"default_model": model["default_model"],
|
||||
"smart_routing": model.get("smart_routing"),
|
||||
"smart_routing_model": model.get("smart_routing_model"),
|
||||
"gateway_smart_model_routing": model.get("gateway_smart_model_routing"),
|
||||
"model_section_sha256": model["model_section_sha256"],
|
||||
"hosted_weights": "external_provider_managed_not_locally_hashable",
|
||||
"actual_model_required_in_turn_receipt": True,
|
||||
},
|
||||
"skills": {
|
||||
"content_sha256": components["procedural_skills"]["content"]["sha256"],
|
||||
"file_count": components["procedural_skills"]["content"]["file_count"],
|
||||
},
|
||||
"permissions": {
|
||||
"identity_config_sha256": model["identity_config_sha256"],
|
||||
"gateway_permissions_sha256": model["gateway_permissions_sha256"],
|
||||
"tool_permissions_sha256": model["tool_permissions_sha256"],
|
||||
"runtime_policy": model["identity_runtime_policy"],
|
||||
"authorization_decision_required_in_runtime_receipt": True,
|
||||
},
|
||||
"canonical_database": {
|
||||
"database": marker["database"],
|
||||
"database_user": marker["database_user"],
|
||||
"system_identifier": marker["system_identifier"],
|
||||
"authority": database_provenance["authority"],
|
||||
"canonical_authority_granted": database_provenance["canonical_authority_granted"],
|
||||
**{
|
||||
key: database_fingerprint[key]
|
||||
for key in ("fingerprint_sha256", "table_rows_sha256", "structure_sha256", "table_count", "total_rows")
|
||||
},
|
||||
"source": source_binding(
|
||||
database_fingerprint_path,
|
||||
source_root=source_root,
|
||||
semantic_value=database_fingerprint_semantic(database_fingerprint),
|
||||
count=database_fingerprint["table_count"],
|
||||
pin_content=False,
|
||||
),
|
||||
},
|
||||
"identity_sources": {
|
||||
"static_constitution": {
|
||||
"authority": "pinned_static_source",
|
||||
**source_binding(constitution_path, source_root=source_root, semantic_value=rules, count=len(rules)),
|
||||
},
|
||||
"database_derived_identity": {
|
||||
"authority": database_provenance["authority"],
|
||||
"provenance": database_provenance,
|
||||
"database_fingerprint_sha256": database_fingerprint["fingerprint_sha256"],
|
||||
**source_binding(
|
||||
database_identity_path,
|
||||
source_root=source_root,
|
||||
semantic_value={"provenance": database_provenance, "records": records},
|
||||
count=len(records),
|
||||
),
|
||||
},
|
||||
},
|
||||
"session_boundary": expected_session_boundary(),
|
||||
"gatewayrunner_execution_contract": expected_gatewayrunner_contract(),
|
||||
}
|
||||
identity_inputs_sha256 = behavior.canonical_sha256(core)
|
||||
views = render_identity_views(
|
||||
identity_inputs_sha256=identity_inputs_sha256,
|
||||
database_fingerprint_sha256=database_fingerprint["fingerprint_sha256"],
|
||||
database_provenance=database_provenance,
|
||||
rules=rules,
|
||||
records=records,
|
||||
)
|
||||
stable = {
|
||||
"schema": SCHEMA,
|
||||
"identity_inputs": core,
|
||||
"identity_inputs_sha256": identity_inputs_sha256,
|
||||
"compiled_views": {
|
||||
name: {
|
||||
"role": "generated_view_not_authority",
|
||||
"sha256": behavior.sha256_bytes(content.encode("utf-8")),
|
||||
"generated_from_identity_inputs_sha256": identity_inputs_sha256,
|
||||
}
|
||||
for name, content in sorted(views.items())
|
||||
},
|
||||
}
|
||||
return {**stable, "manifest_sha256": behavior.canonical_sha256(stable)}
|
||||
|
||||
|
||||
def validate_identity_manifest(value: dict[str, Any]) -> None:
|
||||
_require(value.get("schema") == SCHEMA, "unsupported identity manifest schema")
|
||||
expected = value.get("manifest_sha256")
|
||||
_require(_is_sha256(expected), "identity manifest sha256 is invalid")
|
||||
stable = {key: item for key, item in value.items() if key != "manifest_sha256"}
|
||||
_require(behavior.canonical_sha256(stable) == expected, "identity manifest hash drift detected")
|
||||
core = value.get("identity_inputs")
|
||||
_require(isinstance(core, dict), "identity inputs are missing")
|
||||
_require(
|
||||
set(core)
|
||||
== {
|
||||
"identity_name",
|
||||
"source_commit",
|
||||
"runtime",
|
||||
"model",
|
||||
"skills",
|
||||
"permissions",
|
||||
"canonical_database",
|
||||
"identity_sources",
|
||||
"session_boundary",
|
||||
"gatewayrunner_execution_contract",
|
||||
},
|
||||
"identity input contract fields are invalid",
|
||||
)
|
||||
identity_hash = value.get("identity_inputs_sha256")
|
||||
_require(_is_sha256(identity_hash), "identity input hash is invalid")
|
||||
_require(behavior.canonical_sha256(core) == identity_hash, "identity input hash drift detected")
|
||||
_require(core.get("identity_name") == "Leo", "identity name must be Leo")
|
||||
|
||||
runtime = core.get("runtime")
|
||||
_require(isinstance(runtime, dict), "runtime binding is missing")
|
||||
_require(
|
||||
set(runtime)
|
||||
== {
|
||||
"identity_runtime_sha256",
|
||||
"hermes_git_head",
|
||||
"hermes_source_sha256",
|
||||
"teleo_git_head",
|
||||
"teleo_source_sha256",
|
||||
"python",
|
||||
},
|
||||
"runtime binding fields are invalid",
|
||||
)
|
||||
_require(_is_sha256(runtime.get("identity_runtime_sha256")), "identity runtime binding is invalid")
|
||||
for field in ("hermes_git_head", "teleo_git_head"):
|
||||
_require(bool(HEX_40.fullmatch(str(runtime.get(field) or ""))), f"runtime {field} is invalid")
|
||||
for field in ("hermes_source_sha256", "teleo_source_sha256"):
|
||||
_require(_is_sha256(runtime.get(field)), f"runtime {field} is invalid")
|
||||
_require(core.get("source_commit") == runtime["teleo_git_head"], "source commit is not bound to Teleo Git")
|
||||
python_runtime = runtime.get("python")
|
||||
_require(isinstance(python_runtime, dict), "Python runtime binding is missing")
|
||||
_require(
|
||||
bool(python_runtime.get("implementation") and python_runtime.get("python_version")),
|
||||
"Python runtime binding is incomplete",
|
||||
)
|
||||
_require(_is_sha256(python_runtime.get("runtime_distributions_sha256")), "Python distribution binding is invalid")
|
||||
|
||||
model = core.get("model")
|
||||
_require(isinstance(model, dict), "model binding is missing")
|
||||
_require(
|
||||
set(model)
|
||||
== {
|
||||
"provider",
|
||||
"default_model",
|
||||
"smart_routing",
|
||||
"smart_routing_model",
|
||||
"gateway_smart_model_routing",
|
||||
"model_section_sha256",
|
||||
"hosted_weights",
|
||||
"actual_model_required_in_turn_receipt",
|
||||
},
|
||||
"model binding fields are invalid",
|
||||
)
|
||||
_require(bool(model.get("provider") and model.get("default_model")), "model provider and default must be pinned")
|
||||
_require(_is_sha256(model.get("model_section_sha256")), "model section binding is invalid")
|
||||
_require(
|
||||
model.get("hosted_weights") == "external_provider_managed_not_locally_hashable",
|
||||
"hosted model boundary is invalid",
|
||||
)
|
||||
_require(model.get("actual_model_required_in_turn_receipt") is True, "per-turn model receipt gate is invalid")
|
||||
|
||||
skills = core.get("skills")
|
||||
_require(isinstance(skills, dict) and set(skills) == {"content_sha256", "file_count"}, "skills binding is invalid")
|
||||
_require(_is_sha256(skills.get("content_sha256")), "skills content hash is invalid")
|
||||
_require(
|
||||
isinstance(skills.get("file_count"), int)
|
||||
and not isinstance(skills.get("file_count"), bool)
|
||||
and skills["file_count"] > 0,
|
||||
"skills file count is invalid",
|
||||
)
|
||||
|
||||
permissions = core.get("permissions")
|
||||
_require(isinstance(permissions, dict), "permission binding is missing")
|
||||
_require(
|
||||
set(permissions)
|
||||
== {
|
||||
"identity_config_sha256",
|
||||
"gateway_permissions_sha256",
|
||||
"tool_permissions_sha256",
|
||||
"runtime_policy",
|
||||
"authorization_decision_required_in_runtime_receipt",
|
||||
},
|
||||
"permission binding fields are invalid",
|
||||
)
|
||||
for field in ("identity_config_sha256", "gateway_permissions_sha256", "tool_permissions_sha256"):
|
||||
_require(_is_sha256(permissions.get(field)), f"permission binding {field} is invalid")
|
||||
_require(
|
||||
permissions.get("runtime_policy") == expected_runtime_policy(),
|
||||
"identity runtime policy must be the exact local readback policy",
|
||||
)
|
||||
_require(
|
||||
permissions.get("authorization_decision_required_in_runtime_receipt") is True,
|
||||
"runtime authorization receipt gate is invalid",
|
||||
)
|
||||
|
||||
database = core.get("canonical_database")
|
||||
_require(isinstance(database, dict), "canonical database binding is missing")
|
||||
for field in ("fingerprint_sha256", "table_rows_sha256", "structure_sha256"):
|
||||
_require(_is_sha256(database.get(field)), f"canonical database {field} is invalid")
|
||||
_require(
|
||||
bool(database.get("database") and database.get("database_user") and database.get("system_identifier")),
|
||||
"canonical database identity is incomplete",
|
||||
)
|
||||
allowed_authorities = {"synthetic_noncanonical_fixture": False}
|
||||
_require(database.get("authority") in allowed_authorities, "database authority is invalid")
|
||||
_require(
|
||||
database.get("canonical_authority_granted") is allowed_authorities[database["authority"]],
|
||||
"database canonical authority grant is invalid",
|
||||
)
|
||||
_require(
|
||||
isinstance(database.get("table_count"), int)
|
||||
and not isinstance(database.get("table_count"), bool)
|
||||
and database["table_count"] > 0,
|
||||
"canonical database table_count is invalid",
|
||||
)
|
||||
_require(
|
||||
isinstance(database.get("total_rows"), int)
|
||||
and not isinstance(database.get("total_rows"), bool)
|
||||
and database["total_rows"] >= 0,
|
||||
"canonical database total_rows is invalid",
|
||||
)
|
||||
|
||||
def validate_source_binding(binding: Any, *, label: str, content_required: bool) -> None:
|
||||
_require(isinstance(binding, dict), f"{label} source binding is missing")
|
||||
repo_path = binding.get("repo_path")
|
||||
_require(
|
||||
isinstance(repo_path, str)
|
||||
and bool(repo_path)
|
||||
and not Path(repo_path).is_absolute()
|
||||
and ".." not in Path(repo_path).parts,
|
||||
f"{label} source path is invalid",
|
||||
)
|
||||
_require(_is_sha256(binding.get("semantic_sha256")), f"{label} semantic binding is invalid")
|
||||
_require(
|
||||
isinstance(binding.get("record_count"), int)
|
||||
and not isinstance(binding.get("record_count"), bool)
|
||||
and binding["record_count"] > 0,
|
||||
f"{label} record count is invalid",
|
||||
)
|
||||
if content_required:
|
||||
_require(_is_sha256(binding.get("content_sha256")), f"{label} content binding is invalid")
|
||||
else:
|
||||
_require("content_sha256" not in binding, f"{label} content binding must exclude volatile capture markers")
|
||||
|
||||
validate_source_binding(database.get("source"), label="database fingerprint", content_required=False)
|
||||
sources = core.get("identity_sources")
|
||||
_require(
|
||||
isinstance(sources, dict) and set(sources) == {"static_constitution", "database_derived_identity"},
|
||||
"identity source bindings are invalid",
|
||||
)
|
||||
constitution = sources["static_constitution"]
|
||||
derived = sources["database_derived_identity"]
|
||||
_require(isinstance(constitution, dict), "constitution source binding is invalid")
|
||||
_require(isinstance(derived, dict), "database identity source binding is invalid")
|
||||
_require(constitution.get("authority") == "pinned_static_source", "constitution authority is invalid")
|
||||
_require(derived.get("authority") == database["authority"], "database identity authority is invalid")
|
||||
provenance = derived.get("provenance")
|
||||
_require(isinstance(provenance, dict), "database identity provenance is missing")
|
||||
_require(provenance.get("authority") == database["authority"], "database identity provenance authority is invalid")
|
||||
_require(
|
||||
provenance.get("canonical_authority_granted") is database["canonical_authority_granted"],
|
||||
"database identity provenance grant is invalid",
|
||||
)
|
||||
_require(provenance.get("mode") == SYNTHETIC_DATABASE_MODE, "synthetic database provenance mode is invalid")
|
||||
_require(
|
||||
provenance.get("same_transaction_as_fingerprint") is False,
|
||||
"synthetic fixture transaction claim is invalid",
|
||||
)
|
||||
_require(provenance.get("export_receipt_sha256") is None, "synthetic fixture must not claim an export receipt")
|
||||
_require(
|
||||
derived.get("database_fingerprint_sha256") == database["fingerprint_sha256"],
|
||||
"database identity source is misbound",
|
||||
)
|
||||
validate_source_binding(constitution, label="constitution", content_required=True)
|
||||
validate_source_binding(derived, label="database identity", content_required=True)
|
||||
_require(core.get("session_boundary") == expected_session_boundary(), "session authority boundary is invalid")
|
||||
_require(
|
||||
core.get("gatewayrunner_execution_contract") == expected_gatewayrunner_contract(),
|
||||
"GatewayRunner execution contract is invalid",
|
||||
)
|
||||
|
||||
views = value.get("compiled_views")
|
||||
_require(isinstance(views, dict) and set(views) == {"SOUL.md", "identity.json"}, "compiled views are incomplete")
|
||||
for name, view in views.items():
|
||||
_require(isinstance(view, dict) and _is_sha256(view.get("sha256")), f"compiled view {name} is invalid")
|
||||
_require(
|
||||
set(view) == {"role", "sha256", "generated_from_identity_inputs_sha256"},
|
||||
f"compiled view {name} fields are invalid",
|
||||
)
|
||||
_require(view.get("role") == "generated_view_not_authority", f"compiled view {name} authority is invalid")
|
||||
_require(
|
||||
view.get("generated_from_identity_inputs_sha256") == identity_hash, f"compiled view {name} is misbound"
|
||||
)
|
||||
|
||||
|
||||
def verify_bound_sources(value: dict[str, Any], source_root: Path) -> dict[str, str]:
|
||||
validate_identity_manifest(value)
|
||||
core = value["identity_inputs"]
|
||||
bindings = {
|
||||
"database fingerprint": core["canonical_database"]["source"],
|
||||
"constitution source": core["identity_sources"]["static_constitution"],
|
||||
"database identity source": core["identity_sources"]["database_derived_identity"],
|
||||
}
|
||||
paths = {label: source_root / binding["repo_path"] for label, binding in bindings.items()}
|
||||
for label, binding in bindings.items():
|
||||
path = paths[label]
|
||||
_require(path.is_file(), f"bound {label} is missing")
|
||||
if binding.get("content_sha256"):
|
||||
_require(behavior.file_sha256(path) == binding["content_sha256"], f"bound {label} content drift detected")
|
||||
fingerprint = load_json(paths["database fingerprint"], "database fingerprint")
|
||||
validate_database_fingerprint(fingerprint)
|
||||
_require(
|
||||
behavior.canonical_sha256(database_fingerprint_semantic(fingerprint))
|
||||
== bindings["database fingerprint"]["semantic_sha256"],
|
||||
"database fingerprint semantic drift detected",
|
||||
)
|
||||
marker = fingerprint["read_consistency"]["before"]
|
||||
for field in ("database", "database_user", "system_identifier"):
|
||||
_require(marker[field] == core["canonical_database"][field], f"canonical database {field} drift detected")
|
||||
rules = canonical_rules(load_json(paths["constitution source"], "constitution source"))
|
||||
database_identity = load_json(paths["database identity source"], "database identity source")
|
||||
records = canonical_database_records(
|
||||
database_identity,
|
||||
fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||
)
|
||||
database_provenance = database_identity_provenance(
|
||||
database_identity,
|
||||
fingerprint=fingerprint,
|
||||
)
|
||||
_require(
|
||||
behavior.canonical_sha256(rules) == bindings["constitution source"]["semantic_sha256"],
|
||||
"constitution semantic drift detected",
|
||||
)
|
||||
_require(
|
||||
behavior.canonical_sha256({"provenance": database_provenance, "records": records})
|
||||
== bindings["database identity source"]["semantic_sha256"],
|
||||
"database identity semantic drift detected",
|
||||
)
|
||||
_require(
|
||||
database_provenance == core["identity_sources"]["database_derived_identity"]["provenance"],
|
||||
"database identity provenance drift detected",
|
||||
)
|
||||
_require(
|
||||
fingerprint["fingerprint_sha256"] == core["canonical_database"]["fingerprint_sha256"],
|
||||
"canonical database fingerprint drift detected",
|
||||
)
|
||||
views = render_identity_views(
|
||||
identity_inputs_sha256=value["identity_inputs_sha256"],
|
||||
database_fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||
database_provenance=database_provenance,
|
||||
rules=rules,
|
||||
records=records,
|
||||
)
|
||||
for name, content in views.items():
|
||||
_require(
|
||||
behavior.sha256_bytes(content.encode("utf-8")) == value["compiled_views"][name]["sha256"],
|
||||
f"compiled view contract drift detected for {name}",
|
||||
)
|
||||
return views
|
||||
|
||||
|
||||
def write_json(path: Path, value: dict[str, Any]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
generate = subparsers.add_parser("generate")
|
||||
generate.add_argument("--behavior-manifest", type=Path, required=True)
|
||||
generate.add_argument("--database-fingerprint", type=Path, required=True)
|
||||
generate.add_argument("--constitution", type=Path, required=True)
|
||||
generate.add_argument("--database-identity", type=Path, required=True)
|
||||
generate.add_argument("--source-root", type=Path, required=True)
|
||||
generate.add_argument("--source-commit")
|
||||
generate.add_argument("--output", type=Path, required=True)
|
||||
verify = subparsers.add_parser("verify")
|
||||
verify.add_argument("--manifest", type=Path, required=True)
|
||||
verify.add_argument("--source-root", type=Path)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.command == "generate":
|
||||
manifest = build_identity_manifest(
|
||||
behavior_manifest=load_json(args.behavior_manifest, "behavior manifest"),
|
||||
database_fingerprint_path=args.database_fingerprint,
|
||||
constitution_path=args.constitution,
|
||||
database_identity_path=args.database_identity,
|
||||
source_root=args.source_root,
|
||||
source_commit=args.source_commit,
|
||||
)
|
||||
write_json(args.output, manifest)
|
||||
else:
|
||||
manifest = load_json(args.manifest, "identity manifest")
|
||||
validate_identity_manifest(manifest)
|
||||
if args.source_root:
|
||||
verify_bound_sources(manifest, args.source_root)
|
||||
print(json.dumps({"status": "ok", "manifest_sha256": manifest["manifest_sha256"]}))
|
||||
return 0
|
||||
except IdentityManifestError as exc:
|
||||
print(json.dumps({"status": "error", "error": "identity_manifest_invalid", "message": str(exc)}))
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
495
scripts/leo_identity_profile.py
Normal file
495
scripts/leo_identity_profile.py
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Compile, verify, and query a disposable Leo identity profile."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
|
||||
if __package__ in {None, ""}:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from scripts import leo_behavior_manifest as behavior
|
||||
from scripts import leo_identity_manifest as identity
|
||||
|
||||
LOCK_SCHEMA = "livingip.leoIdentityProfileLock.v1"
|
||||
COMPILE_RECEIPT_SCHEMA = "livingip.leoIdentityCompileReceipt.v1"
|
||||
QUERY_RECEIPT_SCHEMA = "livingip.leoIdentityQueryReceipt.v1"
|
||||
SESSION_RECORD_SCHEMA = "livingip.leoTemporarySessionRecord.v1"
|
||||
FIXED_QUERY = "What defines Leo's identity, and what is temporary?"
|
||||
STATIC_PROFILE_ENTRIES = ("config.yaml", "skills", "plugins", "bin")
|
||||
COMPILED_PROFILE_ENTRIES = frozenset(
|
||||
{
|
||||
"config.yaml",
|
||||
"skills",
|
||||
"plugins",
|
||||
"bin",
|
||||
"SOUL.md",
|
||||
"identity.json",
|
||||
"identity-manifest.json",
|
||||
"identity-lock.json",
|
||||
"compile-receipt.json",
|
||||
"sessions",
|
||||
"state",
|
||||
}
|
||||
)
|
||||
OMITTED_NONAUTHORITATIVE_ENTRIES = (
|
||||
"memories",
|
||||
"MEMORY.md",
|
||||
"USER.md",
|
||||
"platforms/pairing",
|
||||
".skills_prompt_snapshot.json",
|
||||
".hermes.md",
|
||||
"HERMES.md",
|
||||
"AGENTS.md",
|
||||
"CLAUDE.md",
|
||||
".cursorrules",
|
||||
)
|
||||
|
||||
|
||||
class IdentityProfileError(identity.IdentityManifestError):
|
||||
"""A compiled profile cannot be trusted or started."""
|
||||
|
||||
|
||||
def _require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise IdentityProfileError(message)
|
||||
|
||||
|
||||
def _copy_static_profile(template: Path, target: Path) -> list[str]:
|
||||
_require(template.is_dir(), "profile template is missing")
|
||||
_require(not target.exists(), "compiled profile target already exists")
|
||||
target.mkdir(parents=True, mode=0o700)
|
||||
copied: list[str] = []
|
||||
for name in STATIC_PROFILE_ENTRIES:
|
||||
source = template / name
|
||||
if not source.exists():
|
||||
continue
|
||||
destination = target / name
|
||||
_require(not source.is_symlink(), f"unsafe symlinked static profile entry: {name}")
|
||||
if source.is_dir():
|
||||
_require(
|
||||
not any(path.is_symlink() for path in source.rglob("*")),
|
||||
f"unsafe symlink inside static profile entry: {name}",
|
||||
)
|
||||
shutil.copytree(source, destination, symlinks=False)
|
||||
elif source.is_file() and not source.is_symlink():
|
||||
if name == "config.yaml":
|
||||
try:
|
||||
raw_config = yaml.safe_load(source.read_text(encoding="utf-8")) or {}
|
||||
except (OSError, TypeError, yaml.YAMLError) as exc:
|
||||
raise IdentityProfileError(f"cannot sanitize profile config: {type(exc).__name__}") from exc
|
||||
_require(isinstance(raw_config, dict), "profile config must be a mapping")
|
||||
destination.write_text(
|
||||
yaml.safe_dump(behavior.identity_config_projection(raw_config), sort_keys=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
destination.chmod(0o600)
|
||||
else:
|
||||
shutil.copy2(source, destination)
|
||||
else:
|
||||
raise IdentityProfileError(f"unsafe static profile entry: {name}")
|
||||
copied.append(name)
|
||||
_require("config.yaml" in copied, "profile template config.yaml is missing")
|
||||
return copied
|
||||
|
||||
|
||||
def _behavior(profile: Path, hermes_root: Path, deployment_root: Path) -> dict[str, Any]:
|
||||
return behavior.build_manifest(profile, hermes_root, deployment_root)
|
||||
|
||||
|
||||
def _verify_runtime_binding(
|
||||
manifest: dict[str, Any],
|
||||
*,
|
||||
profile: Path,
|
||||
hermes_root: Path,
|
||||
deployment_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
observed = _behavior(profile, hermes_root, deployment_root)
|
||||
identity.validate_behavior_manifest(observed)
|
||||
expected = manifest["identity_inputs"]["runtime"]
|
||||
_require(
|
||||
observed["identity_runtime_sha256"] == expected["identity_runtime_sha256"], "identity runtime drift detected"
|
||||
)
|
||||
_require(observed["hermes_runtime"]["git_head"] == expected["hermes_git_head"], "Hermes Git drift detected")
|
||||
_require(
|
||||
observed["hermes_runtime"]["source_tree"]["sha256"] == expected["hermes_source_sha256"],
|
||||
"Hermes source drift detected",
|
||||
)
|
||||
_require(
|
||||
observed["teleo_infrastructure_runtime"]["git_head"] == expected["teleo_git_head"],
|
||||
"Teleo Git drift detected",
|
||||
)
|
||||
_require(
|
||||
observed["teleo_infrastructure_runtime"]["identity_source_tree"]["sha256"] == expected["teleo_source_sha256"],
|
||||
"Teleo identity source drift detected",
|
||||
)
|
||||
_require(observed["python_runtime"] == expected["python"], "Python runtime drift detected")
|
||||
_require(
|
||||
manifest["identity_inputs"]["source_commit"] == observed["teleo_infrastructure_runtime"]["git_head"],
|
||||
"source commit drift detected",
|
||||
)
|
||||
|
||||
expected_model = manifest["identity_inputs"]["model"]
|
||||
observed_model = observed["model_runtime"]
|
||||
model_bindings = {
|
||||
"provider": observed_model.get("provider"),
|
||||
"default_model": observed_model.get("default_model"),
|
||||
"smart_routing": observed_model.get("smart_routing"),
|
||||
"smart_routing_model": observed_model.get("smart_routing_model"),
|
||||
"gateway_smart_model_routing": observed_model.get("gateway_smart_model_routing"),
|
||||
"model_section_sha256": observed_model.get("model_section_sha256"),
|
||||
"hosted_weights": observed_model.get("base_model_weights"),
|
||||
"actual_model_required_in_turn_receipt": observed_model.get(
|
||||
"actual_per_turn_model_must_be_recorded_by_the_call_receipt"
|
||||
),
|
||||
}
|
||||
for field, observed_value in model_bindings.items():
|
||||
_require(expected_model.get(field) == observed_value, f"model runtime binding drift detected: {field}")
|
||||
|
||||
expected_permissions = manifest["identity_inputs"]["permissions"]
|
||||
permission_bindings = {
|
||||
"identity_config_sha256": observed_model.get("identity_config_sha256"),
|
||||
"gateway_permissions_sha256": observed_model.get("gateway_permissions_sha256"),
|
||||
"tool_permissions_sha256": observed_model.get("tool_permissions_sha256"),
|
||||
"runtime_policy": observed_model.get("identity_runtime_policy"),
|
||||
"authorization_decision_required_in_runtime_receipt": True,
|
||||
}
|
||||
for field, observed_value in permission_bindings.items():
|
||||
_require(
|
||||
expected_permissions.get(field) == observed_value, f"permission runtime binding drift detected: {field}"
|
||||
)
|
||||
|
||||
observed_skills = observed["components"]["procedural_skills"]["content"]
|
||||
expected_skills = manifest["identity_inputs"]["skills"]
|
||||
_require(expected_skills["content_sha256"] == observed_skills["sha256"], "skills runtime binding drift detected")
|
||||
_require(expected_skills["file_count"] == observed_skills["file_count"], "skills file count drift detected")
|
||||
return observed
|
||||
|
||||
|
||||
def _view_file_sha256(profile: Path, name: str) -> str:
|
||||
path = profile / name
|
||||
_require(path.is_file() and not path.is_symlink(), f"compiled view is missing or unsafe: {name}")
|
||||
return behavior.file_sha256(path)
|
||||
|
||||
|
||||
def _verify_nonauthoritative_surfaces(
|
||||
profile: Path,
|
||||
*,
|
||||
require_empty_sessions: bool,
|
||||
require_compile_receipt: bool,
|
||||
) -> dict[str, Any]:
|
||||
omitted = {
|
||||
name: not (profile / name).exists() and not (profile / name).is_symlink()
|
||||
for name in OMITTED_NONAUTHORITATIVE_ENTRIES
|
||||
}
|
||||
_require(all(omitted.values()), "profile contains a forbidden nonauthoritative input surface")
|
||||
expected_entries = set(COMPILED_PROFILE_ENTRIES)
|
||||
if not require_compile_receipt:
|
||||
expected_entries.remove("compile-receipt.json")
|
||||
profile_entries = list(profile.iterdir())
|
||||
actual_entries = {path.name for path in profile_entries}
|
||||
_require(actual_entries == expected_entries, "compiled profile entry set is not exact")
|
||||
_require(not any(path.is_symlink() for path in profile_entries), "compiled profile entries must not be symlinks")
|
||||
if require_compile_receipt:
|
||||
compile_receipt = identity.load_json(profile / "compile-receipt.json", "identity compile receipt")
|
||||
_require(compile_receipt.get("schema") == COMPILE_RECEIPT_SCHEMA, "identity compile receipt schema is invalid")
|
||||
_require(compile_receipt.get("status") == "pass", "identity compile receipt status is invalid")
|
||||
state = profile / "state"
|
||||
sessions = profile / "sessions"
|
||||
for path, label in ((state, "state"), (sessions, "sessions")):
|
||||
_require(path.is_dir() and not path.is_symlink(), f"profile {label} directory is missing or unsafe")
|
||||
_require(not any(state.iterdir()), "profile state directory must remain empty")
|
||||
session_files = list(sessions.iterdir())
|
||||
if require_empty_sessions:
|
||||
_require(not session_files, "profile sessions must start empty")
|
||||
for path in session_files:
|
||||
_require(path.is_file() and not path.is_symlink() and path.suffix == ".json", "unsafe session entry detected")
|
||||
record = identity.load_json(path, "temporary session record")
|
||||
_require(
|
||||
set(record)
|
||||
== {
|
||||
"schema",
|
||||
"authority",
|
||||
"classification",
|
||||
"query_sha256",
|
||||
"answer_sha256",
|
||||
"may_be_used_as_evidence",
|
||||
},
|
||||
"temporary session record fields are invalid",
|
||||
)
|
||||
_require(record.get("schema") == SESSION_RECORD_SCHEMA, "temporary session record schema is invalid")
|
||||
_require(record.get("authority") == "none", "temporary session authority is invalid")
|
||||
_require(record.get("classification") == "temporary_noncanonical", "temporary session class is invalid")
|
||||
_require(record.get("may_be_used_as_evidence") is False, "temporary session cannot be evidence")
|
||||
_require(identity._is_sha256(record.get("query_sha256")), "temporary session query hash is invalid")
|
||||
_require(identity._is_sha256(record.get("answer_sha256")), "temporary session answer hash is invalid")
|
||||
return {
|
||||
"nonauthoritative_inputs_omitted": omitted,
|
||||
"session_file_count": len(session_files),
|
||||
"state_empty": True,
|
||||
}
|
||||
|
||||
|
||||
def compile_profile(
|
||||
manifest: dict[str, Any],
|
||||
*,
|
||||
source_root: Path,
|
||||
profile_template: Path,
|
||||
profile: Path,
|
||||
hermes_root: Path,
|
||||
deployment_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
views = identity.verify_bound_sources(manifest, source_root)
|
||||
template_behavior = _behavior(profile_template, hermes_root, deployment_root)
|
||||
identity.validate_behavior_manifest(template_behavior)
|
||||
_require(
|
||||
template_behavior["identity_runtime_sha256"]
|
||||
== manifest["identity_inputs"]["runtime"]["identity_runtime_sha256"],
|
||||
"profile template does not match the pinned identity runtime",
|
||||
)
|
||||
copied = _copy_static_profile(profile_template, profile)
|
||||
try:
|
||||
for name, content in views.items():
|
||||
path = profile / name
|
||||
path.write_text(content, encoding="utf-8")
|
||||
path.chmod(0o600)
|
||||
identity.write_json(profile / "identity-manifest.json", manifest)
|
||||
(profile / "identity-manifest.json").chmod(0o600)
|
||||
for directory in (profile / "sessions", profile / "state"):
|
||||
directory.mkdir(mode=0o700)
|
||||
lock = {
|
||||
"schema": LOCK_SCHEMA,
|
||||
"manifest_sha256": manifest["manifest_sha256"],
|
||||
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
||||
"compiled_views": {name: {"sha256": _view_file_sha256(profile, name)} for name in sorted(views)},
|
||||
"session_boundary": manifest["identity_inputs"]["session_boundary"],
|
||||
}
|
||||
identity.write_json(profile / "identity-lock.json", lock)
|
||||
(profile / "identity-lock.json").chmod(0o600)
|
||||
observed = _verify_runtime_binding(
|
||||
manifest,
|
||||
profile=profile,
|
||||
hermes_root=hermes_root,
|
||||
deployment_root=deployment_root,
|
||||
)
|
||||
for name, expected in manifest["compiled_views"].items():
|
||||
_require(_view_file_sha256(profile, name) == expected["sha256"], f"compiled {name} hash mismatch")
|
||||
soul_files = observed["components"]["runtime_identity"]["content"]["files"]
|
||||
soul = next((item for item in soul_files if item.get("path") == "SOUL.md"), None)
|
||||
_require(
|
||||
isinstance(soul, dict) and soul.get("sha256") == manifest["compiled_views"]["SOUL.md"]["sha256"],
|
||||
"SOUL runtime binding failed",
|
||||
)
|
||||
surface_readback = _verify_nonauthoritative_surfaces(
|
||||
profile,
|
||||
require_empty_sessions=True,
|
||||
require_compile_receipt=False,
|
||||
)
|
||||
receipt = {
|
||||
"schema": COMPILE_RECEIPT_SCHEMA,
|
||||
"status": "pass",
|
||||
"manifest_sha256": manifest["manifest_sha256"],
|
||||
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
||||
"profile_static_entries": copied,
|
||||
"compiled_view_sha256": {
|
||||
name: manifest["compiled_views"][name]["sha256"] for name in sorted(manifest["compiled_views"])
|
||||
},
|
||||
"runtime_identity_sha256": observed["identity_runtime_sha256"],
|
||||
"session_directories_started_empty": all(
|
||||
not any((profile / name).iterdir()) for name in ("sessions", "state")
|
||||
),
|
||||
"nonauthoritative_inputs_omitted": surface_readback["nonauthoritative_inputs_omitted"],
|
||||
"generated_views_are_authority": False,
|
||||
}
|
||||
identity.write_json(profile / "compile-receipt.json", receipt)
|
||||
return receipt
|
||||
except BaseException:
|
||||
shutil.rmtree(profile, ignore_errors=True)
|
||||
raise
|
||||
|
||||
|
||||
def verify_profile(
|
||||
profile: Path,
|
||||
*,
|
||||
source_root: Path,
|
||||
hermes_root: Path,
|
||||
deployment_root: Path,
|
||||
) -> tuple[dict[str, Any], dict[str, str], dict[str, Any]]:
|
||||
manifest = identity.load_json(profile / "identity-manifest.json", "compiled identity manifest")
|
||||
views = identity.verify_bound_sources(manifest, source_root)
|
||||
lock = identity.load_json(profile / "identity-lock.json", "identity profile lock")
|
||||
_verify_nonauthoritative_surfaces(
|
||||
profile,
|
||||
require_empty_sessions=False,
|
||||
require_compile_receipt=True,
|
||||
)
|
||||
_require(lock.get("schema") == LOCK_SCHEMA, "identity profile lock schema is invalid")
|
||||
_require(
|
||||
lock.get("manifest_sha256") == manifest["manifest_sha256"], "identity profile lock manifest drift detected"
|
||||
)
|
||||
_require(
|
||||
lock.get("identity_inputs_sha256") == manifest["identity_inputs_sha256"],
|
||||
"identity profile lock input drift detected",
|
||||
)
|
||||
compile_receipt = identity.load_json(profile / "compile-receipt.json", "identity compile receipt")
|
||||
_require(
|
||||
compile_receipt.get("manifest_sha256") == manifest["manifest_sha256"],
|
||||
"identity compile receipt manifest drift detected",
|
||||
)
|
||||
_require(
|
||||
compile_receipt.get("identity_inputs_sha256") == manifest["identity_inputs_sha256"],
|
||||
"identity compile receipt input drift detected",
|
||||
)
|
||||
for name, expected_content in views.items():
|
||||
expected_hash = behavior.sha256_bytes(expected_content.encode("utf-8"))
|
||||
_require(expected_hash == manifest["compiled_views"][name]["sha256"], f"manifest view drift detected: {name}")
|
||||
_require(_view_file_sha256(profile, name) == expected_hash, f"compiled view drift detected: {name}")
|
||||
_require(
|
||||
(lock.get("compiled_views") or {}).get(name, {}).get("sha256") == expected_hash,
|
||||
f"profile lock view drift detected: {name}",
|
||||
)
|
||||
observed = _verify_runtime_binding(
|
||||
manifest,
|
||||
profile=profile,
|
||||
hermes_root=hermes_root,
|
||||
deployment_root=deployment_root,
|
||||
)
|
||||
return manifest, views, observed
|
||||
|
||||
|
||||
def query_profile(
|
||||
profile: Path,
|
||||
*,
|
||||
query: str,
|
||||
source_root: Path,
|
||||
hermes_root: Path,
|
||||
deployment_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
_require(query == FIXED_QUERY, "only the pinned identity readback query is allowed")
|
||||
manifest, _views, observed = verify_profile(
|
||||
profile,
|
||||
source_root=source_root,
|
||||
hermes_root=hermes_root,
|
||||
deployment_root=deployment_root,
|
||||
)
|
||||
structured = identity.load_json(profile / "identity.json", "compiled structured identity view")
|
||||
rule_count = len(structured["static_constitution"]["rules"])
|
||||
record_count = len(structured["database_identity"]["records"])
|
||||
fingerprint = structured["database_identity"]["database_fingerprint_sha256"]
|
||||
canonical_authority = structured["database_identity"]["canonical_authority_granted"]
|
||||
database_description = "canonical database" if canonical_authority else "synthetic noncanonical fixture"
|
||||
answer = (
|
||||
f"Leo is defined by {rule_count} pinned static constitutional rules and {record_count} active "
|
||||
f"database-derived identity rows from a {database_description} at fingerprint {fingerprint}. "
|
||||
"SOUL.md is a generated view; "
|
||||
"runtime/session memory is temporary, noncanonical, and cannot be evidence."
|
||||
)
|
||||
query_sha256 = behavior.sha256_bytes(query.encode("utf-8"))
|
||||
answer_sha256 = behavior.sha256_bytes(answer.encode("utf-8"))
|
||||
instance_id = uuid.uuid4().hex
|
||||
session_record = {
|
||||
"schema": SESSION_RECORD_SCHEMA,
|
||||
"authority": "none",
|
||||
"classification": "temporary_noncanonical",
|
||||
"query_sha256": query_sha256,
|
||||
"answer_sha256": answer_sha256,
|
||||
"may_be_used_as_evidence": False,
|
||||
}
|
||||
session_path = profile / "sessions" / f"{instance_id}.json"
|
||||
identity.write_json(session_path, session_record)
|
||||
receipt = {
|
||||
"schema": QUERY_RECEIPT_SCHEMA,
|
||||
"status": "pass",
|
||||
"started_at_utc": datetime.now(UTC).isoformat(),
|
||||
"process_id": os.getpid(),
|
||||
"runtime_instance_id": instance_id,
|
||||
"query": query,
|
||||
"query_sha256": query_sha256,
|
||||
"answer": answer,
|
||||
"answer_sha256": answer_sha256,
|
||||
"manifest_sha256": manifest["manifest_sha256"],
|
||||
"identity_inputs_sha256": manifest["identity_inputs_sha256"],
|
||||
"output_provenance": {
|
||||
"static_constitution_sha256": manifest["identity_inputs"]["identity_sources"]["static_constitution"][
|
||||
"semantic_sha256"
|
||||
],
|
||||
"database_identity_sha256": manifest["identity_inputs"]["identity_sources"]["database_derived_identity"][
|
||||
"semantic_sha256"
|
||||
],
|
||||
"database_fingerprint_sha256": fingerprint,
|
||||
"database_authority": structured["database_identity"]["authority"],
|
||||
"database_canonical_authority_granted": canonical_authority,
|
||||
"compiled_identity_sha256": manifest["compiled_views"]["identity.json"]["sha256"],
|
||||
"compiled_soul_sha256": manifest["compiled_views"]["SOUL.md"]["sha256"],
|
||||
"runtime_identity_sha256": observed["identity_runtime_sha256"],
|
||||
"actual_model_call_performed": False,
|
||||
},
|
||||
"authorization": {
|
||||
"declared_runtime_policy": manifest["identity_inputs"]["permissions"]["runtime_policy"],
|
||||
"authorization_decision_observed": False,
|
||||
"claim": "local_policy_binding_not_an_authorizer_readback",
|
||||
},
|
||||
"session": {
|
||||
"record_sha256": behavior.file_sha256(session_path),
|
||||
"classification": "temporary_noncanonical",
|
||||
"included_in_output_provenance": False,
|
||||
"may_be_used_as_evidence": False,
|
||||
},
|
||||
}
|
||||
return receipt
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
compile_command = subparsers.add_parser("compile")
|
||||
compile_command.add_argument("--manifest", type=Path, required=True)
|
||||
compile_command.add_argument("--source-root", type=Path, required=True)
|
||||
compile_command.add_argument("--profile-template", type=Path, required=True)
|
||||
compile_command.add_argument("--profile", type=Path, required=True)
|
||||
compile_command.add_argument("--hermes-root", type=Path, required=True)
|
||||
compile_command.add_argument("--deployment-root", type=Path, required=True)
|
||||
query_command = subparsers.add_parser("query")
|
||||
query_command.add_argument("--profile", type=Path, required=True)
|
||||
query_command.add_argument("--query", default=FIXED_QUERY)
|
||||
query_command.add_argument("--source-root", type=Path, required=True)
|
||||
query_command.add_argument("--hermes-root", type=Path, required=True)
|
||||
query_command.add_argument("--deployment-root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.command == "compile":
|
||||
result = compile_profile(
|
||||
identity.load_json(args.manifest, "identity manifest"),
|
||||
source_root=args.source_root,
|
||||
profile_template=args.profile_template,
|
||||
profile=args.profile,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
else:
|
||||
result = query_profile(
|
||||
args.profile,
|
||||
query=args.query,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
return 0
|
||||
except identity.IdentityManifestError as exc:
|
||||
print(json.dumps({"status": "error", "error": "identity_drift", "message": str(exc)}))
|
||||
return 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -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.append(
|
||||
{
|
||||
"quote": _resolve_reference(
|
||||
evidence_quote = _resolve_reference(
|
||||
evidence_ref,
|
||||
fragments,
|
||||
f"claims[{claim_index}].evidence[{evidence_index}].quote_ref",
|
||||
),
|
||||
)
|
||||
evidence.append(
|
||||
{
|
||||
"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),
|
||||
|
|
|
|||
517
scripts/run_leo_identity_reconstruction_canary.py
Normal file
517
scripts/run_leo_identity_reconstruction_canary.py
Normal file
|
|
@ -0,0 +1,517 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Prove manifest-driven Leo identity reconstruction across a cold restart."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
from typing import Any
|
||||
|
||||
if __package__ in {None, ""}:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from scripts import leo_behavior_manifest as behavior
|
||||
from scripts import leo_identity_manifest as identity
|
||||
from scripts import leo_identity_profile as profile_runtime
|
||||
|
||||
SCHEMA = "livingip.leoIdentityReconstructionReceipt.v1"
|
||||
PRIVATE_PATH_PREFIXES = tuple("/" + value for value in ("Users/", "private/tmp/", "var/folders/"))
|
||||
|
||||
|
||||
class CanaryError(RuntimeError):
|
||||
"""The disposable identity runtime did not satisfy its T2 contract."""
|
||||
|
||||
|
||||
def _require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise CanaryError(message)
|
||||
|
||||
|
||||
def _receipt_strings(value: object) -> list[str]:
|
||||
if isinstance(value, dict):
|
||||
keys = [key for key in value if isinstance(key, str)]
|
||||
return keys + [text for item in value.values() for text in _receipt_strings(item)]
|
||||
if isinstance(value, list):
|
||||
return [text for item in value for text in _receipt_strings(item)]
|
||||
return [value] if isinstance(value, str) else []
|
||||
|
||||
|
||||
def _validate_portable_receipt(value: object) -> None:
|
||||
for text in _receipt_strings(value):
|
||||
if (
|
||||
any(prefix in text for prefix in PRIVATE_PATH_PREFIXES)
|
||||
or PurePosixPath(text).is_absolute()
|
||||
or PureWindowsPath(text).is_absolute()
|
||||
):
|
||||
raise CanaryError("receipt contains a private or absolute filesystem path")
|
||||
|
||||
|
||||
def _process_group_absent(process_group: int) -> bool:
|
||||
try:
|
||||
os.killpg(process_group, 0)
|
||||
except ProcessLookupError:
|
||||
return True
|
||||
except PermissionError:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def _wait_for_process_group_absence(process_group: int, timeout_seconds: float = 5) -> bool:
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while time.monotonic() < deadline:
|
||||
if _process_group_absent(process_group):
|
||||
return True
|
||||
time.sleep(0.02)
|
||||
return _process_group_absent(process_group)
|
||||
|
||||
|
||||
def _signal_process_group(process_group: int, signal_number: signal.Signals) -> bool:
|
||||
try:
|
||||
os.killpg(process_group, signal_number)
|
||||
return True
|
||||
except (ProcessLookupError, PermissionError):
|
||||
return False
|
||||
|
||||
|
||||
def _cleanup_remaining_process_group(process_group: int) -> str | None:
|
||||
if not _signal_process_group(process_group, signal.SIGTERM):
|
||||
return None
|
||||
if _wait_for_process_group_absence(process_group, timeout_seconds=1):
|
||||
return "SIGTERM"
|
||||
if not _signal_process_group(process_group, signal.SIGKILL):
|
||||
return "SIGTERM"
|
||||
return "SIGKILL"
|
||||
|
||||
|
||||
def _logical_repo_path(path: Path, *, deployment_root: Path, placeholder: str) -> str:
|
||||
"""Return a stable repo-relative path or a non-identifying placeholder."""
|
||||
|
||||
try:
|
||||
relative = path.resolve().relative_to(deployment_root.resolve())
|
||||
except (OSError, ValueError):
|
||||
return placeholder
|
||||
value = relative.as_posix()
|
||||
return value if value != "." else "."
|
||||
|
||||
|
||||
def _logical_query_command(
|
||||
*,
|
||||
deployment_root: Path,
|
||||
source_root: Path,
|
||||
hermes_root: Path,
|
||||
) -> list[str]:
|
||||
return [
|
||||
"<python>",
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
"<temporary-profile>",
|
||||
"--source-root",
|
||||
_logical_repo_path(source_root, deployment_root=deployment_root, placeholder="<source-root>"),
|
||||
"--hermes-root",
|
||||
_logical_repo_path(hermes_root, deployment_root=deployment_root, placeholder="<hermes-root>"),
|
||||
"--deployment-root",
|
||||
".",
|
||||
"--query",
|
||||
profile_runtime.FIXED_QUERY,
|
||||
]
|
||||
|
||||
|
||||
def _run_query_child(
|
||||
*,
|
||||
deployment_root: Path,
|
||||
profile: Path,
|
||||
source_root: Path,
|
||||
hermes_root: Path,
|
||||
profile_role: str,
|
||||
timeout_seconds: float = 60,
|
||||
) -> dict[str, Any]:
|
||||
deployment_root = deployment_root.resolve()
|
||||
profile = profile.resolve()
|
||||
source_root = source_root.resolve()
|
||||
hermes_root = hermes_root.resolve()
|
||||
actual_argv = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"scripts.leo_identity_profile",
|
||||
"query",
|
||||
"--profile",
|
||||
str(profile),
|
||||
"--source-root",
|
||||
str(source_root),
|
||||
"--hermes-root",
|
||||
str(hermes_root),
|
||||
"--deployment-root",
|
||||
str(deployment_root),
|
||||
"--query",
|
||||
profile_runtime.FIXED_QUERY,
|
||||
]
|
||||
process = subprocess.Popen(
|
||||
actual_argv,
|
||||
cwd=deployment_root,
|
||||
env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
start_new_session=True,
|
||||
)
|
||||
timed_out = False
|
||||
terminated_with: str | None = None
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
process_group_absent_after_wait = False
|
||||
orphan_cleanup_required = False
|
||||
try:
|
||||
try:
|
||||
stdout, stderr = process.communicate(timeout=timeout_seconds)
|
||||
except subprocess.TimeoutExpired:
|
||||
timed_out = True
|
||||
if _signal_process_group(process.pid, signal.SIGTERM):
|
||||
terminated_with = "SIGTERM"
|
||||
try:
|
||||
stdout, stderr = process.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
if _signal_process_group(process.pid, signal.SIGKILL):
|
||||
terminated_with = "SIGKILL"
|
||||
stdout, stderr = process.communicate(timeout=5)
|
||||
finally:
|
||||
if process.poll() is None:
|
||||
if _signal_process_group(process.pid, signal.SIGTERM):
|
||||
terminated_with = terminated_with or "SIGTERM"
|
||||
try:
|
||||
process.communicate(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
if _signal_process_group(process.pid, signal.SIGKILL):
|
||||
terminated_with = "SIGKILL"
|
||||
process.communicate(timeout=5)
|
||||
orphan_cleanup_required = not _process_group_absent(process.pid)
|
||||
if orphan_cleanup_required:
|
||||
terminated_with = _cleanup_remaining_process_group(process.pid) or terminated_with
|
||||
process_group_absent_after_wait = _wait_for_process_group_absence(process.pid)
|
||||
try:
|
||||
payload = json.loads(stdout.strip().splitlines()[-1]) if stdout.strip() else {}
|
||||
except json.JSONDecodeError as exc:
|
||||
raise CanaryError("identity child returned invalid JSON") from exc
|
||||
return {
|
||||
"logical_command": _logical_query_command(
|
||||
deployment_root=deployment_root,
|
||||
source_root=source_root,
|
||||
hermes_root=hermes_root,
|
||||
),
|
||||
"command_serialization": "logical_redacted_v1",
|
||||
"profile_role": profile_role,
|
||||
"actual_argv_persisted": False,
|
||||
"launcher_pid": process.pid,
|
||||
"returncode": process.returncode,
|
||||
"timed_out": timed_out,
|
||||
"terminated_with": terminated_with,
|
||||
"orphan_cleanup_required": orphan_cleanup_required,
|
||||
"stdout": payload,
|
||||
"stderr_sha256": behavior.sha256_bytes(stderr.encode("utf-8")),
|
||||
"process_group_absent_after_wait": process_group_absent_after_wait,
|
||||
}
|
||||
|
||||
|
||||
def _query_passed(child: dict[str, Any]) -> bool:
|
||||
payload = child.get("stdout") or {}
|
||||
return bool(
|
||||
child.get("returncode") == 0
|
||||
and child.get("timed_out") is False
|
||||
and child.get("orphan_cleanup_required") is False
|
||||
and child.get("process_group_absent_after_wait") is True
|
||||
and payload.get("schema") == profile_runtime.QUERY_RECEIPT_SCHEMA
|
||||
and payload.get("status") == "pass"
|
||||
)
|
||||
|
||||
|
||||
def _drift_attempt(
|
||||
*,
|
||||
deployment_root: Path,
|
||||
source_root: Path,
|
||||
hermes_root: Path,
|
||||
drift_profile: Path,
|
||||
) -> dict[str, Any]:
|
||||
with (drift_profile / "SOUL.md").open("a", encoding="utf-8") as handle:
|
||||
handle.write("\nInjected drift must fail closed.\n")
|
||||
child = _run_query_child(
|
||||
deployment_root=deployment_root,
|
||||
profile=drift_profile,
|
||||
source_root=source_root,
|
||||
hermes_root=hermes_root,
|
||||
profile_role="drift_negative",
|
||||
)
|
||||
child["fail_closed"] = bool(
|
||||
child["returncode"] == 3
|
||||
and child["timed_out"] is False
|
||||
and child["orphan_cleanup_required"] is False
|
||||
and child["process_group_absent_after_wait"] is True
|
||||
and (child.get("stdout") or {}).get("error") == "identity_drift"
|
||||
and "compiled view drift detected" in str((child.get("stdout") or {}).get("message"))
|
||||
)
|
||||
child["answer_returned"] = bool((child.get("stdout") or {}).get("answer"))
|
||||
return child
|
||||
|
||||
|
||||
def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]:
|
||||
output = args.output.resolve()
|
||||
temporary_root = Path(tempfile.mkdtemp(prefix="leo-identity-reconstruction-"))
|
||||
report: dict[str, Any] = {
|
||||
"schema": SCHEMA,
|
||||
"generated_at_utc": datetime.now(UTC).isoformat(),
|
||||
"required_tier": "T2_runtime",
|
||||
"mode": "drift_before_restart"
|
||||
if args.inject_drift_before_restart
|
||||
else "successful_restart_plus_drift_negative",
|
||||
"fixed_query": profile_runtime.FIXED_QUERY,
|
||||
"fixed_query_sha256": behavior.sha256_bytes(profile_runtime.FIXED_QUERY.encode("utf-8")),
|
||||
"production_profile_replaced": False,
|
||||
"production_database_contacted": False,
|
||||
"telegram_message_sent": False,
|
||||
"actual_hosted_model_call_performed": False,
|
||||
"errors": [],
|
||||
}
|
||||
exit_code = 1
|
||||
try:
|
||||
behavior_manifest = behavior.build_manifest(args.profile_template, args.hermes_root, args.deployment_root)
|
||||
identity.validate_behavior_manifest(behavior_manifest)
|
||||
manifest = identity.build_identity_manifest(
|
||||
behavior_manifest=behavior_manifest,
|
||||
database_fingerprint_path=args.database_fingerprint,
|
||||
constitution_path=args.constitution,
|
||||
database_identity_path=args.database_identity,
|
||||
source_root=args.source_root,
|
||||
)
|
||||
report["manifest"] = manifest
|
||||
report["behavior_observation_before_compile"] = {
|
||||
"behavior_sha256": behavior_manifest["behavior_sha256"],
|
||||
"identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"],
|
||||
"source_commit": behavior_manifest["teleo_infrastructure_runtime"]["git_head"],
|
||||
}
|
||||
compiled_profile = temporary_root / "first-profile"
|
||||
report["compile"] = profile_runtime.compile_profile(
|
||||
manifest,
|
||||
source_root=args.source_root,
|
||||
profile_template=args.profile_template,
|
||||
profile=compiled_profile,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
compiled_behavior_before_query = behavior.build_manifest(
|
||||
compiled_profile, args.hermes_root, args.deployment_root
|
||||
)
|
||||
report["compiled_profile_before_query"] = {
|
||||
"behavior_sha256": compiled_behavior_before_query["behavior_sha256"],
|
||||
"identity_runtime_sha256": compiled_behavior_before_query["identity_runtime_sha256"],
|
||||
}
|
||||
first = _run_query_child(
|
||||
deployment_root=args.deployment_root,
|
||||
profile=compiled_profile,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
profile_role="first_start",
|
||||
)
|
||||
report["first_start"] = first
|
||||
_require(_query_passed(first), "first disposable identity process did not answer successfully")
|
||||
after_first = behavior.build_manifest(compiled_profile, args.hermes_root, args.deployment_root)
|
||||
report["session_boundary_readback"] = {
|
||||
"full_behavior_changed_after_session": after_first["behavior_sha256"]
|
||||
!= compiled_behavior_before_query["behavior_sha256"],
|
||||
"identity_runtime_unchanged_after_session": after_first["identity_runtime_sha256"]
|
||||
== compiled_behavior_before_query["identity_runtime_sha256"],
|
||||
"session_file_count": len(list((compiled_profile / "sessions").glob("*.json"))),
|
||||
"session_classification": "temporary_noncanonical",
|
||||
"session_used_as_output_provenance": False,
|
||||
}
|
||||
if args.inject_drift_before_restart:
|
||||
with (compiled_profile / "SOUL.md").open("a", encoding="utf-8") as handle:
|
||||
handle.write("\nInjected pre-restart drift.\n")
|
||||
rejected = _run_query_child(
|
||||
deployment_root=args.deployment_root,
|
||||
profile=compiled_profile,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
profile_role="pre_restart_drift",
|
||||
)
|
||||
report["pre_restart_drift"] = rejected
|
||||
report["drift_target"] = "compiled SOUL.md generated view"
|
||||
report["second_start_attempted"] = False
|
||||
report["gates"] = {
|
||||
"first_start_passed": True,
|
||||
"first_process_stopped": first["process_group_absent_after_wait"],
|
||||
"drift_detected_before_restart": rejected["returncode"] == 3
|
||||
and (rejected.get("stdout") or {}).get("error") == "identity_drift",
|
||||
"drift_returned_no_answer": not bool((rejected.get("stdout") or {}).get("answer")),
|
||||
"drift_process_stopped": rejected["process_group_absent_after_wait"],
|
||||
"drift_process_had_no_orphan_cleanup": rejected["orphan_cleanup_required"] is False,
|
||||
"second_start_not_attempted": True,
|
||||
}
|
||||
report["status"] = "pass" if all(report["gates"].values()) else "fail"
|
||||
else:
|
||||
profile_runtime.verify_profile(
|
||||
compiled_profile,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
report["pre_restart_verification"] = "pass"
|
||||
report["second_start_attempted"] = True
|
||||
restarted_profile = temporary_root / "restart-profile"
|
||||
report["restart_compile"] = profile_runtime.compile_profile(
|
||||
manifest,
|
||||
source_root=args.source_root,
|
||||
profile_template=args.profile_template,
|
||||
profile=restarted_profile,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
profile_runtime.verify_profile(
|
||||
restarted_profile,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
second = _run_query_child(
|
||||
deployment_root=args.deployment_root,
|
||||
profile=restarted_profile,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
profile_role="restart",
|
||||
)
|
||||
report["restart"] = second
|
||||
_require(_query_passed(second), "restarted disposable identity process did not answer successfully")
|
||||
first_payload = first["stdout"]
|
||||
second_payload = second["stdout"]
|
||||
drift_profile = temporary_root / "drift-profile"
|
||||
report["drift_compile"] = profile_runtime.compile_profile(
|
||||
manifest,
|
||||
source_root=args.source_root,
|
||||
profile_template=args.profile_template,
|
||||
profile=drift_profile,
|
||||
hermes_root=args.hermes_root,
|
||||
deployment_root=args.deployment_root,
|
||||
)
|
||||
drift = _drift_attempt(
|
||||
deployment_root=args.deployment_root,
|
||||
source_root=args.source_root,
|
||||
hermes_root=args.hermes_root,
|
||||
drift_profile=drift_profile,
|
||||
)
|
||||
report["drift_negative"] = drift
|
||||
report["drift_target"] = "compiled SOUL.md generated view"
|
||||
report["gates"] = {
|
||||
"manifest_generated": identity._is_sha256(manifest["manifest_sha256"]),
|
||||
"views_compiled_and_bound": report["compile"]["status"] == "pass",
|
||||
"first_start_passed": True,
|
||||
"first_process_stopped": first["process_group_absent_after_wait"],
|
||||
"pre_restart_verification_passed": True,
|
||||
"restart_profile_recompiled_from_manifest": report["restart_compile"]["status"] == "pass",
|
||||
"restart_profile_started_without_sessions": report["restart_compile"][
|
||||
"session_directories_started_empty"
|
||||
],
|
||||
"restart_passed": True,
|
||||
"restart_process_is_distinct": first_payload["process_id"] != second_payload["process_id"]
|
||||
and first_payload["runtime_instance_id"] != second_payload["runtime_instance_id"],
|
||||
"identity_inputs_reproduced": first_payload["identity_inputs_sha256"]
|
||||
== second_payload["identity_inputs_sha256"],
|
||||
"manifest_reproduced": first_payload["manifest_sha256"] == second_payload["manifest_sha256"],
|
||||
"query_reproduced": first_payload["query_sha256"] == second_payload["query_sha256"],
|
||||
"answer_and_provenance_explainable": first_payload["answer_sha256"] == second_payload["answer_sha256"]
|
||||
and first_payload["output_provenance"] == second_payload["output_provenance"],
|
||||
"session_excluded_from_identity": report["session_boundary_readback"][
|
||||
"identity_runtime_unchanged_after_session"
|
||||
],
|
||||
"drift_failed_closed": drift["fail_closed"] and not drift["answer_returned"],
|
||||
"drift_profile_started_without_sessions": report["drift_compile"]["session_directories_started_empty"],
|
||||
"drift_process_stopped": drift["process_group_absent_after_wait"],
|
||||
"drift_process_had_no_orphan_cleanup": drift["orphan_cleanup_required"] is False,
|
||||
"restart_process_stopped": second["process_group_absent_after_wait"],
|
||||
}
|
||||
report["status"] = "pass" if all(report["gates"].values()) else "fail"
|
||||
if report["status"] == "pass":
|
||||
exit_code = 0
|
||||
except (CanaryError, identity.IdentityManifestError, OSError, subprocess.SubprocessError) as exc:
|
||||
report["status"] = "fail"
|
||||
report["errors"].append(
|
||||
{
|
||||
"type": type(exc).__name__,
|
||||
"message": "details_redacted",
|
||||
"detail_sha256": behavior.sha256_bytes(str(exc).encode("utf-8")),
|
||||
}
|
||||
)
|
||||
finally:
|
||||
shutil.rmtree(temporary_root, ignore_errors=True)
|
||||
report["cleanup"] = {
|
||||
"temporary_root_removed": not temporary_root.exists(),
|
||||
"no_profile_retained": not temporary_root.exists(),
|
||||
}
|
||||
if not all(report["cleanup"].values()):
|
||||
report["status"] = "fail"
|
||||
exit_code = 1
|
||||
positive_restart_passed = report.get("status") == "pass" and not args.inject_drift_before_restart
|
||||
report["current_tier"] = "T2_runtime" if positive_restart_passed else "T1_model"
|
||||
report["goal_tier_satisfied"] = positive_restart_passed
|
||||
report["runtime_component_proven"] = (
|
||||
"fresh_profile_recompile_plus_distinct_process_restart"
|
||||
if positive_restart_passed
|
||||
else "first_local_process_plus_pre_restart_drift_refusal"
|
||||
)
|
||||
report["runtime_variant"] = "local_deterministic_identity_compiler_readback"
|
||||
report["tier_basis"] = (
|
||||
"Capability Tier Proof defines T2_runtime as local API/runtime behavior with restart where relevant; "
|
||||
"this is compiler-process runtime proof and explicitly excludes GatewayRunner and hosted-model proof."
|
||||
if positive_restart_passed
|
||||
else "This negative component does not complete the required restart path, so it remains below T2_runtime."
|
||||
)
|
||||
report["claim_ceiling"] = (
|
||||
"Local disposable identity compilation, fresh-profile reconstruction, and cold-process restart only; "
|
||||
"not a live GatewayRunner, hosted-model, Telegram, production database, VPS restart, or T3 proof."
|
||||
if positive_restart_passed
|
||||
else "Local first-process and pre-restart drift-refusal component only; no successful restart proof, "
|
||||
"GatewayRunner, hosted model, production database, VPS, or T3 claim."
|
||||
)
|
||||
_validate_portable_receipt(report)
|
||||
stable = {key: value for key, value in report.items() if key != "receipt_sha256"}
|
||||
report["receipt_sha256"] = behavior.canonical_sha256(stable)
|
||||
identity.write_json(output, report)
|
||||
return report, exit_code
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--profile-template", type=Path, required=True)
|
||||
parser.add_argument("--hermes-root", type=Path, required=True)
|
||||
parser.add_argument("--deployment-root", type=Path, required=True)
|
||||
parser.add_argument("--source-root", type=Path, required=True)
|
||||
parser.add_argument("--database-fingerprint", type=Path, required=True)
|
||||
parser.add_argument("--constitution", type=Path, required=True)
|
||||
parser.add_argument("--database-identity", type=Path, required=True)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--inject-drift-before-restart", action="store_true")
|
||||
args = parser.parse_args()
|
||||
report, exit_code = run_canary(args)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"status": report.get("status"),
|
||||
"current_tier": report.get("current_tier"),
|
||||
"receipt_sha256": report.get("receipt_sha256"),
|
||||
"output": str(args.output),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return exit_code
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import json
|
|||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts import leo_behavior_manifest as manifest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
|
@ -58,6 +60,11 @@ gateway:
|
|||
assert '{"private":"conversation"}' not in encoded
|
||||
|
||||
|
||||
def test_identity_config_rejects_nonboolean_smart_model_routing() -> None:
|
||||
with pytest.raises(ValueError, match="smart_model_routing must be boolean"):
|
||||
manifest.identity_config_projection({"smart_model_routing": {"route": "untrusted"}})
|
||||
|
||||
|
||||
def test_behavior_manifest_changes_when_a_behavior_layer_changes(tmp_path: Path) -> None:
|
||||
profile = tmp_path / "profile"
|
||||
hermes = tmp_path / "hermes"
|
||||
|
|
@ -156,3 +163,64 @@ def test_git_state_marks_untracked_files_dirty(tmp_path: Path) -> None:
|
|||
|
||||
assert state["worktree_clean"] is False
|
||||
assert len(str(state["status_sha256"])) == 64
|
||||
|
||||
|
||||
def test_identity_runtime_omits_secret_values_but_pins_semantic_model_settings(tmp_path: Path) -> None:
|
||||
profile = tmp_path / "profile"
|
||||
hermes = tmp_path / "hermes"
|
||||
deployment = tmp_path / "deployment"
|
||||
config = """model:
|
||||
provider: fixture
|
||||
default: identity-v1
|
||||
max_tokens: 512
|
||||
gateway:
|
||||
telegram:
|
||||
bot_token: token-a
|
||||
botToken: camel-token-a
|
||||
provider:
|
||||
apiKey: camel-api-key-a
|
||||
transport:
|
||||
endpoint: https://fixture-user:fixture-password-a@example.invalid/v1
|
||||
headers: ['Authorization: Bearer fixture-header-a']
|
||||
tools:
|
||||
allowed: [identity_readback]
|
||||
identity_runtime:
|
||||
network_send_enabled: false
|
||||
database_write_enabled: false
|
||||
allowed_tools: [identity_readback]
|
||||
"""
|
||||
_write(profile / "config.yaml", config)
|
||||
_write(profile / "skills" / "identity" / "SKILL.md", "identity\n")
|
||||
_write(profile / "plugins" / "identity" / "__init__.py", "BOUNDARY = True\n")
|
||||
_write(profile / "bin" / "identity.py", "READ_ONLY = True\n")
|
||||
_write(hermes / "run_agent.py", "runtime\n")
|
||||
_write(hermes / "agent" / "prompt_builder.py", "prompts\n")
|
||||
for name in (
|
||||
"leo_behavior_manifest.py",
|
||||
"leo_identity_manifest.py",
|
||||
"leo_identity_profile.py",
|
||||
"run_leo_identity_reconstruction_canary.py",
|
||||
):
|
||||
_write(deployment / "scripts" / name, f"# {name}\n")
|
||||
|
||||
before = manifest.build_manifest(profile, hermes, deployment)
|
||||
_write(
|
||||
profile / "config.yaml",
|
||||
config.replace("token-a", "token-b")
|
||||
.replace("camel-api-key-a", "camel-api-key-b")
|
||||
.replace("fixture-password-a", "fixture-password-b")
|
||||
.replace("fixture-header-a", "fixture-header-b"),
|
||||
)
|
||||
secret_rotated = manifest.build_manifest(profile, hermes, deployment)
|
||||
_write(profile / "config.yaml", config.replace("max_tokens: 512", "max_tokens: 1024"))
|
||||
model_changed = manifest.build_manifest(profile, hermes, deployment)
|
||||
|
||||
assert before["behavior_sha256"] != secret_rotated["behavior_sha256"]
|
||||
assert before["identity_runtime_sha256"] == secret_rotated["identity_runtime_sha256"]
|
||||
assert before["identity_runtime_sha256"] != model_changed["identity_runtime_sha256"]
|
||||
encoded = json.dumps(before)
|
||||
assert "token-a" not in encoded
|
||||
assert "camel-token-a" not in encoded
|
||||
assert "camel-api-key-a" not in encoded
|
||||
assert "fixture-password-a" not in encoded
|
||||
assert "fixture-header-a" not in encoded
|
||||
|
|
|
|||
794
tests/test_leo_identity_manifest.py
Normal file
794
tests/test_leo_identity_manifest.py
Normal file
|
|
@ -0,0 +1,794 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path, PureWindowsPath
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts import leo_behavior_manifest as behavior
|
||||
from scripts import leo_identity_manifest as identity
|
||||
from scripts import leo_identity_profile as profile_runtime
|
||||
from scripts import run_leo_identity_reconstruction_canary as canary
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
COMMITTED_RECEIPTS = (
|
||||
ROOT / "docs/reports/leo-working-state-20260709/leo-reproducible-identity-t2-current.json",
|
||||
ROOT / "docs/reports/leo-working-state-20260709/leo-reproducible-identity-drift-negative-current.json",
|
||||
)
|
||||
|
||||
|
||||
def _write(path: Path, value: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(value, encoding="utf-8")
|
||||
|
||||
|
||||
def _write_json(path: Path, value: dict) -> None:
|
||||
_write(path, json.dumps(value, indent=2, sort_keys=True) + "\n")
|
||||
|
||||
|
||||
def _assert_path_portable_receipt(value: object) -> None:
|
||||
canary._validate_portable_receipt(value)
|
||||
|
||||
|
||||
def _assert_logical_child_execution(child: dict, *, profile_role: str) -> None:
|
||||
logical_command = child["logical_command"]
|
||||
assert "command" not in child
|
||||
assert child["profile_role"] == profile_role
|
||||
assert child["command_serialization"] == "logical_redacted_v1"
|
||||
assert child["actual_argv_persisted"] is False
|
||||
assert logical_command[0] == "<python>"
|
||||
assert logical_command[logical_command.index("--profile") + 1] == "<temporary-profile>"
|
||||
_assert_path_portable_receipt(child)
|
||||
|
||||
|
||||
def _fixture(tmp_path: Path) -> dict[str, Path | dict]:
|
||||
repo = tmp_path / "repo"
|
||||
profile = repo / "profile-template"
|
||||
hermes = repo / "hermes-runtime"
|
||||
compiled = tmp_path / "compiled-profile"
|
||||
_write(
|
||||
profile / "config.yaml",
|
||||
"""model:
|
||||
provider: fixture-local
|
||||
default: identity-v1
|
||||
max_tokens: 512
|
||||
memory:
|
||||
enabled: false
|
||||
gateway:
|
||||
execution_mode: local_no_send
|
||||
telegram:
|
||||
bot_token: never-emit-this-fixture-value
|
||||
transport:
|
||||
endpoint: https://fixture-user:fixture-password@example.invalid/v1
|
||||
headers: ['Authorization: Bearer fixture-secret']
|
||||
tools:
|
||||
allowed: [identity_readback]
|
||||
write_enabled: false
|
||||
identity_runtime:
|
||||
network_send_enabled: false
|
||||
database_write_enabled: false
|
||||
allowed_tools: [identity_readback]
|
||||
""",
|
||||
)
|
||||
_write(profile / "skills" / "identity" / "SKILL.md", "# identity readback\n")
|
||||
_write(profile / "plugins" / "identity" / "__init__.py", "BOUNDARY = True\n")
|
||||
_write(profile / "bin" / "identity_readback.py", "READ_ONLY = True\n")
|
||||
_write(hermes / "run_agent.py", "RUNTIME = 'identity-v1'\n")
|
||||
_write(hermes / "agent" / "prompt_builder.py", "SOUL_IS_GENERATED = True\n")
|
||||
_write(hermes / "gateway" / "run.py", "SESSION_IS_IDENTITY = False\n")
|
||||
_write(hermes / "hermes_cli" / "tools_config.py", "TOOLS = ('identity_readback',)\n")
|
||||
_write(hermes / "tools" / "registry.py", "READ_ONLY = True\n")
|
||||
for name in (
|
||||
"leo_behavior_manifest.py",
|
||||
"leo_identity_manifest.py",
|
||||
"leo_identity_profile.py",
|
||||
"run_leo_identity_reconstruction_canary.py",
|
||||
):
|
||||
_write(repo / "scripts" / name, (ROOT / "scripts" / name).read_text(encoding="utf-8"))
|
||||
|
||||
fingerprint_path = repo / "fixtures" / "database-fingerprint.json"
|
||||
constitution_path = repo / "fixtures" / "constitution.json"
|
||||
database_identity_path = repo / "fixtures" / "database-identity.json"
|
||||
fingerprint = {
|
||||
"schema": identity.DATABASE_FINGERPRINT_SCHEMA,
|
||||
"status": "ok",
|
||||
"environment": "disposable_fixture",
|
||||
"fingerprint_sha256": "1" * 64,
|
||||
"table_rows_sha256": "2" * 64,
|
||||
"structure_sha256": "3" * 64,
|
||||
"table_count": 2,
|
||||
"total_rows": 2,
|
||||
"read_consistency": {
|
||||
"status": "stable_wal_marker",
|
||||
"before": {
|
||||
"database": "fixture",
|
||||
"database_user": "reader",
|
||||
"system_identifier": "12345",
|
||||
"wal_lsn": "0/1",
|
||||
},
|
||||
"after": {
|
||||
"database": "fixture",
|
||||
"database_user": "reader",
|
||||
"system_identifier": "12345",
|
||||
"wal_lsn": "0/1",
|
||||
},
|
||||
},
|
||||
}
|
||||
constitution = {
|
||||
"schema": identity.CONSTITUTION_SCHEMA,
|
||||
"identity_name": "Leo",
|
||||
"rules": [
|
||||
{"id": "evidence", "text": "Never use temporary session memory as canonical evidence."},
|
||||
{"id": "truth", "text": "Fail closed when a pinned identity input drifts."},
|
||||
],
|
||||
}
|
||||
database_identity = {
|
||||
"schema": identity.DATABASE_IDENTITY_SCHEMA,
|
||||
"identity_name": "Leo",
|
||||
"database_fingerprint_sha256": "1" * 64,
|
||||
"source_provenance": {
|
||||
"mode": identity.SYNTHETIC_DATABASE_MODE,
|
||||
"canonical_authority_granted": False,
|
||||
"same_transaction_as_fingerprint": False,
|
||||
"export_receipt_sha256": None,
|
||||
},
|
||||
"records": [
|
||||
{
|
||||
"table": "public.personas",
|
||||
"row_id": "persona-1",
|
||||
"kind": "persona",
|
||||
"rank": 0,
|
||||
"text": "Leo is an evidence-bound reasoning interface.",
|
||||
"status": "active",
|
||||
"evidence_sha256": "a" * 64,
|
||||
},
|
||||
{
|
||||
"table": "public.beliefs",
|
||||
"row_id": "belief-1",
|
||||
"kind": "belief",
|
||||
"rank": 0,
|
||||
"text": "Exact provenance is stronger than plausible recollection.",
|
||||
"status": "active",
|
||||
"evidence_sha256": "b" * 64,
|
||||
},
|
||||
],
|
||||
}
|
||||
_write_json(fingerprint_path, fingerprint)
|
||||
_write_json(constitution_path, constitution)
|
||||
_write_json(database_identity_path, database_identity)
|
||||
|
||||
subprocess.run(["git", "init", "-q", str(repo)], check=True)
|
||||
subprocess.run(["git", "-C", str(repo), "config", "user.email", "test@example.com"], check=True)
|
||||
subprocess.run(["git", "-C", str(repo), "config", "user.name", "Test"], check=True)
|
||||
subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
|
||||
subprocess.run(
|
||||
["git", "-C", str(repo), "commit", "-qm", "fixture"],
|
||||
check=True,
|
||||
env={
|
||||
**dict(__import__("os").environ),
|
||||
"GIT_AUTHOR_DATE": "2026-01-01T00:00:00Z",
|
||||
"GIT_COMMITTER_DATE": "2026-01-01T00:00:00Z",
|
||||
},
|
||||
)
|
||||
behavior_manifest = behavior.build_manifest(profile, hermes, repo)
|
||||
manifest = identity.build_identity_manifest(
|
||||
behavior_manifest=behavior_manifest,
|
||||
database_fingerprint_path=fingerprint_path,
|
||||
constitution_path=constitution_path,
|
||||
database_identity_path=database_identity_path,
|
||||
source_root=repo,
|
||||
)
|
||||
return {
|
||||
"repo": repo,
|
||||
"profile": profile,
|
||||
"hermes": hermes,
|
||||
"compiled": compiled,
|
||||
"fingerprint": fingerprint_path,
|
||||
"constitution": constitution_path,
|
||||
"database_identity": database_identity_path,
|
||||
"behavior": behavior_manifest,
|
||||
"manifest": manifest,
|
||||
}
|
||||
|
||||
|
||||
def _fully_rehash_manifest(fixture: dict[str, Path | dict], manifest: dict) -> None:
|
||||
identity_hash = behavior.canonical_sha256(manifest["identity_inputs"])
|
||||
manifest["identity_inputs_sha256"] = identity_hash
|
||||
fingerprint = identity.load_json(fixture["fingerprint"], "database fingerprint")
|
||||
rules = identity.canonical_rules(identity.load_json(fixture["constitution"], "constitution"))
|
||||
records = identity.canonical_database_records(
|
||||
identity.load_json(fixture["database_identity"], "database identity"),
|
||||
fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||
)
|
||||
database_provenance = identity.database_identity_provenance(
|
||||
identity.load_json(fixture["database_identity"], "database identity"),
|
||||
fingerprint=fingerprint,
|
||||
)
|
||||
rendered = identity.render_identity_views(
|
||||
identity_inputs_sha256=identity_hash,
|
||||
database_fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||
database_provenance=database_provenance,
|
||||
rules=rules,
|
||||
records=records,
|
||||
)
|
||||
for name, content in rendered.items():
|
||||
manifest["compiled_views"][name]["sha256"] = behavior.sha256_bytes(content.encode("utf-8"))
|
||||
manifest["compiled_views"][name]["generated_from_identity_inputs_sha256"] = identity_hash
|
||||
stable = {key: value for key, value in manifest.items() if key != "manifest_sha256"}
|
||||
manifest["manifest_sha256"] = behavior.canonical_sha256(stable)
|
||||
|
||||
|
||||
def _rehash_behavior_manifest(manifest: dict) -> None:
|
||||
manifest["identity_runtime_sha256"] = behavior.canonical_sha256(behavior.identity_runtime_payload(manifest))
|
||||
manifest["static_runtime_sha256"] = behavior.canonical_sha256(behavior.static_runtime_payload(manifest))
|
||||
manifest["behavior_sha256"] = behavior.canonical_sha256(behavior.stable_manifest_payload(manifest))
|
||||
|
||||
|
||||
def _canary_args(fixture: dict[str, Path | dict], output: Path, *, inject_drift: bool = False) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
profile_template=fixture["profile"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
source_root=fixture["repo"],
|
||||
database_fingerprint=fixture["fingerprint"],
|
||||
constitution=fixture["constitution"],
|
||||
database_identity=fixture["database_identity"],
|
||||
output=output,
|
||||
inject_drift_before_restart=inject_drift,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"private_path",
|
||||
[
|
||||
"/" + "Users/operator/checkout/profile",
|
||||
"/" + "private/tmp/lane/.venv/bin/python",
|
||||
"/" + "var/folders/cache/temporary-profile",
|
||||
"/" + "tmp/temporary-profile",
|
||||
"/" + "opt/checkout/interpreter",
|
||||
"/" + "workspace/checkout/temporary-profile",
|
||||
str(PureWindowsPath("C:/operator/checkout/temporary-profile")),
|
||||
"\\\\" + "server\\share\\checkout\\temporary-profile",
|
||||
],
|
||||
)
|
||||
def test_receipt_path_guard_recursively_rejects_private_and_absolute_paths(private_path: str) -> None:
|
||||
with pytest.raises(canary.CanaryError, match="private or absolute filesystem path"):
|
||||
_assert_path_portable_receipt({"outer": [{"logical_command": ["<python>", private_path]}]})
|
||||
|
||||
|
||||
def test_receipt_path_guard_rejects_an_absolute_path_in_a_nested_key() -> None:
|
||||
private_key = "/" + "tmp/checkout/temporary-profile"
|
||||
with pytest.raises(canary.CanaryError, match="private or absolute filesystem path"):
|
||||
_assert_path_portable_receipt({"outer": [{private_key: "<temporary-profile>"}]})
|
||||
|
||||
|
||||
def test_logical_command_uses_placeholders_for_paths_outside_the_deployment_root(tmp_path: Path) -> None:
|
||||
logical_command = canary._logical_query_command(
|
||||
deployment_root=tmp_path / "checkout",
|
||||
source_root=tmp_path / "outside-source",
|
||||
hermes_root=tmp_path / "outside-hermes",
|
||||
)
|
||||
|
||||
assert logical_command[logical_command.index("--source-root") + 1] == "<source-root>"
|
||||
assert logical_command[logical_command.index("--hermes-root") + 1] == "<hermes-root>"
|
||||
_assert_path_portable_receipt(logical_command)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("receipt_path", COMMITTED_RECEIPTS, ids=lambda path: path.name)
|
||||
def test_committed_identity_receipts_are_path_portable_and_self_hashed(receipt_path: Path) -> None:
|
||||
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
||||
_assert_path_portable_receipt(receipt)
|
||||
stable = {key: value for key, value in receipt.items() if key != "receipt_sha256"}
|
||||
assert behavior.canonical_sha256(stable) == receipt["receipt_sha256"]
|
||||
|
||||
|
||||
def test_manifest_compiles_generated_views_and_reproduces_identity_across_queries(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
manifest = fixture["manifest"]
|
||||
assert isinstance(manifest, dict)
|
||||
rebuilt = identity.build_identity_manifest(
|
||||
behavior_manifest=fixture["behavior"],
|
||||
database_fingerprint_path=fixture["fingerprint"],
|
||||
constitution_path=fixture["constitution"],
|
||||
database_identity_path=fixture["database_identity"],
|
||||
source_root=fixture["repo"],
|
||||
)
|
||||
assert rebuilt == manifest
|
||||
|
||||
compile_receipt = profile_runtime.compile_profile(
|
||||
manifest,
|
||||
source_root=fixture["repo"],
|
||||
profile_template=fixture["profile"],
|
||||
profile=fixture["compiled"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
first = profile_runtime.query_profile(
|
||||
fixture["compiled"],
|
||||
query=profile_runtime.FIXED_QUERY,
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
second = profile_runtime.query_profile(
|
||||
fixture["compiled"],
|
||||
query=profile_runtime.FIXED_QUERY,
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
|
||||
assert compile_receipt["status"] == "pass"
|
||||
assert "never-emit-this-fixture-value" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8")
|
||||
assert "fixture-password" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8")
|
||||
assert "fixture-secret" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8")
|
||||
assert (fixture["compiled"] / "SOUL.md").read_text(encoding="utf-8").startswith("<!-- GENERATED FILE")
|
||||
assert first["manifest_sha256"] == second["manifest_sha256"]
|
||||
assert first["identity_inputs_sha256"] == second["identity_inputs_sha256"]
|
||||
assert first["answer_sha256"] == second["answer_sha256"]
|
||||
assert first["output_provenance"] == second["output_provenance"]
|
||||
assert first["runtime_instance_id"] != second["runtime_instance_id"]
|
||||
assert first["session"]["included_in_output_provenance"] is False
|
||||
assert first["output_provenance"]["database_canonical_authority_granted"] is False
|
||||
assert first["authorization"]["authorization_decision_observed"] is False
|
||||
assert "synthetic noncanonical fixture" in first["answer"]
|
||||
assert len(list((fixture["compiled"] / "sessions").glob("*.json"))) == 2
|
||||
|
||||
|
||||
def test_profile_and_bound_source_drift_fail_closed(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
profile_runtime.compile_profile(
|
||||
fixture["manifest"],
|
||||
source_root=fixture["repo"],
|
||||
profile_template=fixture["profile"],
|
||||
profile=fixture["compiled"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
with (fixture["compiled"] / "SOUL.md").open("a", encoding="utf-8") as handle:
|
||||
handle.write("drift\n")
|
||||
with pytest.raises(identity.IdentityManifestError, match="compiled view drift detected"):
|
||||
profile_runtime.query_profile(
|
||||
fixture["compiled"],
|
||||
query=profile_runtime.FIXED_QUERY,
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
|
||||
_write(fixture["constitution"], fixture["constitution"].read_text(encoding="utf-8") + "\n")
|
||||
with pytest.raises(identity.IdentityManifestError, match="constitution source content drift"):
|
||||
identity.verify_bound_sources(fixture["manifest"], fixture["repo"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("relative_path", "content", "message"),
|
||||
[
|
||||
("memories/USER.md", "untrusted memory\n", "forbidden nonauthoritative"),
|
||||
("MEMORY.md", "untrusted top-level memory\n", "forbidden nonauthoritative"),
|
||||
("USER.md", "untrusted top-level user memory\n", "forbidden nonauthoritative"),
|
||||
("platforms/pairing/telegram.json", "{}\n", "forbidden nonauthoritative"),
|
||||
(".env", "LEO_SECRET=untrusted\n", "profile entry set is not exact"),
|
||||
("gateway.json", "{}\n", "profile entry set is not exact"),
|
||||
("unexpected.txt", "untrusted input\n", "profile entry set is not exact"),
|
||||
("sessions/injected.txt", "untrusted session\n", "unsafe session entry"),
|
||||
("state/injected.json", "{}\n", "state directory must remain empty"),
|
||||
],
|
||||
)
|
||||
def test_profile_rechecks_nonauthoritative_surfaces_before_every_query(
|
||||
tmp_path: Path, relative_path: str, content: str, message: str
|
||||
) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
profile_runtime.compile_profile(
|
||||
fixture["manifest"],
|
||||
source_root=fixture["repo"],
|
||||
profile_template=fixture["profile"],
|
||||
profile=fixture["compiled"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
_write(fixture["compiled"] / relative_path, content)
|
||||
|
||||
with pytest.raises(profile_runtime.IdentityProfileError, match=message):
|
||||
profile_runtime.query_profile(
|
||||
fixture["compiled"],
|
||||
query=profile_runtime.FIXED_QUERY,
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
|
||||
|
||||
def test_manifest_rejects_rehashed_core_tampering(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
tampered = copy.deepcopy(fixture["manifest"])
|
||||
tampered["identity_inputs"]["session_boundary"]["may_be_used_as_evidence"] = True
|
||||
stable = {key: value for key, value in tampered.items() if key != "manifest_sha256"}
|
||||
tampered["manifest_sha256"] = behavior.canonical_sha256(stable)
|
||||
|
||||
with pytest.raises(identity.IdentityManifestError, match="identity input hash drift"):
|
||||
identity.validate_identity_manifest(tampered)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "value", "message"),
|
||||
[
|
||||
(("permissions", "runtime_policy", "network_send_enabled"), True, "exact local readback policy"),
|
||||
(("session_boundary", "may_be_used_as_evidence"), True, "session authority boundary"),
|
||||
],
|
||||
)
|
||||
def test_manifest_rejects_fully_rehashed_safety_invariant_tampering(
|
||||
tmp_path: Path, path: tuple[str, ...], value: object, message: str
|
||||
) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
tampered = copy.deepcopy(fixture["manifest"])
|
||||
target = tampered["identity_inputs"]
|
||||
for key in path[:-1]:
|
||||
target = target[key]
|
||||
target[path[-1]] = value
|
||||
_fully_rehash_manifest(fixture, tampered)
|
||||
|
||||
with pytest.raises(identity.IdentityManifestError, match=message):
|
||||
identity.verify_bound_sources(tampered, fixture["repo"])
|
||||
|
||||
|
||||
def test_behavior_manifest_rejects_unhashed_model_claim_tampering(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
tampered_behavior = copy.deepcopy(fixture["behavior"])
|
||||
tampered_behavior["model_runtime"]["default_model"] = "tampered-unobserved-model"
|
||||
|
||||
with pytest.raises(identity.IdentityManifestError, match="behavior identity runtime hash drift"):
|
||||
identity.build_identity_manifest(
|
||||
behavior_manifest=tampered_behavior,
|
||||
database_fingerprint_path=fixture["fingerprint"],
|
||||
constitution_path=fixture["constitution"],
|
||||
database_identity_path=fixture["database_identity"],
|
||||
source_root=fixture["repo"],
|
||||
)
|
||||
|
||||
|
||||
def test_profile_rejects_a_dangling_extra_symlink(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
profile_runtime.compile_profile(
|
||||
fixture["manifest"],
|
||||
source_root=fixture["repo"],
|
||||
profile_template=fixture["profile"],
|
||||
profile=fixture["compiled"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
(fixture["compiled"] / ".env").symlink_to(tmp_path / "missing-env")
|
||||
|
||||
with pytest.raises(profile_runtime.IdentityProfileError, match="profile entry set is not exact"):
|
||||
profile_runtime.query_profile(
|
||||
fixture["compiled"],
|
||||
query=profile_runtime.FIXED_QUERY,
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
|
||||
|
||||
def test_profile_rejects_a_symlink_in_an_allowed_entry(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
profile_runtime.compile_profile(
|
||||
fixture["manifest"],
|
||||
source_root=fixture["repo"],
|
||||
profile_template=fixture["profile"],
|
||||
profile=fixture["compiled"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
external_config = tmp_path / "external-config.yaml"
|
||||
external_config.write_bytes((fixture["compiled"] / "config.yaml").read_bytes())
|
||||
(fixture["compiled"] / "config.yaml").unlink()
|
||||
(fixture["compiled"] / "config.yaml").symlink_to(external_config)
|
||||
|
||||
with pytest.raises(profile_runtime.IdentityProfileError, match="profile entries must not be symlinks"):
|
||||
profile_runtime.query_profile(
|
||||
fixture["compiled"],
|
||||
query=profile_runtime.FIXED_QUERY,
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mutation", "message"),
|
||||
[
|
||||
("unreadable_file", "unreadable inputs"),
|
||||
("symlink", "unsupported symlinks"),
|
||||
("forged_structure", "is empty"),
|
||||
],
|
||||
)
|
||||
def test_behavior_manifest_rejects_unreplayable_identity_components(
|
||||
tmp_path: Path, mutation: str, message: str
|
||||
) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
tampered_behavior = copy.deepcopy(fixture["behavior"])
|
||||
content = tampered_behavior["components"]["procedural_skills"]["content"]
|
||||
if mutation == "unreadable_file":
|
||||
content["files"][0].pop("sha256")
|
||||
content["files"][0]["status"] = "unavailable"
|
||||
elif mutation == "symlink":
|
||||
content["symlinks"].append(
|
||||
{
|
||||
"path": "skills/identity/link",
|
||||
"target": "/outside/checkout",
|
||||
"target_fingerprint": {"kind": "unavailable", "error": "FileNotFoundError"},
|
||||
}
|
||||
)
|
||||
else:
|
||||
content["files"] = []
|
||||
content["sha256"] = behavior.canonical_sha256({key: content[key] for key in ("files", "missing", "symlinks")})
|
||||
_rehash_behavior_manifest(tampered_behavior)
|
||||
|
||||
with pytest.raises(identity.IdentityManifestError, match=message):
|
||||
identity.validate_behavior_manifest(tampered_behavior)
|
||||
|
||||
|
||||
def test_fully_rehashed_model_claim_must_match_observed_runtime(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
tampered = copy.deepcopy(fixture["manifest"])
|
||||
tampered["identity_inputs"]["model"]["default_model"] = "tampered-unobserved-model"
|
||||
_fully_rehash_manifest(fixture, tampered)
|
||||
identity.validate_identity_manifest(tampered)
|
||||
|
||||
with pytest.raises(
|
||||
profile_runtime.IdentityProfileError, match="model runtime binding drift detected: default_model"
|
||||
):
|
||||
profile_runtime.compile_profile(
|
||||
tampered,
|
||||
source_root=fixture["repo"],
|
||||
profile_template=fixture["profile"],
|
||||
profile=fixture["compiled"],
|
||||
hermes_root=fixture["hermes"],
|
||||
deployment_root=fixture["repo"],
|
||||
)
|
||||
assert not fixture["compiled"].exists()
|
||||
|
||||
|
||||
def test_restart_canary_uses_distinct_processes_and_cleans_up(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
output = tmp_path / "restart-receipt.json"
|
||||
report, exit_code = canary.run_canary(_canary_args(fixture, output))
|
||||
|
||||
assert exit_code == 0
|
||||
assert report["status"] == "pass"
|
||||
assert report["gates"]["restart_process_is_distinct"] is True
|
||||
assert report["gates"]["restart_profile_recompiled_from_manifest"] is True
|
||||
assert report["restart_compile"]["session_directories_started_empty"] is True
|
||||
assert report["drift_compile"]["session_directories_started_empty"] is True
|
||||
_assert_logical_child_execution(report["first_start"], profile_role="first_start")
|
||||
_assert_logical_child_execution(report["restart"], profile_role="restart")
|
||||
_assert_logical_child_execution(report["drift_negative"], profile_role="drift_negative")
|
||||
_assert_path_portable_receipt(report)
|
||||
assert report["first_start"]["launcher_pid"] != report["restart"]["launcher_pid"]
|
||||
assert report["first_start"]["process_group_absent_after_wait"] is True
|
||||
assert report["restart"]["process_group_absent_after_wait"] is True
|
||||
assert report["drift_negative"]["fail_closed"] is True
|
||||
assert report["drift_negative"]["process_group_absent_after_wait"] is True
|
||||
assert report["goal_tier_satisfied"] is True
|
||||
assert report["current_tier"] == "T2_runtime"
|
||||
assert report["cleanup"]["temporary_root_removed"] is True
|
||||
assert behavior.git_state(fixture["repo"])["worktree_clean"] is True
|
||||
assert json.loads(output.read_text(encoding="utf-8")) == report
|
||||
|
||||
|
||||
def test_restart_canary_rejects_drift_before_second_start(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
output = tmp_path / "drift-receipt.json"
|
||||
report, exit_code = canary.run_canary(_canary_args(fixture, output, inject_drift=True))
|
||||
|
||||
assert exit_code == 0
|
||||
assert report["status"] == "pass"
|
||||
assert report["gates"]["drift_detected_before_restart"] is True
|
||||
assert report["second_start_attempted"] is False
|
||||
assert "restart" not in report
|
||||
_assert_logical_child_execution(report["first_start"], profile_role="first_start")
|
||||
_assert_logical_child_execution(report["pre_restart_drift"], profile_role="pre_restart_drift")
|
||||
_assert_path_portable_receipt(report)
|
||||
assert report["pre_restart_drift"]["process_group_absent_after_wait"] is True
|
||||
assert all(report["gates"].values())
|
||||
assert report["goal_tier_satisfied"] is False
|
||||
assert report["current_tier"] == "T1_model"
|
||||
assert report["cleanup"]["temporary_root_removed"] is True
|
||||
assert json.loads(output.read_text(encoding="utf-8")) == report
|
||||
|
||||
|
||||
def test_query_child_timeout_terminates_process_group(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
marker = tmp_path / "descendant.pid"
|
||||
sleeping_module = f"""import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
child = subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(60)'])
|
||||
Path({str(marker)!r}).write_text(str(child.pid), encoding='utf-8')
|
||||
time.sleep(60)
|
||||
"""
|
||||
_write(fixture["repo"] / "scripts" / "leo_identity_profile.py", sleeping_module)
|
||||
|
||||
result = canary._run_query_child(
|
||||
deployment_root=fixture["repo"],
|
||||
profile=fixture["compiled"],
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
profile_role="timeout_test",
|
||||
timeout_seconds=0.5,
|
||||
)
|
||||
|
||||
assert result["timed_out"] is True
|
||||
assert result["terminated_with"] in {"SIGTERM", "SIGKILL"}
|
||||
assert result["returncode"] != 0
|
||||
assert result["process_group_absent_after_wait"] is True
|
||||
descendant_pid = int(marker.read_text(encoding="utf-8"))
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
os.kill(descendant_pid, 0)
|
||||
except ProcessLookupError:
|
||||
break
|
||||
time.sleep(0.02)
|
||||
with pytest.raises(ProcessLookupError):
|
||||
os.kill(descendant_pid, 0)
|
||||
|
||||
|
||||
def test_query_child_cleans_and_rejects_orphan_from_completed_launcher(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
marker = tmp_path / "completed-descendant.pid"
|
||||
query_payload = {"schema": profile_runtime.QUERY_RECEIPT_SCHEMA, "status": "pass"}
|
||||
leaking_module = f"""import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
child = subprocess.Popen(
|
||||
[sys.executable, '-c', 'import time; time.sleep(60)'],
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
Path({str(marker)!r}).write_text(str(child.pid), encoding='utf-8')
|
||||
print(json.dumps({query_payload!r}))
|
||||
"""
|
||||
_write(fixture["repo"] / "scripts" / "leo_identity_profile.py", leaking_module)
|
||||
|
||||
result = canary._run_query_child(
|
||||
deployment_root=fixture["repo"],
|
||||
profile=fixture["compiled"],
|
||||
source_root=fixture["repo"],
|
||||
hermes_root=fixture["hermes"],
|
||||
profile_role="orphan_cleanup_test",
|
||||
timeout_seconds=5,
|
||||
)
|
||||
|
||||
assert result["returncode"] == 0
|
||||
assert result["orphan_cleanup_required"] is True
|
||||
assert result["terminated_with"] in {"SIGTERM", "SIGKILL"}
|
||||
assert result["process_group_absent_after_wait"] is True
|
||||
assert canary._query_passed(result) is False
|
||||
|
||||
|
||||
def test_wal_capture_marker_is_not_an_identity_input(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
original = fixture["manifest"]
|
||||
fingerprint = json.loads(fixture["fingerprint"].read_text(encoding="utf-8"))
|
||||
fingerprint["read_consistency"]["before"]["wal_lsn"] = "0/2"
|
||||
fingerprint["read_consistency"]["after"]["wal_lsn"] = "0/2"
|
||||
_write_json(fixture["fingerprint"], fingerprint)
|
||||
rebuilt = identity.build_identity_manifest(
|
||||
behavior_manifest=fixture["behavior"],
|
||||
database_fingerprint_path=fixture["fingerprint"],
|
||||
constitution_path=fixture["constitution"],
|
||||
database_identity_path=fixture["database_identity"],
|
||||
source_root=fixture["repo"],
|
||||
)
|
||||
|
||||
assert rebuilt["identity_inputs_sha256"] == original["identity_inputs_sha256"]
|
||||
assert rebuilt["manifest_sha256"] == original["manifest_sha256"]
|
||||
|
||||
|
||||
def test_database_identity_and_system_markers_are_identity_inputs(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
original = fixture["manifest"]
|
||||
fingerprint = json.loads(fixture["fingerprint"].read_text(encoding="utf-8"))
|
||||
for marker in (fingerprint["read_consistency"]["before"], fingerprint["read_consistency"]["after"]):
|
||||
marker["database"] = "different_database"
|
||||
marker["database_user"] = "different_reader"
|
||||
marker["system_identifier"] = "99999"
|
||||
_write_json(fixture["fingerprint"], fingerprint)
|
||||
rebuilt = identity.build_identity_manifest(
|
||||
behavior_manifest=fixture["behavior"],
|
||||
database_fingerprint_path=fixture["fingerprint"],
|
||||
constitution_path=fixture["constitution"],
|
||||
database_identity_path=fixture["database_identity"],
|
||||
source_root=fixture["repo"],
|
||||
)
|
||||
|
||||
assert rebuilt["identity_inputs_sha256"] != original["identity_inputs_sha256"]
|
||||
with pytest.raises(identity.IdentityManifestError, match="database fingerprint semantic drift"):
|
||||
identity.verify_bound_sources(original, fixture["repo"])
|
||||
|
||||
|
||||
def test_synthetic_database_rows_cannot_claim_canonical_authority(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
database_identity = json.loads(fixture["database_identity"].read_text(encoding="utf-8"))
|
||||
database_identity["source_provenance"] = {
|
||||
"mode": identity.CANONICAL_DATABASE_MODE,
|
||||
"canonical_authority_granted": True,
|
||||
"same_transaction_as_fingerprint": True,
|
||||
"export_receipt_sha256": "f" * 64,
|
||||
}
|
||||
_write_json(fixture["database_identity"], database_identity)
|
||||
|
||||
with pytest.raises(identity.IdentityManifestError, match="independently verified output"):
|
||||
identity.build_identity_manifest(
|
||||
behavior_manifest=fixture["behavior"],
|
||||
database_fingerprint_path=fixture["fingerprint"],
|
||||
constitution_path=fixture["constitution"],
|
||||
database_identity_path=fixture["database_identity"],
|
||||
source_root=fixture["repo"],
|
||||
)
|
||||
|
||||
|
||||
def test_self_hashed_database_export_receipt_cannot_grant_canonical_authority(tmp_path: Path) -> None:
|
||||
fixture = _fixture(tmp_path)
|
||||
fingerprint = json.loads(fixture["fingerprint"].read_text(encoding="utf-8"))
|
||||
fingerprint["environment"] = "canonical_readonly_capture"
|
||||
_write_json(fixture["fingerprint"], fingerprint)
|
||||
database_identity = json.loads(fixture["database_identity"].read_text(encoding="utf-8"))
|
||||
records = identity.canonical_database_records(
|
||||
database_identity,
|
||||
fingerprint_sha256=fingerprint["fingerprint_sha256"],
|
||||
)
|
||||
marker = fingerprint["read_consistency"]["before"]
|
||||
receipt_stable = {
|
||||
"schema": "livingip.untrustedCallerSuppliedExportReceipt.v1",
|
||||
"read_only": True,
|
||||
"same_transaction_as_fingerprint": True,
|
||||
"transaction_isolation": "repeatable_read_read_only",
|
||||
"database": marker["database"],
|
||||
"database_user": marker["database_user"],
|
||||
"system_identifier": marker["system_identifier"],
|
||||
"database_fingerprint_sha256": fingerprint["fingerprint_sha256"],
|
||||
"records_sha256": behavior.canonical_sha256(records),
|
||||
}
|
||||
receipt_hash = behavior.canonical_sha256(receipt_stable)
|
||||
database_identity["source_provenance"] = {
|
||||
"mode": identity.CANONICAL_DATABASE_MODE,
|
||||
"canonical_authority_granted": True,
|
||||
"same_transaction_as_fingerprint": True,
|
||||
"export_receipt_sha256": receipt_hash,
|
||||
}
|
||||
database_identity["export_receipt"] = {**receipt_stable, "receipt_sha256": receipt_hash}
|
||||
_write_json(fixture["database_identity"], database_identity)
|
||||
|
||||
with pytest.raises(identity.IdentityManifestError, match="independently verified output"):
|
||||
identity.build_identity_manifest(
|
||||
behavior_manifest=fixture["behavior"],
|
||||
database_fingerprint_path=fixture["fingerprint"],
|
||||
constitution_path=fixture["constitution"],
|
||||
database_identity_path=fixture["database_identity"],
|
||||
source_root=fixture["repo"],
|
||||
)
|
||||
|
||||
|
||||
def test_same_clean_commit_reproduces_identity_from_different_checkout_paths(tmp_path: Path) -> None:
|
||||
first = _fixture(tmp_path / "checkout-a")
|
||||
second = _fixture(tmp_path / "checkout-b")
|
||||
|
||||
assert (
|
||||
first["behavior"]["teleo_infrastructure_runtime"]["git_head"]
|
||||
== second["behavior"]["teleo_infrastructure_runtime"]["git_head"]
|
||||
)
|
||||
assert first["behavior"]["identity_runtime_sha256"] == second["behavior"]["identity_runtime_sha256"]
|
||||
assert first["manifest"]["identity_inputs_sha256"] == second["manifest"]["identity_inputs_sha256"]
|
||||
assert first["manifest"]["manifest_sha256"] == second["manifest"]["manifest_sha256"]
|
||||
|
|
@ -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,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"}],
|
||||
}
|
||||
)
|
||||
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,
|
||||
},
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
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