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
|
needs the full approved proposal row, immutable approval snapshot, and final
|
||||||
applied proposal row.
|
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
|
`ops/run_local_genesis_ledger_rebuild.py` now executes the first exact
|
||||||
genesis-plus-ledger slice in one command:
|
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
|
must contain every current `kb_stage.kb_proposals` column; partial envelopes
|
||||||
are rejected.
|
are rejected.
|
||||||
|
|
||||||
The command verifies every hash before starting Docker, restores a tmpfs-backed
|
The command verifies every hash before starting Docker, requires a
|
||||||
Postgres with `--network none`, reapplies the current guarded prerequisites,
|
SHA-256-pinned Postgres image (and defaults to a pinned PostgreSQL 16 Alpine
|
||||||
and proves genesis parity. For each entry it seeds the receipt's exact row IDs
|
multi-platform digest), restores it on tmpfs with `--network none`, reapplies
|
||||||
and timestamps in the disposable clone, executes the existing `kb_apply`
|
the current guarded prerequisites, and proves genesis parity. Insert-only
|
||||||
payload-bound guard and ledger transition, checks exact proposal and canonical
|
entries seed the receipt's exact canonical row IDs and timestamps before the
|
||||||
row readbacks, then verifies the complete final parity manifest. Its public
|
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
|
mode-`0600` receipt contains hashes, IDs, types, counts, parity summaries, and
|
||||||
cleanup proof, but no payloads, rows, SQL, source paths, or command errors.
|
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
|
- sequence gaps, hash drift, engine drift, duplicate proposals, and legacy or
|
||||||
freeform payloads fail before container start;
|
freeform payloads fail before container start;
|
||||||
- `revise_strategy` fails closed because its current receipt omits the prior
|
- `revise_strategy` is accepted only when the receipt pins the exact SQL that
|
||||||
strategies and nodes that the apply engine deactivates or retires;
|
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
|
- full proposal before/after rows are mandatory because the current receipt
|
||||||
envelope does not retain proposal origin fields or exact `updated_at`;
|
envelope does not retain proposal origin fields or exact `updated_at`;
|
||||||
- this proves only an isolated local reconstruction. It does not touch or prove
|
- this proves only an isolated local reconstruction. It does not touch or prove
|
||||||
VPS, GCP, Telegram, a live database, or blank-schema source recompilation.
|
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,
|
The source compiler now turns one raw artifact, its strict UTF-8 extraction,
|
||||||
and a reviewed extraction manifest into a deterministic, hash-bound
|
and a reviewed extraction manifest into a deterministic, hash-bound
|
||||||
`pending_review` proposal bundle:
|
`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
|
The existing full-data clone canary separately proves that a reviewed packet
|
||||||
can create source, claim, evidence, and edge rows and affect later reasoning.
|
can create source, claim, evidence, and edge rows and affect later reasoning.
|
||||||
The remaining reconstruction work is to retain complete deltas for mutating
|
The remaining reconstruction work is to backfill or explicitly reject legacy
|
||||||
contracts such as `revise_strategy`, backfill or explicitly reject legacy
|
freeform applies and extend beyond genesis recovery to a blank-schema source
|
||||||
freeform applies, and extend beyond genesis recovery to a blank-schema source
|
compiler. The strict ledger runner does not prove that every historical
|
||||||
compiler. The insert-only ledger runner does not prove that every historical
|
|
||||||
canonical row can be rebuilt semantically from retained sources.
|
canonical row can be rebuilt semantically from retained sources.
|
||||||
|
|
||||||
## Definition Of Working
|
## 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
|
#!/usr/bin/env python3
|
||||||
"""Deterministically rebuild a disposable Postgres from genesis plus a strict ledger.
|
"""Deterministically rebuild a disposable Postgres from genesis plus a strict ledger.
|
||||||
|
|
||||||
The v1 replay boundary is intentionally narrow. It supports strict, insert-only
|
The v1 replay boundary supports strict ``add_edge``, ``attach_evidence``,
|
||||||
``add_edge``, ``attach_evidence``, and ``approve_claim`` receipts. Each ledger
|
``approve_claim``, and ``revise_strategy`` receipts. Insert-only actions seed
|
||||||
entry must also retain the exact approved and applied proposal rows plus the
|
their receipt-pinned rows before the guarded transition. ``revise_strategy``
|
||||||
immutable approval snapshot. Legacy/freeform entries and ``revise_strategy``
|
instead captures the exact prestate identities, executes the exact hash-matched
|
||||||
fail before Docker starts because their retained material is not a complete
|
guarded SQL, validates the generated delta, and normalizes only that delta to the
|
||||||
database delta.
|
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
|
from __future__ import annotations
|
||||||
|
|
@ -46,15 +48,18 @@ LEDGER_ARTIFACT = "teleo_genesis_plus_ledger"
|
||||||
MATERIAL_ARTIFACT = "teleo_strict_proposal_replay_material"
|
MATERIAL_ARTIFACT = "teleo_strict_proposal_replay_material"
|
||||||
LEDGER_CONTRACT_VERSION = 1
|
LEDGER_CONTRACT_VERSION = 1
|
||||||
MATERIAL_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 = (
|
CLAIM_CEILING = (
|
||||||
"exact genesis plus ordered strict insert-only proposal replay; supported types are "
|
"exact genesis plus ordered strict proposal replay; supported types are add_edge, "
|
||||||
"add_edge, attach_evidence, and approve_claim; full approved/applied proposal rows "
|
"attach_evidence, approve_claim, and revise_strategy; mutating strategy replay "
|
||||||
"and immutable approval snapshots are required"
|
"captures prestate identities and normalizes the exact receipt-pinned poststate; "
|
||||||
|
"full approved/applied proposal rows and immutable approval snapshots are required"
|
||||||
)
|
)
|
||||||
NOT_PROVEN = (
|
NOT_PROVEN = (
|
||||||
"legacy or freeform proposal replay",
|
"legacy or freeform proposal replay",
|
||||||
"revise_strategy replay because prior-row mutations are absent from v1 receipts",
|
|
||||||
"source-corpus recompilation from a blank schema",
|
"source-corpus recompilation from a blank schema",
|
||||||
"VPS, GCP, Telegram, or any live database mutation",
|
"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")
|
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(
|
PROPOSAL_TRANSITION_FIELDS = frozenset(
|
||||||
{"status", "applied_by_handle", "applied_by_agent_id", "applied_at", "updated_at"}
|
{"status", "applied_by_handle", "applied_by_agent_id", "applied_at", "updated_at"}
|
||||||
)
|
)
|
||||||
|
|
@ -121,6 +127,35 @@ CANONICAL_TABLE_ORDER = (
|
||||||
"claim_evidence",
|
"claim_evidence",
|
||||||
"claim_edges",
|
"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):
|
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(
|
def _validate_entry_material(
|
||||||
material: dict[str, Any],
|
material: dict[str, Any],
|
||||||
*,
|
*,
|
||||||
|
|
@ -340,11 +485,6 @@ def _validate_entry_material(
|
||||||
if not isinstance(proposal, dict):
|
if not isinstance(proposal, dict):
|
||||||
raise ReconstructionError("invalid_material", "replay receipt has no proposal object")
|
raise ReconstructionError("invalid_material", "replay receipt has no proposal object")
|
||||||
proposal_type = proposal.get("proposal_type")
|
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:
|
if proposal_type not in SUPPORTED_PROPOSAL_TYPES:
|
||||||
raise ReconstructionError(
|
raise ReconstructionError(
|
||||||
"unsupported_proposal_type", "ledger entry proposal type is not supported by v1 reconstruction"
|
"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")
|
canonical_rows = receipt.get("canonical_rows")
|
||||||
if not isinstance(apply_metadata, dict) or not isinstance(canonical_rows, dict):
|
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")
|
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:
|
try:
|
||||||
rebuilt = replay_receipt.build_replay_receipt(
|
rebuilt = replay_receipt.build_replay_receipt(
|
||||||
proposal,
|
proposal,
|
||||||
|
|
@ -382,6 +524,7 @@ def _validate_entry_material(
|
||||||
raise ReconstructionError(
|
raise ReconstructionError(
|
||||||
"replay_material_hash_mismatch", "ledger replay-material pin does not match the private receipt"
|
"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"]
|
approved = material["approved_proposal"]
|
||||||
approval = material["approval_snapshot"]
|
approval = material["approval_snapshot"]
|
||||||
|
|
@ -394,6 +537,15 @@ def _validate_entry_material(
|
||||||
"current_apply_engine_rejected_material",
|
"current_apply_engine_rejected_material",
|
||||||
"current guarded apply engine rejected the strict replay material",
|
"current guarded apply engine rejected the strict replay material",
|
||||||
) from exc
|
) 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(
|
return LedgerEntry(
|
||||||
sequence=expected_sequence,
|
sequence=expected_sequence,
|
||||||
|
|
@ -405,7 +557,7 @@ def _validate_entry_material(
|
||||||
applied_proposal=applied,
|
applied_proposal=applied,
|
||||||
receipt=receipt,
|
receipt=receipt,
|
||||||
current_apply_sql=current_apply_sql,
|
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"
|
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:
|
def build_proposal_readback_sql(proposal_id: str) -> str:
|
||||||
literal = apply_engine.sql_literal(proposal_id)
|
literal = apply_engine.sql_literal(proposal_id)
|
||||||
return f"""select jsonb_build_object(
|
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,
|
"current_apply_sql_sha256": entry.current_apply_sql_sha256,
|
||||||
"expected_row_counts": entry.receipt["expected_row_counts"],
|
"expected_row_counts": entry.receipt["expected_row_counts"],
|
||||||
"seed_exact": False,
|
"seed_exact": False,
|
||||||
|
"proposal_seed_exact": False,
|
||||||
|
"canonical_seed_exact": False,
|
||||||
"guarded_apply_executed": False,
|
"guarded_apply_executed": False,
|
||||||
|
"mutating_prestate_captured": False,
|
||||||
|
"mutating_delta_validated": False,
|
||||||
|
"mutating_poststate_normalized": False,
|
||||||
"proposal_exact": False,
|
"proposal_exact": False,
|
||||||
"canonical_rows_exact": False,
|
"canonical_rows_exact": False,
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
|
|
@ -707,6 +1124,7 @@ def apply_entry(
|
||||||
entry: LedgerEntry,
|
entry: LedgerEntry,
|
||||||
summary: dict[str, Any],
|
summary: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
mutating_prestate: dict[str, Any] | None = None
|
||||||
try:
|
try:
|
||||||
_psql(
|
_psql(
|
||||||
args,
|
args,
|
||||||
|
|
@ -733,20 +1151,34 @@ def apply_entry(
|
||||||
code="entry_seed_mismatch",
|
code="entry_seed_mismatch",
|
||||||
action=f"ledger entry {entry.sequence} proposal seed",
|
action=f"ledger entry {entry.sequence} proposal seed",
|
||||||
)
|
)
|
||||||
seeded_rows = _read_json(
|
summary["proposal_seed_exact"] = True
|
||||||
args,
|
if entry.applied_proposal["proposal_type"] == "revise_strategy":
|
||||||
container,
|
mutating_prestate = _validate_revise_strategy_prestate(
|
||||||
replay_receipt.build_postflight_sql(entry.receipt["proposal"]),
|
_read_json(
|
||||||
code="entry_seed_readback_failed",
|
args,
|
||||||
action=f"ledger entry {entry.sequence} canonical seed readback",
|
container,
|
||||||
)
|
build_revise_strategy_prestate_sql(entry),
|
||||||
_assert_exact_json(
|
code="mutating_prestate_readback_failed",
|
||||||
seeded_rows,
|
action=f"ledger entry {entry.sequence} revise_strategy prestate readback",
|
||||||
entry.receipt["canonical_rows"],
|
)
|
||||||
code="entry_seed_mismatch",
|
)
|
||||||
action=f"ledger entry {entry.sequence} canonical seed",
|
summary["mutating_prestate_captured"] = True
|
||||||
)
|
else:
|
||||||
summary["seed_exact"] = True
|
seeded_rows = _read_json(
|
||||||
|
args,
|
||||||
|
container,
|
||||||
|
replay_receipt.build_postflight_sql(entry.receipt["proposal"]),
|
||||||
|
code="entry_seed_readback_failed",
|
||||||
|
action=f"ledger entry {entry.sequence} canonical seed readback",
|
||||||
|
)
|
||||||
|
_assert_exact_json(
|
||||||
|
seeded_rows,
|
||||||
|
entry.receipt["canonical_rows"],
|
||||||
|
code="entry_seed_mismatch",
|
||||||
|
action=f"ledger entry {entry.sequence} canonical seed",
|
||||||
|
)
|
||||||
|
summary["canonical_seed_exact"] = True
|
||||||
|
summary["seed_exact"] = summary["proposal_seed_exact"] and summary["canonical_seed_exact"]
|
||||||
|
|
||||||
_psql(
|
_psql(
|
||||||
args,
|
args,
|
||||||
|
|
@ -759,6 +1191,27 @@ def apply_entry(
|
||||||
)
|
)
|
||||||
summary["guarded_apply_executed"] = True
|
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(
|
_psql(
|
||||||
args,
|
args,
|
||||||
container,
|
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", required=True, type=Path)
|
||||||
parser.add_argument("--ledger-sha256", required=True)
|
parser.add_argument("--ledger-sha256", required=True)
|
||||||
parser.add_argument("--database", default="teleo")
|
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("--container-prefix", default="teleo-genesis-ledger-rebuild")
|
||||||
parser.add_argument("--tmpfs-mb", default=1024, type=int)
|
parser.add_argument("--tmpfs-mb", default=1024, type=int)
|
||||||
parser.add_argument("--startup-timeout", default=60.0, type=float)
|
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")
|
parser.error("--database must be a safe PostgreSQL identifier up to 63 characters")
|
||||||
if not canonical_rebuild.CONTAINER_PREFIX_RE.fullmatch(args.container_prefix):
|
if not canonical_rebuild.CONTAINER_PREFIX_RE.fullmatch(args.container_prefix):
|
||||||
parser.error("--container-prefix must be 1-40 Docker-safe characters")
|
parser.error("--container-prefix must be 1-40 Docker-safe characters")
|
||||||
if not args.image or any(character.isspace() for character in args.image):
|
if not IMAGE_DIGEST_RE.fullmatch(args.image):
|
||||||
parser.error("--image must be a non-empty Docker image reference without whitespace")
|
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):
|
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")
|
parser.error("--docker-bin must be one executable name or path without whitespace")
|
||||||
if not 64 <= args.tmpfs_mb <= 65536:
|
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;
|
alter table kb_stage.kb_proposal_approvals owner to kb_gate_owner;
|
||||||
|
|
||||||
insert into kb_stage.kb_review_principals
|
insert into kb_stage.kb_review_principals
|
||||||
(db_role, reviewed_by_handle, reviewed_by_agent_id, active)
|
(db_role, reviewed_by_handle, reviewed_by_agent_id, active, created_at)
|
||||||
select 'kb_review'::name, a.handle, a.id, true
|
select 'kb_review'::name, a.handle, a.id, true,
|
||||||
|
'1970-01-01 00:00:00+00'::timestamptz
|
||||||
from public.agents a
|
from public.agents a
|
||||||
where a.handle = 'm3ta'
|
where a.handle = 'm3ta'
|
||||||
on conflict (db_role) do nothing;
|
on conflict (db_role) do nothing;
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,13 @@ from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import importlib.metadata
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
@ -24,6 +28,54 @@ SCHEMA = "livingip.leoBehaviorManifest.v1"
|
||||||
DEFAULT_PROFILE = Path("/home/teleo/.hermes/profiles/leoclean")
|
DEFAULT_PROFILE = Path("/home/teleo/.hermes/profiles/leoclean")
|
||||||
DEFAULT_HERMES_ROOT = Path("/home/teleo/.hermes/hermes-agent")
|
DEFAULT_HERMES_ROOT = Path("/home/teleo/.hermes/hermes-agent")
|
||||||
DEFAULT_DEPLOYMENT_ROOT = Path("/opt/teleo-eval/workspaces/deploy-infra")
|
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:
|
def sha256_bytes(value: bytes) -> str:
|
||||||
|
|
@ -50,7 +102,9 @@ def _safe_relative(path: Path, root: Path) -> str:
|
||||||
return str(path)
|
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."""
|
"""Hash a symlink target, following nested links while stopping directory cycles."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -141,21 +195,139 @@ def _nested(config: dict[str, Any], *path: str) -> Any:
|
||||||
return value
|
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]:
|
def safe_model_config(config_path: Path) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
|
||||||
except (OSError, TypeError, yaml.YAMLError) as exc:
|
except (OSError, TypeError, yaml.YAMLError) as exc:
|
||||||
return {"status": "unavailable", "error": type(exc).__name__}
|
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 {
|
return {
|
||||||
"status": "observed",
|
"status": "observed",
|
||||||
"provider": _nested(raw, "model", "provider"),
|
"config_sha256": file_sha256(config_path),
|
||||||
"default_model": _nested(raw, "model", "default"),
|
"semantic_config_sha256": canonical_sha256(semantic),
|
||||||
"smart_routing": _nested(raw, "model", "smart_routing"),
|
"identity_config_sha256": canonical_sha256(identity_config),
|
||||||
"smart_routing_model": _nested(raw, "model", "smart_routing_model"),
|
"model_section_sha256": canonical_sha256(model_semantic),
|
||||||
"max_tokens": _nested(raw, "model", "max_tokens"),
|
"gateway_permissions_sha256": canonical_sha256(identity_config.get("gateway")),
|
||||||
"reasoning_effort": _nested(raw, "agent", "reasoning_effort"),
|
"tool_permissions_sha256": canonical_sha256(identity_config.get("tools")),
|
||||||
"memory_enabled": _nested(raw, "memory", "enabled"),
|
"memory_section_sha256": canonical_sha256(identity_config.get("memory")),
|
||||||
"memory_search": _nested(raw, "memory", "search"),
|
"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."""
|
"""Hash executable source while excluding generated caches and environments."""
|
||||||
|
|
||||||
allowed_suffixes = {
|
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"}
|
excluded_parts = {".git", ".pytest_cache", "__pycache__", "node_modules", "venv", ".venv"}
|
||||||
files: list[dict[str, Any]] = []
|
files: list[dict[str, Any]] = []
|
||||||
missing: list[str] = []
|
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:
|
for requested in paths:
|
||||||
if not requested.exists() and not requested.is_symlink():
|
if not requested.exists() and not requested.is_symlink():
|
||||||
missing.append(_safe_relative(requested, profile))
|
missing.append(source_label(requested))
|
||||||
continue
|
continue
|
||||||
candidates = [requested] if requested.is_file() else sorted(requested.rglob("*"))
|
candidates = [requested] if requested.is_file() else sorted(requested.rglob("*"))
|
||||||
for path in candidates:
|
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):
|
if not path.is_file() or excluded_parts & set(path.parts):
|
||||||
continue
|
continue
|
||||||
stat = path.stat()
|
stat = path.stat()
|
||||||
|
|
@ -229,14 +416,14 @@ def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, An
|
||||||
continue
|
continue
|
||||||
files.append(
|
files.append(
|
||||||
{
|
{
|
||||||
"path": _safe_relative(path, profile),
|
"path": source_label(path),
|
||||||
"bytes": stat.st_size,
|
"bytes": stat.st_size,
|
||||||
"mode": oct(stat.st_mode & 0o777),
|
"mode": oct(stat.st_mode & 0o777),
|
||||||
"sha256": file_sha256(path),
|
"sha256": file_sha256(path),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
files.sort(key=lambda item: item["path"])
|
files.sort(key=lambda item: item["path"])
|
||||||
stable = {"files": files, "missing": sorted(missing)}
|
stable = {"files": files, "missing": sorted(missing), "symlinks": sorted(symlinks)}
|
||||||
return {
|
return {
|
||||||
**stable,
|
**stable,
|
||||||
"file_count": len(files),
|
"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(
|
def build_manifest(
|
||||||
profile: Path = DEFAULT_PROFILE,
|
profile: Path = DEFAULT_PROFILE,
|
||||||
hermes_root: Path = DEFAULT_HERMES_ROOT,
|
hermes_root: Path = DEFAULT_HERMES_ROOT,
|
||||||
|
|
@ -270,6 +523,12 @@ def build_manifest(
|
||||||
hermes_root = hermes_root.resolve()
|
hermes_root = hermes_root.resolve()
|
||||||
deployment_root = deployment_root.resolve()
|
deployment_root = deployment_root.resolve()
|
||||||
config = safe_model_config(profile / "config.yaml")
|
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 = {
|
components = {
|
||||||
"profile_config": component(
|
"profile_config": component(
|
||||||
profile,
|
profile,
|
||||||
|
|
@ -294,10 +553,10 @@ def build_manifest(
|
||||||
),
|
),
|
||||||
"runtime_middleware": component(
|
"runtime_middleware": component(
|
||||||
profile,
|
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",
|
mutability="deployment_static",
|
||||||
replayability="fully_hashable",
|
replayability="fully_hashable",
|
||||||
paths=(profile / "plugins", hermes_root / "run_agent.py"),
|
paths=(profile / "plugins",),
|
||||||
),
|
),
|
||||||
"database_tools": component(
|
"database_tools": component(
|
||||||
profile,
|
profile,
|
||||||
|
|
@ -353,14 +612,28 @@ def build_manifest(
|
||||||
hermes_root / "hermes_cli",
|
hermes_root / "hermes_cli",
|
||||||
hermes_root / "tools",
|
hermes_root / "tools",
|
||||||
),
|
),
|
||||||
|
source_root=hermes_root,
|
||||||
|
namespace="hermes",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
"teleo_infrastructure_runtime": {
|
"teleo_infrastructure_runtime": {
|
||||||
"git_head": git_head(deployment_root),
|
"git_head": git_head(deployment_root),
|
||||||
"git_state": git_state(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,
|
"components": components,
|
||||||
|
"python_runtime": python_runtime_manifest(),
|
||||||
"canonical_database": {
|
"canonical_database": {
|
||||||
"included": False,
|
"included": False,
|
||||||
"reason": "Fingerprint through a read-only Postgres manifest in the database-owning harness.",
|
"reason": "Fingerprint through a read-only Postgres manifest in the database-owning harness.",
|
||||||
|
|
@ -372,7 +645,9 @@ def build_manifest(
|
||||||
"profile_root": str(profile),
|
"profile_root": str(profile),
|
||||||
"hermes_root": str(hermes_root),
|
"hermes_root": str(hermes_root),
|
||||||
"deployment_root": str(deployment_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"
|
EXTRACTOR_VERSION = "2"
|
||||||
DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
DEFAULT_OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
|
||||||
DEFAULT_KEY_FILE = Path("/opt/teleo-eval/secrets/openrouter-key")
|
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_SOURCE_CHARS = 120_000
|
||||||
MAX_CANDIDATES = 20
|
MAX_CANDIDATES = 20
|
||||||
MAX_CLAIMS = 3
|
MAX_CLAIMS = 3
|
||||||
|
|
@ -124,20 +124,37 @@ def _load_context(path: Path) -> dict[str, Any]:
|
||||||
return {"database_search_query": query.strip(), "candidate_claims": normalized}
|
return {"database_search_query": query.strip(), "candidate_claims": normalized}
|
||||||
|
|
||||||
|
|
||||||
def build_source_fragments(text: str) -> dict[str, str]:
|
def build_source_fragment_index(text: str) -> tuple[dict[str, str], dict[str, dict[str, Any]]]:
|
||||||
"""Label non-empty source lines so the model selects text instead of copying it."""
|
"""Return selectable lines plus exact line/character provenance."""
|
||||||
fragments: dict[str, str] = {}
|
fragments: dict[str, str] = {}
|
||||||
for line in text.splitlines():
|
locations: dict[str, dict[str, Any]] = {}
|
||||||
if line.strip():
|
offset = 0
|
||||||
fragments[f"S{len(fragments) + 1:05d}"] = line
|
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:
|
if not fragments:
|
||||||
raise PreparationError("extracted text has no selectable source lines")
|
raise PreparationError("extracted text has no selectable source lines")
|
||||||
return fragments
|
return fragments, locations
|
||||||
|
|
||||||
|
|
||||||
def build_prompt(
|
def build_source_fragments(text: str) -> dict[str, str]:
|
||||||
fragments: dict[str, str], context: dict[str, Any], repair_error: str | None = None
|
"""Label non-empty source lines so the model selects text instead of copying it."""
|
||||||
) -> str:
|
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)
|
candidates = json.dumps(context["candidate_claims"], indent=2, ensure_ascii=True)
|
||||||
source = "\n".join(f"[{reference}] {line}" for reference, line in fragments.items())
|
source = "\n".join(f"[{reference}] {line}" for reference, line in fragments.items())
|
||||||
repair = ""
|
repair = ""
|
||||||
|
|
@ -273,9 +290,7 @@ def parse_model_output(content: str) -> dict[str, Any]:
|
||||||
if not isinstance(claim, dict):
|
if not isinstance(claim, dict):
|
||||||
raise PreparationError(f"model extraction claims[{index}] must be an object")
|
raise PreparationError(f"model extraction claims[{index}] must be an object")
|
||||||
if set(claim) != MODEL_CLAIM_KEYS:
|
if set(claim) != MODEL_CLAIM_KEYS:
|
||||||
raise PreparationError(
|
raise PreparationError(f"model extraction claims[{index}] keys must equal {sorted(MODEL_CLAIM_KEYS)}")
|
||||||
f"model extraction claims[{index}] keys must equal {sorted(MODEL_CLAIM_KEYS)}"
|
|
||||||
)
|
|
||||||
confidence = claim.get("confidence")
|
confidence = claim.get("confidence")
|
||||||
if confidence is not None and (
|
if confidence is not None and (
|
||||||
isinstance(confidence, bool) or not isinstance(confidence, (int, float)) or not 0 <= confidence <= 0.75
|
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]
|
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]] = []
|
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"]):
|
for claim_index, claim in enumerate(selection["claims"]):
|
||||||
quote_ref = claim["quote_ref"]
|
quote_ref = claim["quote_ref"]
|
||||||
quote = _resolve_reference(quote_ref, fragments, f"claims[{claim_index}].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]] = []
|
evidence: list[dict[str, str]] = []
|
||||||
for evidence_index, row in enumerate(claim["evidence"]):
|
for evidence_index, row in enumerate(claim["evidence"]):
|
||||||
evidence_ref = row["quote_ref"]
|
evidence_ref = row["quote_ref"]
|
||||||
|
evidence_quote = _resolve_reference(
|
||||||
|
evidence_ref,
|
||||||
|
fragments,
|
||||||
|
f"claims[{claim_index}].evidence[{evidence_index}].quote_ref",
|
||||||
|
)
|
||||||
evidence.append(
|
evidence.append(
|
||||||
{
|
{
|
||||||
"quote": _resolve_reference(
|
"quote": evidence_quote,
|
||||||
evidence_ref,
|
|
||||||
fragments,
|
|
||||||
f"claims[{claim_index}].evidence[{evidence_index}].quote_ref",
|
|
||||||
),
|
|
||||||
"role": row["role"],
|
"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(
|
claims.append(
|
||||||
{
|
{
|
||||||
"claim_key": claim["claim_key"],
|
"claim_key": claim["claim_key"],
|
||||||
|
|
@ -351,7 +386,14 @@ def resolve_model_output(selection: dict[str, Any], fragments: dict[str, str]) -
|
||||||
"reason": duplicate["reason"],
|
"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 {
|
return {
|
||||||
"schema": RESOLVED_OUTPUT_SCHEMA,
|
"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(
|
def build_manifest(
|
||||||
*,
|
*,
|
||||||
args: argparse.Namespace,
|
args: argparse.Namespace,
|
||||||
|
|
@ -427,7 +500,7 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
||||||
artifact_bytes = _read_nonempty(args.artifact, "artifact")
|
artifact_bytes = _read_nonempty(args.artifact, "artifact")
|
||||||
text_bytes = extract_utf8_text(args.artifact, args.text)
|
text_bytes = extract_utf8_text(args.artifact, args.text)
|
||||||
text = text_bytes.decode("utf-8", errors="strict")
|
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)
|
context = _load_context(args.kb_context)
|
||||||
|
|
||||||
requested_output_dir = args.output_dir.expanduser()
|
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})
|
attempts.append({"attempt": attempt, "response_sha256": sha256_bytes(raw.encode("utf-8")), "usage": usage})
|
||||||
try:
|
try:
|
||||||
selection = parse_model_output(raw)
|
selection = parse_model_output(raw)
|
||||||
extraction = resolve_model_output(selection, fragments)
|
extraction = resolve_model_output(selection, fragments, fragment_locations)
|
||||||
compiler._validate_dedupe(
|
compiler._validate_dedupe(
|
||||||
{
|
{
|
||||||
"database_search_query": context["database_search_query"],
|
"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:
|
with os.fdopen(fd, "wb") as handle:
|
||||||
handle.write(_json_bytes(manifest))
|
handle.write(_json_bytes(manifest))
|
||||||
bundle = compiler.compile_source_packet(args.artifact, temp_text, temp_manifest)
|
bundle = compiler.compile_source_packet(args.artifact, temp_text, temp_manifest)
|
||||||
|
validate_bundle_source_locations(bundle, extraction["selected_source_references"])
|
||||||
finally:
|
finally:
|
||||||
if temp_text.exists():
|
if temp_text.exists():
|
||||||
temp_text.unlink()
|
temp_text.unlink()
|
||||||
|
|
@ -518,6 +592,7 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
||||||
raise PreparationError("prepared extraction has no manifest")
|
raise PreparationError("prepared extraction has no manifest")
|
||||||
_write_private(manifest_path, _json_bytes(manifest))
|
_write_private(manifest_path, _json_bytes(manifest))
|
||||||
bundle = compiler.compile_source_packet(args.artifact, text_path, manifest_path)
|
bundle = compiler.compile_source_packet(args.artifact, text_path, manifest_path)
|
||||||
|
validate_bundle_source_locations(bundle, extraction["selected_source_references"])
|
||||||
|
|
||||||
receipt = {
|
receipt = {
|
||||||
"schema": RECEIPT_SCHEMA,
|
"schema": RECEIPT_SCHEMA,
|
||||||
|
|
@ -545,10 +620,20 @@ def prepare(args: argparse.Namespace) -> dict[str, Any]:
|
||||||
"model": args.model,
|
"model": args.model,
|
||||||
"attempts": attempts,
|
"attempts": attempts,
|
||||||
"claim_count": len(extraction["claims"]),
|
"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),
|
"selectable_source_line_count": len(fragments),
|
||||||
"selected_source_references": extraction["selected_source_references"],
|
"selected_source_references": extraction["selected_source_references"],
|
||||||
"notes": extraction["extraction_notes"],
|
"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": {
|
"outputs": {
|
||||||
"output_dir": str(output_dir),
|
"output_dir": str(output_dir),
|
||||||
"extracted_text": str(text_path),
|
"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", {})
|
manifest = apply_payload.setdefault("normalization_manifest", {})
|
||||||
if not isinstance(manifest, dict):
|
if not isinstance(manifest, dict):
|
||||||
raise bound.CheckpointError("normalized child normalization_manifest must be an object")
|
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)
|
parent_packet_sha256 = canonical_sha256(parent)
|
||||||
manifest["parent_packet_sha256"] = parent_packet_sha256
|
manifest["parent_packet_sha256"] = parent_packet_sha256
|
||||||
child_payload_sha256 = canonical_sha256(payload)
|
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}
|
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:
|
def _catalog_snapshot(container: str) -> str:
|
||||||
return _psql(
|
return _psql(
|
||||||
container,
|
container,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import json
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from scripts import leo_behavior_manifest as manifest
|
from scripts import leo_behavior_manifest as manifest
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
@ -58,6 +60,11 @@ gateway:
|
||||||
assert '{"private":"conversation"}' not in encoded
|
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:
|
def test_behavior_manifest_changes_when_a_behavior_layer_changes(tmp_path: Path) -> None:
|
||||||
profile = tmp_path / "profile"
|
profile = tmp_path / "profile"
|
||||||
hermes = tmp_path / "hermes"
|
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 state["worktree_clean"] is False
|
||||||
assert len(str(state["status_sha256"])) == 64
|
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["dedupe"]["duplicates"][0]["claim_id"] == CANDIDATE_ID
|
||||||
assert manifest["claims"][0]["quote"] == CLAIM_QUOTE
|
assert manifest["claims"][0]["quote"] == CLAIM_QUOTE
|
||||||
assert manifest["dedupe"]["duplicates"][0]["source_quote"] == DUPLICATE_QUOTE
|
assert manifest["dedupe"]["duplicates"][0]["source_quote"] == DUPLICATE_QUOTE
|
||||||
assert receipt["extraction"]["selected_source_references"] == [
|
selected = receipt["extraction"]["selected_source_references"]
|
||||||
{"kind": "claim", "reference": "S00003"},
|
assert [(row["kind"], row["reference"]) for row in selected] == [
|
||||||
{"kind": "evidence", "reference": "S00003"},
|
("claim", "S00003"),
|
||||||
{"kind": "duplicate", "reference": "S00002"},
|
("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
|
assert stat.S_IMODE(output_dir.stat().st_mode) == 0o700
|
||||||
for path in output_dir.iterdir():
|
for path in output_dir.iterdir():
|
||||||
assert stat.S_IMODE(path.stat().st_mode) == 0o600
|
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"}
|
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(
|
def test_prepare_returns_no_novel_claims_without_manifest_or_stageable_packet(
|
||||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -195,6 +258,13 @@ def test_binary_artifact_requires_explicit_utf8_extraction(tmp_path: Path) -> No
|
||||||
preparer.extract_utf8_text(artifact, None)
|
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:
|
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"):
|
with pytest.raises(preparer.PreparationError, match=r"must equal https://openrouter\.ai"):
|
||||||
preparer.call_openrouter(
|
preparer.call_openrouter(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
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
|
import run_leo_local_ingestion_proposal_canary as canary # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def _loaded() -> tuple[dict, dict, dict]:
|
def _loaded(path: Path) -> tuple[dict, dict, dict, dict, dict]:
|
||||||
fixture, input_receipt = canary.load_fixture(canary.DEFAULT_FIXTURE)
|
fixture, input_receipt = canary.load_fixture(path)
|
||||||
row_ids = canary.build_row_ids(input_receipt["artifact_sha256"])
|
row_ids = canary.build_row_ids(input_receipt, fixture)
|
||||||
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()
|
|
||||||
parent = canary.build_parent_packet(fixture, input_receipt, row_ids)
|
parent = canary.build_parent_packet(fixture, input_receipt, row_ids)
|
||||||
child = canary.normalized_stage.prepare_normalized_child(parent)
|
child = canary.normalized_stage.prepare_normalized_child(parent)
|
||||||
payload = child["payload"]["apply_payload"]
|
return fixture, input_receipt, row_ids, parent, child
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def test_capture_and_stage_sql_never_mutate_canonical_public_tables() -> None:
|
def _copy_scenario(tmp_path: Path, scenario_path: Path) -> Path:
|
||||||
fixture, input_receipt, row_ids = _loaded()
|
scenario = json.loads(scenario_path.read_text(encoding="utf-8"))
|
||||||
child = canary.normalized_stage.prepare_normalized_child(
|
artifact_source = scenario_path.parent / scenario["artifact"]["path"]
|
||||||
canary.build_parent_packet(fixture, input_receipt, row_ids)
|
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(
|
sql = "\n".join(
|
||||||
(
|
(
|
||||||
canary.build_schema_sql(),
|
|
||||||
canary.build_capture_sql(fixture, input_receipt, row_ids),
|
canary.build_capture_sql(fixture, input_receipt, row_ids),
|
||||||
canary.normalized_stage.build_stage_sql(child),
|
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 "update public." not in sql
|
||||||
assert "delete from 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.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 "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:
|
def test_canonical_snapshot_covers_all_nonempty_canonical_tables() -> None:
|
||||||
fixture, input_receipt, row_ids = _loaded()
|
schema_sql = canary.build_schema_sql().lower()
|
||||||
child = canary.normalized_stage.prepare_normalized_child(
|
snapshot_sql = canary.build_canonical_snapshot_sql().lower()
|
||||||
canary.build_parent_packet(fixture, input_receipt, row_ids)
|
|
||||||
|
for table in ("claims", "sources", "claim_evidence", "claim_edges"):
|
||||||
|
assert f"insert into public.{table}" in schema_sql
|
||||||
|
assert f"from public.{table}" in snapshot_sql
|
||||||
|
assert "order by" in snapshot_sql
|
||||||
|
assert "begin transaction read only" in snapshot_sql
|
||||||
|
assert canary.canonical_snapshot_complete({}) is False
|
||||||
|
assert (
|
||||||
|
canary.canonical_snapshot_complete(
|
||||||
|
{
|
||||||
|
"claims": [{"id": "1"}],
|
||||||
|
"sources": [{"id": "2"}],
|
||||||
|
"claim_evidence": [{"claim_id": "1"}],
|
||||||
|
"claim_edges": [{"from_claim": "1"}],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
is True
|
||||||
)
|
)
|
||||||
apply_payload = child["payload"]["apply_payload"]
|
|
||||||
readback = {
|
|
||||||
"source_capture": {
|
def test_evaluation_fails_when_exact_evidence_locator_is_changed() -> None:
|
||||||
"id": row_ids["source_capture_id"],
|
fixture, input_receipt, row_ids, _parent, child = _loaded(canary.DEFAULT_TELEGRAM_FIXTURE)
|
||||||
"artifact_sha256": input_receipt["artifact_sha256"],
|
readback = _synthetic_readback(fixture, input_receipt, row_ids, child)
|
||||||
"content_sha256": input_receipt["source_content_sha256"],
|
canonical = {
|
||||||
"source_identity": input_receipt["source_identity"],
|
"claims": [{"id": "1"}],
|
||||||
},
|
"sources": [{"id": "2"}],
|
||||||
"claim_extraction": {
|
"claim_evidence": [{"claim_id": "1"}],
|
||||||
"id": row_ids["claim_extraction_id"],
|
"claim_edges": [{"from_claim": "1"}],
|
||||||
"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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
assert all(checks.values())
|
||||||
broken = copy.deepcopy(readback)
|
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 (
|
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"
|
SERVICE_ID = "44444444-4444-4444-4444-444444444444"
|
||||||
PROPOSAL_ID = "99999999-9999-4999-8999-999999999999"
|
PROPOSAL_ID = "99999999-9999-4999-8999-999999999999"
|
||||||
EDGE_ID = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee"
|
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"
|
PRIVATE_MARKER = "PRIVATE-REPLAY-MATERIAL-MUST-NOT-LEAK"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -155,6 +167,58 @@ def proposal_rows() -> tuple[dict, dict, dict]:
|
||||||
return approved, applied, approval
|
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:
|
def applied_envelope(applied: dict) -> dict:
|
||||||
fields = (
|
fields = (
|
||||||
"id",
|
"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:
|
def write_bundle(tmp_path: Path, *, material: dict | None = None) -> argparse.Namespace:
|
||||||
dump = tmp_path / "genesis.dump"
|
dump = tmp_path / "genesis.dump"
|
||||||
dump.write_bytes(b"PGDMPfixture")
|
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=ledger_path,
|
||||||
ledger_sha256=sha256_file(ledger_path),
|
ledger_sha256=sha256_file(ledger_path),
|
||||||
database="teleo",
|
database="teleo",
|
||||||
image=rebuild.canonical_rebuild.DEFAULT_IMAGE,
|
image=rebuild.DEFAULT_REBUILD_IMAGE,
|
||||||
container_prefix="teleo-genesis-ledger-test",
|
container_prefix="teleo-genesis-ledger-test",
|
||||||
tmpfs_mb=256,
|
tmpfs_mb=256,
|
||||||
startup_timeout=30.0,
|
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)
|
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:
|
def test_revise_strategy_material_validates_exact_engine_and_canonicalizes_row_order(
|
||||||
material = replay_material()
|
tmp_path: Path,
|
||||||
material["replay_receipt"]["proposal"]["proposal_type"] = "revise_strategy"
|
) -> None:
|
||||||
material["applied_proposal"]["proposal_type"] = "revise_strategy"
|
material = revise_strategy_replay_material()
|
||||||
material["approved_proposal"]["proposal_type"] = "revise_strategy"
|
material["replay_receipt"]["canonical_rows"]["strategy_nodes"].reverse()
|
||||||
material["approval_snapshot"]["proposal_type"] = "revise_strategy"
|
|
||||||
args = write_bundle(tmp_path, material=material)
|
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)
|
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:
|
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
|
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:
|
def test_proposal_metadata_outside_apply_transition_is_rejected(tmp_path: Path) -> None:
|
||||||
material = replay_material()
|
material = replay_material()
|
||||||
material["applied_proposal"]["rationale"] = "silently changed after review"
|
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"
|
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:
|
def test_unknown_private_field_name_is_not_echoed_in_public_error(tmp_path: Path) -> None:
|
||||||
material = replay_material()
|
material = replay_material()
|
||||||
material[PRIVATE_MARKER] = "private value"
|
material[PRIVATE_MARKER] = "private value"
|
||||||
|
|
@ -484,17 +822,18 @@ create table public.reasoning_tools (
|
||||||
created_at timestamptz not null default now()
|
created_at timestamptz not null default now()
|
||||||
);
|
);
|
||||||
create table public.strategies (
|
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),
|
agent_id uuid not null references public.agents(id),
|
||||||
diagnosis text,
|
diagnosis text,
|
||||||
guiding_policy text,
|
guiding_policy text,
|
||||||
proximate_objectives jsonb,
|
proximate_objectives jsonb,
|
||||||
version integer not null default 1,
|
version integer not null default 1,
|
||||||
active boolean not null default true,
|
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 (
|
create table public.strategy_nodes (
|
||||||
id uuid primary key,
|
id uuid primary key default gen_random_uuid(),
|
||||||
agent_id uuid references public.agents(id),
|
agent_id uuid references public.agents(id),
|
||||||
node_type text,
|
node_type text,
|
||||||
title text,
|
title text,
|
||||||
|
|
@ -508,6 +847,20 @@ create table public.strategy_nodes (
|
||||||
created_at timestamptz not null default now(),
|
created_at timestamptz not null default now(),
|
||||||
updated_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 (
|
create table kb_stage.kb_proposals (
|
||||||
id uuid primary key,
|
id uuid primary key,
|
||||||
proposal_type text not null,
|
proposal_type text not null,
|
||||||
|
|
@ -530,10 +883,34 @@ create table kb_stage.kb_proposals (
|
||||||
);
|
);
|
||||||
|
|
||||||
insert into public.agents (id, handle, kind) values
|
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
|
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_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}');
|
('{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",
|
"run",
|
||||||
"--rm",
|
"--rm",
|
||||||
"--detach",
|
"--detach",
|
||||||
|
"--network",
|
||||||
|
"none",
|
||||||
"--name",
|
"--name",
|
||||||
container,
|
container,
|
||||||
|
"--label",
|
||||||
|
f"{rebuild.canonical_rebuild.CANARY_LABEL}={container}",
|
||||||
"--env",
|
"--env",
|
||||||
f"POSTGRES_DB={database}",
|
f"POSTGRES_DB={database}",
|
||||||
"--env",
|
"--env",
|
||||||
"POSTGRES_HOST_AUTH_METHOD=trust",
|
"POSTGRES_HOST_AUTH_METHOD=trust",
|
||||||
rebuild.canonical_rebuild.DEFAULT_IMAGE,
|
rebuild.DEFAULT_REBUILD_IMAGE,
|
||||||
],
|
],
|
||||||
text=True,
|
text=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
|
|
@ -692,14 +1073,24 @@ def source_postgres() -> Iterator[tuple[str, str]]:
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
check=False,
|
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,
|
tmp_path: Path,
|
||||||
source_postgres: tuple[str, str],
|
*,
|
||||||
) -> None:
|
stem: str,
|
||||||
source_container, database = source_postgres
|
) -> tuple[Path, Path]:
|
||||||
genesis_dump = tmp_path / "genesis.dump"
|
dump = tmp_path / f"{stem}.dump"
|
||||||
dump_result = subprocess.run(
|
dump_result = subprocess.run(
|
||||||
[
|
[
|
||||||
"docker",
|
"docker",
|
||||||
|
|
@ -711,7 +1102,7 @@ def test_live_genesis_plus_ledger_command_rebuilds_exactly_and_proves_cleanup(
|
||||||
"-d",
|
"-d",
|
||||||
database,
|
database,
|
||||||
"--format=custom",
|
"--format=custom",
|
||||||
"--file=/tmp/genesis.dump",
|
f"--file=/tmp/{stem}.dump",
|
||||||
],
|
],
|
||||||
text=True,
|
text=True,
|
||||||
capture_output=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
|
assert dump_result.returncode == 0, dump_result.stderr
|
||||||
copy_result = subprocess.run(
|
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,
|
text=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
assert copy_result.returncode == 0, copy_result.stderr
|
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 = replay_material()
|
||||||
material_path = write_json(tmp_path / "entry-0001.json", 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"
|
final_manifest = tmp_path / "final-manifest.jsonl"
|
||||||
capture_manifest(source_container, database, final_manifest)
|
capture_manifest(source_container, database, final_manifest)
|
||||||
ledger = {
|
completed, receipt, output = run_genesis_ledger_command(
|
||||||
"artifact": rebuild.LEDGER_ARTIFACT,
|
tmp_path=tmp_path,
|
||||||
"contract_version": rebuild.LEDGER_CONTRACT_VERSION,
|
database=database,
|
||||||
"engine": rebuild._engine_hashes(),
|
genesis_dump=genesis_dump,
|
||||||
"genesis": {
|
genesis_manifest=genesis_manifest,
|
||||||
"dump_sha256": sha256_file(genesis_dump),
|
material_path=material_path,
|
||||||
"parity_manifest_sha256": sha256_file(genesis_manifest),
|
final_manifest=final_manifest,
|
||||||
},
|
artifact_stem="insert-only",
|
||||||
"entries": [
|
container_prefix="teleo-genesis-ledger-live-test",
|
||||||
{
|
run_number=1,
|
||||||
"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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert completed.returncode == 0, completed.stderr + completed.stdout
|
|
||||||
receipt = json.loads(output.read_text(encoding="utf-8"))
|
|
||||||
assert receipt["status"] == "pass"
|
assert receipt["status"] == "pass"
|
||||||
assert receipt["genesis_parity"]["status"] == "pass"
|
assert receipt["genesis_parity"]["status"] == "pass"
|
||||||
assert receipt["ledger"]["entries"][0]["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,
|
check=False,
|
||||||
)
|
)
|
||||||
assert inspect.returncode != 0
|
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)
|
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:
|
def test_stage_sql_validates_exact_pending_row_before_commit_and_never_writes_public() -> None:
|
||||||
child = stage.prepare_normalized_child(_parent())
|
child = stage.prepare_normalized_child(_parent())
|
||||||
sql = stage.build_stage_sql(child)
|
sql = stage.build_stage_sql(child)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue